mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-26 02:07:36 +00:00
Merge v0.8.0 into light/newUI
This commit is contained in:
@@ -53,7 +53,7 @@ jobs:
|
|||||||
python ../nix/cache_dependencies.py unpack
|
python ../nix/cache_dependencies.py unpack
|
||||||
|
|
||||||
- name: ccache
|
- name: ccache
|
||||||
uses: hendrikmuhs/ccache-action@v1.2.20
|
uses: hendrikmuhs/ccache-action@v1.2.22
|
||||||
with:
|
with:
|
||||||
key: mac-${{ matrix.arch }}
|
key: mac-${{ matrix.arch }}
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ jobs:
|
|||||||
python ../IfcOpenShell/nix/cache_dependencies.py unpack
|
python ../IfcOpenShell/nix/cache_dependencies.py unpack
|
||||||
|
|
||||||
- name: ccache
|
- name: ccache
|
||||||
uses: hendrikmuhs/ccache-action@v1.2.20
|
uses: hendrikmuhs/ccache-action@v1.2.22
|
||||||
with:
|
with:
|
||||||
key: ubuntu-22.04-${{ runner.arch }}
|
key: ubuntu-22.04-${{ runner.arch }}
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ jobs:
|
|||||||
python3 ../nix/cache_dependencies.py unpack
|
python3 ../nix/cache_dependencies.py unpack
|
||||||
|
|
||||||
- name: ccache
|
- name: ccache
|
||||||
uses: hendrikmuhs/ccache-action@v1.2.20
|
uses: hendrikmuhs/ccache-action@v1.2.22
|
||||||
with:
|
with:
|
||||||
key: ubuntu-22.04-${{ runner.arch }}-rockylinux9
|
key: ubuntu-22.04-${{ runner.arch }}-rockylinux9
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ jobs:
|
|||||||
python3 ../nix/cache_dependencies.py unpack
|
python3 ../nix/cache_dependencies.py unpack
|
||||||
|
|
||||||
- name: ccache
|
- name: ccache
|
||||||
uses: hendrikmuhs/ccache-action@v1.2.20
|
uses: hendrikmuhs/ccache-action@v1.2.22
|
||||||
with:
|
with:
|
||||||
key: ubuntu-22.04-${{ runner.arch }}-rockylinux9
|
key: ubuntu-22.04-${{ runner.arch }}-rockylinux9
|
||||||
|
|
||||||
|
|||||||
@@ -5,11 +5,26 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build_ifcopenshell:
|
build_ifcopenshell:
|
||||||
runs-on: windows-2022
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
arch: ['x64']
|
include:
|
||||||
|
- arch: x64
|
||||||
|
runs_on: windows-2022
|
||||||
|
deps_dir: _deps-vs2022-x64-installed
|
||||||
|
vcvars: '"C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"'
|
||||||
|
build_branch: windows-x64
|
||||||
|
zip_suffix: win64
|
||||||
|
|
||||||
|
- arch: ARM64
|
||||||
|
runs_on: windows-11-arm
|
||||||
|
deps_dir: _deps-vs2022-ARM64-installed
|
||||||
|
vcvars: '"C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsarm64.bat"'
|
||||||
|
build_branch: windows-arm64
|
||||||
|
zip_suffix: win-arm64
|
||||||
|
|
||||||
|
runs-on: ${{ matrix.runs_on }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Repository
|
- name: Checkout Repository
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
@@ -20,8 +35,8 @@ jobs:
|
|||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
repository: IfcOpenShell/build-outputs
|
repository: IfcOpenShell/build-outputs
|
||||||
path: _deps-vs2022-x64-installed
|
path: ${{ matrix.deps_dir }}
|
||||||
ref: windows-${{ matrix.arch }}
|
ref: ${{ matrix.build_branch }}
|
||||||
lfs: true
|
lfs: true
|
||||||
token: ${{ secrets.BUILD_REPO_TOKEN }}
|
token: ${{ secrets.BUILD_REPO_TOKEN }}
|
||||||
|
|
||||||
@@ -31,13 +46,13 @@ jobs:
|
|||||||
|
|
||||||
- name: Unpack Dependencies
|
- name: Unpack Dependencies
|
||||||
run: |
|
run: |
|
||||||
cd _deps-vs2022-x64-installed
|
cd ${{ matrix.deps_dir }}
|
||||||
Get-ChildItem -Path . -Filter 'cache-*.zip' | ForEach-Object {
|
Get-ChildItem -Path . -Filter 'cache-*.zip' | ForEach-Object {
|
||||||
7z x $_.FullName
|
7z x $_.FullName
|
||||||
}
|
}
|
||||||
|
|
||||||
- name: ccache
|
- name: ccache
|
||||||
uses: hendrikmuhs/ccache-action@v1.2.20
|
uses: hendrikmuhs/ccache-action@v1.2.22
|
||||||
with:
|
with:
|
||||||
key: win-${{ matrix.arch }}
|
key: win-${{ matrix.arch }}
|
||||||
# Windows ccache needs ~1GB
|
# Windows ccache needs ~1GB
|
||||||
@@ -46,14 +61,16 @@ jobs:
|
|||||||
|
|
||||||
- name: Run Build Script And Pack .zip Archives
|
- name: Run Build Script And Pack .zip Archives
|
||||||
shell: cmd
|
shell: cmd
|
||||||
|
env:
|
||||||
|
TARGET_ARCH: ${{ matrix.arch }} # lets the Python script know which arch to target (optional override)
|
||||||
run: |
|
run: |
|
||||||
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
|
call ${{ matrix.vcvars }}
|
||||||
cd win
|
cd win
|
||||||
python build-all-win.py
|
python build-all-win.py
|
||||||
|
|
||||||
- name: Pack Dependencies
|
- name: Pack Dependencies
|
||||||
run: |
|
run: |
|
||||||
cd _deps-vs2022-x64-installed
|
cd ${{ matrix.deps_dir }}
|
||||||
Get-ChildItem -Path . -Directory | ForEach-Object {
|
Get-ChildItem -Path . -Directory | ForEach-Object {
|
||||||
$cacheFile = "cache-$($_.Name).zip"
|
$cacheFile = "cache-$($_.Name).zip"
|
||||||
echo $cacheFile
|
echo $cacheFile
|
||||||
@@ -64,12 +81,13 @@ jobs:
|
|||||||
|
|
||||||
- name: Commit and Push Changes to Build Repository
|
- name: Commit and Push Changes to Build Repository
|
||||||
run: |
|
run: |
|
||||||
cd _deps-vs2022-x64-installed
|
cd ${{ matrix.deps_dir }}
|
||||||
git config user.name "IfcOpenBot"
|
git config user.name "IfcOpenBot"
|
||||||
git config user.email "ifcopenbot@ifcopenshell.org"
|
git config user.email "ifcopenbot@ifcopenshell.org"
|
||||||
|
git checkout -B ${{ matrix.build_branch }}
|
||||||
git add *.zip
|
git add *.zip
|
||||||
git commit -m "Update build artifacts [skip ci]" || echo "No changes to commit"
|
git commit -m "Update build artifacts [skip ci]" || echo "No changes to commit"
|
||||||
git push || echo "Push failed"
|
git push --set-upstream origin ${{ matrix.build_branch }} || echo "Push failed"
|
||||||
|
|
||||||
- name: Configure AWS Credentials
|
- name: Configure AWS Credentials
|
||||||
uses: aws-actions/configure-aws-credentials@v6
|
uses: aws-actions/configure-aws-credentials@v6
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
lint-formatting:
|
lint-formatting:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
MIN_IOS_PY_VERSION: "3.10"
|
||||||
|
MIN_BLENDER_PY_VERSION: "3.11"
|
||||||
steps:
|
steps:
|
||||||
- name: Action - checkout repository
|
- name: Action - checkout repository
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
@@ -14,12 +17,12 @@ jobs:
|
|||||||
- name: Action - install python
|
- name: Action - install python
|
||||||
uses: actions/setup-python@v6
|
uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: "3.10"
|
python-version: ${{ env.MIN_IOS_PY_VERSION }}
|
||||||
|
|
||||||
- name: Action - install python
|
- name: Action - install python
|
||||||
uses: actions/setup-python@v6
|
uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: "3.11"
|
python-version: ${{ env.MIN_BLENDER_PY_VERSION }}
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
@@ -33,8 +36,10 @@ jobs:
|
|||||||
id: syntax-errors
|
id: syntax-errors
|
||||||
run: |
|
run: |
|
||||||
ERROR=0
|
ERROR=0
|
||||||
python3.10 -W error -m compileall -q src/ifcopenshell-python || ERROR=1
|
# Using 2 Python versions - one minimum required for IfcOpenShell
|
||||||
python3.11 -W error -m compileall -q src/bonsai || ERROR=1
|
# and other that's used by Blender currently.
|
||||||
|
python${{ env.MIN_IOS_PY_VERSION }} -W error -m compileall -q src/ifcopenshell-python || ERROR=1
|
||||||
|
python${{ env.MIN_BLENDER_PY_VERSION }} -W error -m compileall -q src/bonsai || ERROR=1
|
||||||
exit $ERROR
|
exit $ERROR
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
|
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ jobs:
|
|||||||
# Ensure Bonsai and ifcsverchok enable/disable works before uploading to extensions repo.
|
# Ensure Bonsai and ifcsverchok enable/disable works before uploading to extensions repo.
|
||||||
|
|
||||||
# Download Blender.
|
# Download Blender.
|
||||||
wget -q -O blender.tar.xz https://download.blender.org/release/Blender5.0/blender-5.0.1-linux-x64.tar.xz
|
wget -q -O blender.tar.xz https://download.blender.org/release/Blender5.0/blender-5.1.0-linux-x64.tar.xz
|
||||||
tar -xf blender.tar.xz
|
tar -xf blender.tar.xz
|
||||||
|
|
||||||
# Setup Blender.
|
# Setup Blender.
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
name: ci-ifcedit-pypi
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
activate:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: |
|
||||||
|
github.repository == 'IfcOpenShell/IfcOpenShell'
|
||||||
|
steps:
|
||||||
|
- name: Set env
|
||||||
|
run: echo ok go
|
||||||
|
|
||||||
|
build:
|
||||||
|
needs: activate
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
- uses: actions/setup-python@v6
|
||||||
|
with:
|
||||||
|
python-version: '3.11'
|
||||||
|
- name: Compile
|
||||||
|
run: |
|
||||||
|
pip install build
|
||||||
|
cd src/ifcedit &&
|
||||||
|
make dist IS_STABLE=TRUE
|
||||||
|
- name: Publish a Python distribution to PyPI
|
||||||
|
uses: pypa/gh-action-pypi-publish@release/v1
|
||||||
|
with:
|
||||||
|
user: __token__
|
||||||
|
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||||
|
packages_dir: src/ifcedit/dist
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
name: ci-ifcmcp-pypi
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
activate:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: |
|
||||||
|
github.repository == 'IfcOpenShell/IfcOpenShell'
|
||||||
|
steps:
|
||||||
|
- name: Set env
|
||||||
|
run: echo ok go
|
||||||
|
|
||||||
|
build:
|
||||||
|
needs: activate
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
- uses: actions/setup-python@v6
|
||||||
|
with:
|
||||||
|
python-version: '3.11'
|
||||||
|
- name: Compile
|
||||||
|
run: |
|
||||||
|
pip install build
|
||||||
|
cd src/ifcmcp &&
|
||||||
|
make dist IS_STABLE=TRUE
|
||||||
|
- name: Publish a Python distribution to PyPI
|
||||||
|
uses: pypa/gh-action-pypi-publish@release/v1
|
||||||
|
with:
|
||||||
|
user: __token__
|
||||||
|
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||||
|
packages_dir: src/ifcmcp/dist
|
||||||
|
verbose: true
|
||||||
@@ -24,7 +24,7 @@ jobs:
|
|||||||
if: |
|
if: |
|
||||||
github.repository == 'IfcOpenShell/IfcOpenShell'
|
github.repository == 'IfcOpenShell/IfcOpenShell'
|
||||||
steps:
|
steps:
|
||||||
- uses: mamba-org/setup-micromamba@v2 # https://github.com/mamba-org/setup-micromamba
|
- uses: mamba-org/setup-micromamba@v3 # https://github.com/mamba-org/setup-micromamba
|
||||||
with:
|
with:
|
||||||
environment-name: test-env
|
environment-name: test-env
|
||||||
create-args: >-
|
create-args: >-
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
curl -L https://github.com/phracker/MacOSX-SDKs/releases/download/11.3/MacOSX10.13.sdk.tar.xz | tar -xvJf - -C /Users/runner/work/
|
curl -L https://github.com/phracker/MacOSX-SDKs/releases/download/11.3/MacOSX10.13.sdk.tar.xz | tar -xvJf - -C /Users/runner/work/
|
||||||
|
|
||||||
- uses: mamba-org/setup-micromamba@v2 # https://github.com/mamba-org/setup-micromamba
|
- uses: mamba-org/setup-micromamba@v3 # https://github.com/mamba-org/setup-micromamba
|
||||||
with:
|
with:
|
||||||
environment-name: test-env
|
environment-name: test-env
|
||||||
create-args: >-
|
create-args: >-
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ jobs:
|
|||||||
|
|
||||||
-
|
-
|
||||||
name: ccache
|
name: ccache
|
||||||
uses: hendrikmuhs/ccache-action@v1.2.20
|
uses: hendrikmuhs/ccache-action@v1.2.22
|
||||||
|
|
||||||
-
|
-
|
||||||
name: Build ifcopenshell
|
name: Build ifcopenshell
|
||||||
@@ -91,26 +91,26 @@ jobs:
|
|||||||
lfs: true
|
lfs: true
|
||||||
|
|
||||||
- name: Download
|
- name: Download
|
||||||
uses: actions/download-artifact@v8.0.0
|
uses: actions/download-artifact@v8.0.1
|
||||||
with:
|
with:
|
||||||
# Artifact name
|
# Artifact name
|
||||||
name: ifcos-artifacts
|
name: ifcos-artifacts
|
||||||
path: artifacts/
|
path: artifacts/
|
||||||
-
|
-
|
||||||
name: Set up QEMU
|
name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v4
|
||||||
-
|
-
|
||||||
name: Set up Docker Buildx
|
name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v4
|
||||||
-
|
-
|
||||||
name: Login to Dockerhub
|
name: Login to Dockerhub
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v4
|
||||||
with:
|
with:
|
||||||
username: aecgeeks
|
username: aecgeeks
|
||||||
password: ${{ secrets.DOCKER_HUB_TOKEN }}
|
password: ${{ secrets.DOCKER_HUB_TOKEN }}
|
||||||
-
|
-
|
||||||
name: Build container image
|
name: Build container image
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v7
|
||||||
with:
|
with:
|
||||||
context: artifacts
|
context: artifacts
|
||||||
repository: aecgeeks/ifcopenshell
|
repository: aecgeeks/ifcopenshell
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
pyver: [py39, py310, py311, py312, py313, py314]
|
pyver: [py310, py311, py312, py313, py314]
|
||||||
config:
|
config:
|
||||||
- {
|
- {
|
||||||
name: "Windows 64bit",
|
name: "Windows 64bit",
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
pyver: [py39, py310, py311, py312, py313, py314]
|
pyver: [py310, py311, py312, py313, py314]
|
||||||
config:
|
config:
|
||||||
- {
|
- {
|
||||||
name: "Windows 64bit",
|
name: "Windows 64bit",
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
name: ci-ifcquery-pypi
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
activate:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: |
|
||||||
|
github.repository == 'IfcOpenShell/IfcOpenShell'
|
||||||
|
steps:
|
||||||
|
- name: Set env
|
||||||
|
run: echo ok go
|
||||||
|
|
||||||
|
build:
|
||||||
|
needs: activate
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
- uses: actions/setup-python@v6
|
||||||
|
with:
|
||||||
|
python-version: '3.11'
|
||||||
|
- name: Compile
|
||||||
|
run: |
|
||||||
|
pip install build
|
||||||
|
cd src/ifcquery &&
|
||||||
|
make dist IS_STABLE=TRUE
|
||||||
|
- name: Publish a Python distribution to PyPI
|
||||||
|
uses: pypa/gh-action-pypi-publish@release/v1
|
||||||
|
with:
|
||||||
|
user: __token__
|
||||||
|
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||||
|
packages_dir: src/ifcquery/dist
|
||||||
@@ -79,7 +79,7 @@ jobs:
|
|||||||
libhdf5-dev libcgal-dev libeigen3-dev
|
libhdf5-dev libcgal-dev libeigen3-dev
|
||||||
|
|
||||||
- name: ccache
|
- name: ccache
|
||||||
uses: hendrikmuhs/ccache-action@v1.2.20
|
uses: hendrikmuhs/ccache-action@v1.2.22
|
||||||
with:
|
with:
|
||||||
key: ubuntu-22.04-${{ runner.arch }}
|
key: ubuntu-22.04-${{ runner.arch }}
|
||||||
|
|
||||||
@@ -181,6 +181,7 @@ jobs:
|
|||||||
"-DSCHEMA_VERSIONS=2x3;4;4x3_add2" \
|
"-DSCHEMA_VERSIONS=2x3;4;4x3_add2" \
|
||||||
-DGLTF_SUPPORT=On \
|
-DGLTF_SUPPORT=On \
|
||||||
-DWITH_ROCKSDB=On \
|
-DWITH_ROCKSDB=On \
|
||||||
|
-DBUILD_EXAMPLES=ON \
|
||||||
../cmake
|
../cmake
|
||||||
sudo make -j $(nproc)
|
sudo make -j $(nproc)
|
||||||
sudo make install
|
sudo make install
|
||||||
@@ -253,6 +254,7 @@ jobs:
|
|||||||
cd ../ifcpatch && make test || ERROR=1
|
cd ../ifcpatch && make test || ERROR=1
|
||||||
pip install -e ../ifctester --no-deps
|
pip install -e ../ifctester --no-deps
|
||||||
cd ../ifctester && make test || ERROR=1
|
cd ../ifctester && make test || ERROR=1
|
||||||
|
make build-ids-docs || ERROR=1
|
||||||
# Run mathutils related tests at the end to ensure no other code is relying on mathutils.
|
# Run mathutils related tests at the end to ensure no other code is relying on mathutils.
|
||||||
cd ../ifcopenshell-python
|
cd ../ifcopenshell-python
|
||||||
pip install mathutils
|
pip install mathutils
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
name: Deploy AI chat App to static page repo
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
|
pages: write
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
paths:
|
||||||
|
- 'src/ifcchat/**'
|
||||||
|
- '.github/workflows/publish-aichat-app.yaml'
|
||||||
|
branches:
|
||||||
|
- v0.8.0
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
activate:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: |
|
||||||
|
github.repository == 'IfcOpenShell/IfcOpenShell'
|
||||||
|
steps:
|
||||||
|
- name: Set env
|
||||||
|
run: echo ok go
|
||||||
|
|
||||||
|
build:
|
||||||
|
needs: activate
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout (recursive)
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Checkout intermediate Pages repo
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
repository: IfcOpenShell/aichat_ifcopenshell_org_static_html
|
||||||
|
ref: gh-pages
|
||||||
|
path: output
|
||||||
|
token: ${{ secrets.WEBSITE_PUBLISH }}
|
||||||
|
- name: Sync demo app into target subfolder
|
||||||
|
run: |
|
||||||
|
rsync -av --delete --exclude='.git/' src/ifcchat/ output/
|
||||||
|
- name: Setup Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: "3.x"
|
||||||
|
- name: Download wheels
|
||||||
|
working-directory: output/
|
||||||
|
run: |
|
||||||
|
pip download ifcquery==0.8.5 ifcopenshell-mcp==0.8.5 ifcedit==0.8.5 lark==1.3.1 isodate==0.7.2 --no-deps -d ./dist
|
||||||
|
- name: Commit and push if changed
|
||||||
|
working-directory: output
|
||||||
|
run: |
|
||||||
|
git config --global user.name 'IfcOpenBot'
|
||||||
|
git config --global user.email 'IfcOpenBot@users.noreply.github.com'
|
||||||
|
|
||||||
|
git add .
|
||||||
|
if git diff --cached --quiet; then
|
||||||
|
echo "No changes to commit"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
git commit -m "$(git log --oneline -1)"
|
||||||
|
git push origin gh-pages
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
name: Deploy Pyodide Demo App to GitHub Pages
|
name: Deploy Pyodide Demo App to static page repo
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
id-token: write
|
id-token: write
|
||||||
@@ -11,6 +11,7 @@ on:
|
|||||||
- '.github/workflows/publish-pyodide-demo-app.yml'
|
- '.github/workflows/publish-pyodide-demo-app.yml'
|
||||||
branches:
|
branches:
|
||||||
- v0.8.0
|
- v0.8.0
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
activate:
|
activate:
|
||||||
@@ -30,21 +31,27 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Pages
|
- name: Checkout intermediate Pages repo
|
||||||
uses: actions/configure-pages@v5
|
uses: actions/checkout@v4
|
||||||
- name: Upload static files as artifact
|
|
||||||
id: deployment
|
|
||||||
uses: actions/upload-pages-artifact@v4
|
|
||||||
with:
|
with:
|
||||||
path: src/pyodide/demo-app/
|
repository: IfcOpenShell/wasm_ifcopenshell_org_static_html
|
||||||
|
ref: gh-pages
|
||||||
|
path: output
|
||||||
|
token: ${{ secrets.WEBSITE_PUBLISH }}
|
||||||
|
- name: Sync demo app into target subfolder
|
||||||
|
run: |
|
||||||
|
rsync -av --delete --exclude='.git/' src/pyodide/demo-app/ output/
|
||||||
|
- name: Commit and push if changed
|
||||||
|
working-directory: output
|
||||||
|
run: |
|
||||||
|
git config --global user.name 'IfcOpenBot'
|
||||||
|
git config --global user.email 'IfcOpenBot@users.noreply.github.com'
|
||||||
|
|
||||||
deploy:
|
git add .
|
||||||
environment:
|
if git diff --cached --quiet; then
|
||||||
name: github-pages
|
echo "No changes to commit"
|
||||||
url: ${{ steps.deployment.outputs.page_url }}
|
exit 0
|
||||||
runs-on: ubuntu-latest
|
fi
|
||||||
needs: build
|
|
||||||
steps:
|
git commit -m "$(git log --oneline -1)"
|
||||||
- name: Deploy to GitHub Pages
|
git push origin gh-pages
|
||||||
id: deployment
|
|
||||||
uses: actions/deploy-pages@v4
|
|
||||||
|
|||||||
+16
-3
@@ -5,6 +5,8 @@
|
|||||||
/_installed-vs*-x*/
|
/_installed-vs*-x*/
|
||||||
/build/
|
/build/
|
||||||
/src/examples/build/
|
/src/examples/build/
|
||||||
|
# ifctester docs output
|
||||||
|
/src/ifctester/test/build/
|
||||||
|
|
||||||
# output directories
|
# output directories
|
||||||
/cmake/out/
|
/cmake/out/
|
||||||
@@ -12,6 +14,7 @@
|
|||||||
/src/ifcmax/out/
|
/src/ifcmax/out/
|
||||||
/src/ifcwrap/out/
|
/src/ifcwrap/out/
|
||||||
/src/qtviewer/out/
|
/src/qtviewer/out/
|
||||||
|
/src/ifctester/webapp/public/pyodide/
|
||||||
|
|
||||||
/win/BuildDepsCache*.txt
|
/win/BuildDepsCache*.txt
|
||||||
|
|
||||||
@@ -80,10 +83,14 @@ src/ifcopenshell-python/test/build
|
|||||||
# bonsai i18n
|
# bonsai i18n
|
||||||
src/bonsai/bonsai/translations.py
|
src/bonsai/bonsai/translations.py
|
||||||
|
|
||||||
# bonsai test temp files
|
# bonsai external dependencies (cloned for just ty checks)
|
||||||
|
src/bonsai/external_dependencies/
|
||||||
|
|
||||||
|
# bonsai test temp/cache files
|
||||||
src/bonsai/test/files/temp
|
src/bonsai/test/files/temp
|
||||||
src/bonsai/test/files/basic.ifc.cache.blend
|
src/bonsai/test/files/*.cache.blend
|
||||||
src/bonsai/test/files/basic.ifc.cache.sqlite
|
src/bonsai/test/files/*.cache.json
|
||||||
|
src/bonsai/test/files/*.cache.sqlite
|
||||||
|
|
||||||
# bonsai data
|
# bonsai data
|
||||||
src/bonsai/bonsai/bim/data/build/
|
src/bonsai/bonsai/bim/data/build/
|
||||||
@@ -115,3 +122,9 @@ dev_environment.bat
|
|||||||
|
|
||||||
src/ifcopenshell-python/ifcopenshell/express/*.exp
|
src/ifcopenshell-python/ifcopenshell/express/*.exp
|
||||||
src/ifcopenshell-python/ifcopenshell/express/*.exp.cache.dat
|
src/ifcopenshell-python/ifcopenshell/express/*.exp.cache.dat
|
||||||
|
|
||||||
|
|
||||||
|
# temp files from AI coding tools
|
||||||
|
*.claude
|
||||||
|
*.py.tmp*
|
||||||
|
*.json.tmp*
|
||||||
|
|||||||
@@ -50,10 +50,13 @@ Contents
|
|||||||
| [ifcconvert](https://docs.ifcopenshell.org/ifcconvert.html) | CLI app to convert IFC to many other formats | LGPL-3.0-or-later\* | [](https://docs.ifcopenshell.org/ifcconvert/installation.html) [](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=ifcconvert&expanded=true)
|
| [ifcconvert](https://docs.ifcopenshell.org/ifcconvert.html) | CLI app to convert IFC to many other formats | LGPL-3.0-or-later\* | [](https://docs.ifcopenshell.org/ifcconvert/installation.html) [](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=ifcconvert&expanded=true)
|
||||||
| [ifccsv](https://docs.ifcopenshell.org/ifccsv.html) | Library and CLI app to export and import schedules from IFC | LGPL-3.0-or-later | [](https://pypi.org/project/ifccsv/) |
|
| [ifccsv](https://docs.ifcopenshell.org/ifccsv.html) | Library and CLI app to export and import schedules from IFC | LGPL-3.0-or-later | [](https://pypi.org/project/ifccsv/) |
|
||||||
| [ifcdiff](https://docs.ifcopenshell.org/ifcdiff.html) | Compare changes between IFC models | LGPL-3.0-or-later | [](https://pypi.org/project/ifcdiff/) |
|
| [ifcdiff](https://docs.ifcopenshell.org/ifcdiff.html) | Compare changes between IFC models | LGPL-3.0-or-later | [](https://pypi.org/project/ifcdiff/) |
|
||||||
|
| [ifcedit](https://docs.ifcopenshell.org/ifcedit.html) | CLI wrapper for ifcopenshell.api IFC model mutation functions | LGPL-3.0-or-later | [](https://pypi.org/project/ifcedit/) |
|
||||||
| [ifcfm](https://docs.ifcopenshell.org/ifcfm.html) | Extract IFC data for FM handover requirements | LGPL-3.0-or-later | [](https://pypi.org/project/ifcfm/) |
|
| [ifcfm](https://docs.ifcopenshell.org/ifcfm.html) | Extract IFC data for FM handover requirements | LGPL-3.0-or-later | [](https://pypi.org/project/ifcfm/) |
|
||||||
| [ifcmax](https://docs.ifcopenshell.org/ifcmax.html) | Historic extension for IFC support in 3DS Max | LGPL-3.0-or-later\* | [](https://docs.ifcopenshell.org/ifcmax.html)
|
| [ifcmax](https://docs.ifcopenshell.org/ifcmax.html) | Historic extension for IFC support in 3DS Max | LGPL-3.0-or-later\* | [](https://docs.ifcopenshell.org/ifcmax.html)
|
||||||
|
| [ifcmcp](https://docs.ifcopenshell.org/ifcmcp.html) | MCP server for querying and editing IFC building models | LGPL-3.0-or-later | [](https://pypi.org/project/ifcmcp/) |
|
||||||
| [ifcopenshell-python](https://docs.ifcopenshell.org/ifcopenshell-python.html) | Python library for IFC manipulation | LGPL-3.0-or-later\* | [](https://docs.ifcopenshell.org/ifcopenshell-python/installation.html) [](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=ifcopenshell-python&expanded=true) [](https://pypi.org/project/ifcopenshell/) [](https://anaconda.org/conda-forge/ifcopenshell) [](https://anaconda.org/ifcopenshell/ifcopenshell) [](https://hub.docker.com/r/aecgeeks/ifcopenshell) [](https://aur.archlinux.org/packages/ifcopenshell) [](https://aur.archlinux.org/packages/ifcopenshell-git) [Pyodide WASM Wheels](https://github.com/IfcOpenShell/wasm-wheels#pyodide-test-wheels) |
|
| [ifcopenshell-python](https://docs.ifcopenshell.org/ifcopenshell-python.html) | Python library for IFC manipulation | LGPL-3.0-or-later\* | [](https://docs.ifcopenshell.org/ifcopenshell-python/installation.html) [](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=ifcopenshell-python&expanded=true) [](https://pypi.org/project/ifcopenshell/) [](https://anaconda.org/conda-forge/ifcopenshell) [](https://anaconda.org/ifcopenshell/ifcopenshell) [](https://hub.docker.com/r/aecgeeks/ifcopenshell) [](https://aur.archlinux.org/packages/ifcopenshell) [](https://aur.archlinux.org/packages/ifcopenshell-git) [Pyodide WASM Wheels](https://github.com/IfcOpenShell/wasm-wheels#pyodide-test-wheels) |
|
||||||
| [ifcpatch](https://docs.ifcopenshell.org/ifcpatch.html) | Utility to run pre-packaged scripts to manipulate IFCs | LGPL-3.0-or-later | [](https://pypi.org/project/ifcpatch/) |
|
| [ifcpatch](https://docs.ifcopenshell.org/ifcpatch.html) | Utility to run pre-packaged scripts to manipulate IFCs | LGPL-3.0-or-later | [](https://pypi.org/project/ifcpatch/) |
|
||||||
|
| [ifcquery](https://docs.ifcopenshell.org/ifcquery.html) | CLI tool for querying and inspecting IFC building models | LGPL-3.0-or-later | [](https://pypi.org/project/ifcquery/) |
|
||||||
| [ifcsverchok](https://docs.ifcopenshell.org/ifcsverchok.html) | Blender Add-on for visual node programming with IFC | GPL-3.0-or-later | [](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=ifcsverchok&expanded=true)
|
| [ifcsverchok](https://docs.ifcopenshell.org/ifcsverchok.html) | Blender Add-on for visual node programming with IFC | GPL-3.0-or-later | [](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=ifcsverchok&expanded=true)
|
||||||
| [ifctester](https://docs.ifcopenshell.org/ifctester.html) | Library, CLI and webapp for IDS model auditing | LGPL-3.0-or-later | [](https://pypi.org/project/ifctester/) |
|
| [ifctester](https://docs.ifcopenshell.org/ifctester.html) | Library, CLI and webapp for IDS model auditing | LGPL-3.0-or-later | [](https://pypi.org/project/ifctester/) |
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import hashlib
|
|||||||
import os
|
import os
|
||||||
import pathlib
|
import pathlib
|
||||||
import re
|
import re
|
||||||
|
import subprocess
|
||||||
from typing import NoReturn
|
from typing import NoReturn
|
||||||
from urllib import request
|
from urllib import request
|
||||||
|
|
||||||
@@ -20,7 +21,7 @@ from github import Github
|
|||||||
|
|
||||||
|
|
||||||
def get_repo_tag_names() -> list[str]:
|
def get_repo_tag_names() -> list[str]:
|
||||||
git_return = os.popen("git tag -l").read()
|
git_return = subprocess.check_output("git tag -l", text=True)
|
||||||
tag_names = [tag_name for tag_name in git_return.split("\n") if tag_name]
|
tag_names = [tag_name for tag_name in git_return.split("\n") if tag_name]
|
||||||
print(f"{len(tag_names)} tag_names found in repo")
|
print(f"{len(tag_names)} tag_names found in repo")
|
||||||
return tag_names
|
return tag_names
|
||||||
@@ -78,6 +79,10 @@ def get_release_zip(tag: str) -> tuple[str, str]:
|
|||||||
raise Exception(f"Couldn't find the release matching '{python_version}' and '{TARGET_OS}' in tag '{tag}'.")
|
raise Exception(f"Couldn't find the release matching '{python_version}' and '{TARGET_OS}' in tag '{tag}'.")
|
||||||
|
|
||||||
|
|
||||||
|
def run(command: str) -> None:
|
||||||
|
subprocess.check_output(command)
|
||||||
|
|
||||||
|
|
||||||
start = datetime.datetime.now()
|
start = datetime.datetime.now()
|
||||||
|
|
||||||
URL_CHOCO_PACKAGE = "https://community.chocolatey.org/packages/blender"
|
URL_CHOCO_PACKAGE = "https://community.chocolatey.org/packages/blender"
|
||||||
@@ -97,7 +102,7 @@ should_release = False
|
|||||||
target_release_tag = ""
|
target_release_tag = ""
|
||||||
TARGET_OS = "windows-x64"
|
TARGET_OS = "windows-x64"
|
||||||
|
|
||||||
git_status = os.popen("git status").read()
|
git_status = subprocess.check_output("git status", text=True)
|
||||||
print(git_status)
|
print(git_status)
|
||||||
|
|
||||||
for tag_name in get_repo_tag_names():
|
for tag_name in get_repo_tag_names():
|
||||||
@@ -147,7 +152,7 @@ blenderbim_build_version = target_release_tag.replace("blenderbim-", "")
|
|||||||
|
|
||||||
# url_blenderbim_py3x_win_zip
|
# url_blenderbim_py3x_win_zip
|
||||||
release_zip_file_name, url_blenderbim_py3x_win_zip = get_release_zip(target_release_tag)
|
release_zip_file_name, url_blenderbim_py3x_win_zip = get_release_zip(target_release_tag)
|
||||||
os.popen(f"wget {url_blenderbim_py3x_win_zip} --no-verbose").read()
|
subprocess.check_call(f"wget {url_blenderbim_py3x_win_zip} --no-verbose")
|
||||||
|
|
||||||
# sha256sum_blenderbim_py310_win_zip
|
# sha256sum_blenderbim_py310_win_zip
|
||||||
sha256sum_blenderbim_py3x_win_zip = get_file_sha256_hash(release_zip_file_name)
|
sha256sum_blenderbim_py3x_win_zip = get_file_sha256_hash(release_zip_file_name)
|
||||||
@@ -201,13 +206,13 @@ print("[INFO] inserting dynamic chocolatey package parameters successful")
|
|||||||
print("\n_____ build choco.exe with mono")
|
print("\n_____ build choco.exe with mono")
|
||||||
|
|
||||||
choco_version = "1.1.0"
|
choco_version = "1.1.0"
|
||||||
os.popen(f"wget https://github.com/chocolatey/choco/archive/refs/tags/{choco_version}.tar.gz --quiet").read()
|
run(f"wget https://github.com/chocolatey/choco/archive/refs/tags/{choco_version}.tar.gz --quiet")
|
||||||
os.popen(f"tar -xzf {choco_version}.tar.gz").read()
|
run(f"tar -xzf {choco_version}.tar.gz")
|
||||||
print("choco tar unpack successful")
|
print("choco tar unpack successful")
|
||||||
os.chdir("choco-1.1.0")
|
os.chdir("choco-1.1.0")
|
||||||
os.popen("./build.sh").read()
|
run("./build.sh")
|
||||||
|
|
||||||
os.popen("cp -r build_output/chocolatey /opt/chocolatey").read()
|
run("cp -r build_output/chocolatey /opt/chocolatey")
|
||||||
os.chdir(BLENDERBIM_DIR)
|
os.chdir(BLENDERBIM_DIR)
|
||||||
|
|
||||||
if pathlib.Path("/opt/chocolatey/choco.exe").exists():
|
if pathlib.Path("/opt/chocolatey/choco.exe").exists():
|
||||||
@@ -215,11 +220,15 @@ if pathlib.Path("/opt/chocolatey/choco.exe").exists():
|
|||||||
|
|
||||||
print("\n_____ build choco pack")
|
print("\n_____ build choco pack")
|
||||||
|
|
||||||
os.popen("mono /opt/chocolatey/choco.exe pack --allow-unofficial").read()
|
run("mono /opt/chocolatey/choco.exe pack --allow-unofficial")
|
||||||
os.popen('mono /opt/chocolatey/choco.exe setapikey --key="{choco_token}" --source="https://push.chocolatey.org/" --allow-unofficial').read()
|
run(
|
||||||
|
'mono /opt/chocolatey/choco.exe setapikey --key="{choco_token}" --source="https://push.chocolatey.org/" --allow-unofficial'
|
||||||
|
)
|
||||||
|
|
||||||
print("\n_____ build choco push")
|
print("\n_____ build choco push")
|
||||||
os.popen('mono /opt/chocolatey/choco.exe push --source="https://push.chocolatey.org/" --key="$CHOCO_TOKEN" --allow-unofficial --verbose').read()
|
run(
|
||||||
|
'mono /opt/chocolatey/choco.exe push --source="https://push.chocolatey.org/" --key="$CHOCO_TOKEN" --allow-unofficial --verbose'
|
||||||
|
)
|
||||||
|
|
||||||
print(f"choco push of version: {target_release_tag} successful!")
|
print(f"choco push of version: {target_release_tag} successful!")
|
||||||
print(f"it took: {datetime.datetime.now() - start}")
|
print(f"it took: {datetime.datetime.now() - start}")
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ option(BUILD_IFCGEOM "Build IfcGeom." ON)
|
|||||||
option(BUILD_IFCPYTHON "Build IfcPython." ON)
|
option(BUILD_IFCPYTHON "Build IfcPython." ON)
|
||||||
option(BUILD_CONVERT "Build IfcConvert executable." ON)
|
option(BUILD_CONVERT "Build IfcConvert executable." ON)
|
||||||
option(BUILD_DOCUMENTATION "Build IfcOpenShell Documentation." OFF)
|
option(BUILD_DOCUMENTATION "Build IfcOpenShell Documentation." OFF)
|
||||||
option(BUILD_EXAMPLES "Build example applications." ON)
|
option(BUILD_EXAMPLES "Build example applications." OFF)
|
||||||
option(BUILD_GEOMSERVER "Build IfcGeomServer executable (Open CASCADE is required)." ON)
|
option(BUILD_GEOMSERVER "Build IfcGeomServer executable (Open CASCADE is required)." ON)
|
||||||
option(BUILD_IFCMAX "Build IfcMax, a 3ds Max plug-in, Windows-only." OFF)
|
option(BUILD_IFCMAX "Build IfcMax, a 3ds Max plug-in, Windows-only." OFF)
|
||||||
option(BUILD_QTVIEWER "Build IfcOpenShell Qt GUI Viewer" OFF) # QtViewer requires Qt6
|
option(BUILD_QTVIEWER "Build IfcOpenShell Qt GUI Viewer" OFF) # QtViewer requires Qt6
|
||||||
|
|||||||
@@ -41,6 +41,9 @@ def pack_dependencies(install_dir: Path) -> None:
|
|||||||
if not dependency_path.is_dir():
|
if not dependency_path.is_dir():
|
||||||
continue
|
continue
|
||||||
dependency_name = dependency_path.name
|
dependency_name = dependency_path.name
|
||||||
|
# Skip ifcopenshell - it's a build output, not a dependency to reuse across builds.
|
||||||
|
if dependency_name == "ifcopenshell":
|
||||||
|
continue
|
||||||
tar_path = install_dir / f"{CACHE_PREFIX}{dependency_name}.tar.gz"
|
tar_path = install_dir / f"{CACHE_PREFIX}{dependency_name}.tar.gz"
|
||||||
if tar_path.exists():
|
if tar_path.exists():
|
||||||
print(f"Skipping existing cache: '{tar_path}'")
|
print(f"Skipping existing cache: '{tar_path}'")
|
||||||
|
|||||||
+183
-3
@@ -2,10 +2,10 @@
|
|||||||
name = "IfcOpenShell"
|
name = "IfcOpenShell"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"black==26.1.0",
|
"black==26.3.1",
|
||||||
"ruff==0.15.4",
|
"ruff==0.15.8",
|
||||||
"poethepoet",
|
"poethepoet",
|
||||||
"gersemi==0.26.0",
|
"gersemi==0.26.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.black]
|
[tool.black]
|
||||||
@@ -28,6 +28,12 @@ extend-exclude = '''
|
|||||||
reportInvalidTypeForm = false
|
reportInvalidTypeForm = false
|
||||||
disableBytesTypePromotions = true
|
disableBytesTypePromotions = true
|
||||||
reportUnnecessaryTypeIgnoreComment = true
|
reportUnnecessaryTypeIgnoreComment = true
|
||||||
|
# Pylance doesn't respect gitignore, so we have to exclude files manually here
|
||||||
|
# to avoid VS Code slowing down.
|
||||||
|
# https://github.com/microsoft/pylance-release/issues/5169
|
||||||
|
exclude = [
|
||||||
|
"_deps",
|
||||||
|
]
|
||||||
|
|
||||||
# Define here general ruff settings,
|
# Define here general ruff settings,
|
||||||
# then they will be inherited by projects' .toml files.
|
# then they will be inherited by projects' .toml files.
|
||||||
@@ -72,6 +78,139 @@ ignore = [
|
|||||||
"UP032", # Replace .format with f-string
|
"UP032", # Replace .format with f-string
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[tool.ty.rules]
|
||||||
|
all = "ignore"
|
||||||
|
|
||||||
|
# Structural rules (no deep type inference needed, easier to adapt).
|
||||||
|
abstract-method-in-final-class = "error"
|
||||||
|
ambiguous-protocol-member = "error"
|
||||||
|
byte-string-type-annotation = "error"
|
||||||
|
conflicting-declarations = "error"
|
||||||
|
conflicting-metaclass = "error"
|
||||||
|
cyclic-class-definition = "error"
|
||||||
|
cyclic-type-alias-definition = "error"
|
||||||
|
dataclass-field-order = "error"
|
||||||
|
duplicate-base = "error"
|
||||||
|
duplicate-kw-only = "error"
|
||||||
|
empty-body = "error"
|
||||||
|
escape-character-in-forward-annotation = "error"
|
||||||
|
final-on-non-method = "error"
|
||||||
|
final-without-value = "error"
|
||||||
|
fstring-type-annotation = "error"
|
||||||
|
ignore-comment-unknown-rule = "error"
|
||||||
|
implicit-concatenated-string-type-annotation = "error"
|
||||||
|
inconsistent-mro = "error"
|
||||||
|
ineffective-final = "error"
|
||||||
|
instance-layout-conflict = "error"
|
||||||
|
invalid-dataclass = "error"
|
||||||
|
invalid-dataclass-override = "error"
|
||||||
|
invalid-enum-member-annotation = "error"
|
||||||
|
invalid-explicit-override = "error"
|
||||||
|
invalid-frozen-dataclass-subclass = "error"
|
||||||
|
invalid-generic-class = "error"
|
||||||
|
invalid-generic-enum = "error"
|
||||||
|
invalid-ignore-comment = "error"
|
||||||
|
invalid-legacy-positional-parameter = "error"
|
||||||
|
invalid-legacy-type-variable = "error"
|
||||||
|
invalid-named-tuple = "error"
|
||||||
|
invalid-newtype = "error"
|
||||||
|
invalid-overload = "error"
|
||||||
|
invalid-paramspec = "error"
|
||||||
|
invalid-protocol = "error"
|
||||||
|
invalid-syntax-in-forward-annotation = "error"
|
||||||
|
invalid-total-ordering = "error"
|
||||||
|
invalid-type-alias-type = "error"
|
||||||
|
invalid-type-checking-constant = "error"
|
||||||
|
invalid-type-guard-definition = "error"
|
||||||
|
invalid-type-variable-bound = "error"
|
||||||
|
invalid-type-variable-constraints = "error"
|
||||||
|
invalid-typed-dict-header = "error"
|
||||||
|
invalid-typed-dict-statement = "error"
|
||||||
|
override-of-final-method = "error"
|
||||||
|
override-of-final-variable = "error"
|
||||||
|
possibly-missing-import = "error"
|
||||||
|
possibly-missing-submodule = "error"
|
||||||
|
# Has false positives due to ty walrus operator bug.
|
||||||
|
# possibly-unresolved-reference = "error"
|
||||||
|
raw-string-type-annotation = "error"
|
||||||
|
redundant-final-classvar = "error"
|
||||||
|
shadowed-type-variable = "error"
|
||||||
|
subclass-of-final-class = "error"
|
||||||
|
super-call-in-named-tuple-method = "error"
|
||||||
|
unavailable-implicit-super-arguments = "error"
|
||||||
|
unbound-type-variable = "error"
|
||||||
|
undefined-reveal = "error"
|
||||||
|
unresolved-global = "error"
|
||||||
|
unresolved-import = "error"
|
||||||
|
unresolved-reference = "error"
|
||||||
|
unused-ignore-comment = "error"
|
||||||
|
unused-type-ignore-comment = "error"
|
||||||
|
useless-overload-body = "error"
|
||||||
|
|
||||||
|
# Non-structural rules:
|
||||||
|
deprecated = "error"
|
||||||
|
zero-stepsize-in-slice = "error"
|
||||||
|
possibly-missing-implicit-call = "error"
|
||||||
|
unused-awaitable = "error"
|
||||||
|
|
||||||
|
# Function argument rules:
|
||||||
|
# Conflicts with `ifcopenshell.api.geometry.add_representation` type of callables we have, confusing them with a module.
|
||||||
|
# call-non-callable = "error"
|
||||||
|
conflicting-argument-forms = "error"
|
||||||
|
# Too many false positives.
|
||||||
|
# invalid-argument-type = "error"
|
||||||
|
missing-argument = "error"
|
||||||
|
parameter-already-assigned = "error"
|
||||||
|
positional-only-parameter-as-kwarg = "error"
|
||||||
|
too-many-positional-arguments = "error"
|
||||||
|
unknown-argument = "error"
|
||||||
|
# Has a lot of warnings due to current ty walrus operator issues.
|
||||||
|
# index-out-of-bounds = "error"
|
||||||
|
# unresolved-attribute = "error"
|
||||||
|
|
||||||
|
[tool.ty.environment]
|
||||||
|
extra-paths = [
|
||||||
|
"src/bonsai/external_dependencies",
|
||||||
|
"src/bcf",
|
||||||
|
"src/bsdd",
|
||||||
|
"src/bonsai",
|
||||||
|
"src/ifc4d",
|
||||||
|
"src/ifc5d",
|
||||||
|
"src/ifccityjson",
|
||||||
|
"src/ifcclash",
|
||||||
|
"src/ifccsv",
|
||||||
|
"src/ifcdiff",
|
||||||
|
"src/ifcfm",
|
||||||
|
"src/ifcopenshell-python",
|
||||||
|
"src/ifcpatch",
|
||||||
|
"src/ifctester",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.ty.src]
|
||||||
|
exclude = [
|
||||||
|
# External dependencies cloned for type checking only.
|
||||||
|
"src/bonsai/external_dependencies",
|
||||||
|
# Submodules.
|
||||||
|
"src/ifcopenshell-python/ifcopenshell/express",
|
||||||
|
"src/ifcopenshell-python/ifcopenshell/mvd",
|
||||||
|
"src/ifcopenshell-python/ifcopenshell/simple_spf",
|
||||||
|
"src/svgfill/3rdparty",
|
||||||
|
# Has special dependencies.
|
||||||
|
"src/ifcopenshell-python/ifcopenshell/geom/app.py",
|
||||||
|
"src/ifcopenshell-python/ifcopenshell/geom/code_editor_pane.py",
|
||||||
|
"src/ifcopenshell-python/ifcopenshell/util/doc.py",
|
||||||
|
"src/ifcopenshell-python/ifcopenshell/util/generate_pset_templates.py",
|
||||||
|
"src/ifcopenshell-python/ifcopenshell/util/ifc4x3dev_scrape_data_for_docs.py",
|
||||||
|
# Too esoteric.
|
||||||
|
"src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.py",
|
||||||
|
"src/ifc2ca/templates",
|
||||||
|
# Too dev.
|
||||||
|
"src/bcf/setup.py",
|
||||||
|
"src/bsdd/yml_to_classes.py",
|
||||||
|
# Deprecated.
|
||||||
|
"src/ifc2ca/_deprecated",
|
||||||
|
]
|
||||||
|
|
||||||
[tool.poe.tasks]
|
[tool.poe.tasks]
|
||||||
|
|
||||||
ruff-main = "ruff check --extend-exclude nix/build-all.py"
|
ruff-main = "ruff check --extend-exclude nix/build-all.py"
|
||||||
@@ -81,6 +220,47 @@ ruff.sequence = ["ruff-main", "ruff-old"]
|
|||||||
|
|
||||||
black = "black ."
|
black = "black ."
|
||||||
|
|
||||||
|
ty.sequence = ["ty-bonsai", "ty-ios"]
|
||||||
|
ty.help = "Run ty type checker. Requires ty-venv to be set up first."
|
||||||
|
ty-bonsai = "ty check src/bonsai --python=src/bonsai/.venv"
|
||||||
|
|
||||||
|
ty-venv.sequence = ["ty-venv-bonsai", "ty-venv-ios"]
|
||||||
|
|
||||||
|
ty-venv-bonsai.sequence = [
|
||||||
|
{cmd = "uv venv src/bonsai/.venv --python=3.11 --allow-existing"},
|
||||||
|
{cmd = "uv pip install -r src/bonsai/type-check-requirements.txt --python=src/bonsai/.venv"},
|
||||||
|
]
|
||||||
|
|
||||||
|
ty-venv-ios.sequence = [
|
||||||
|
{cmd = "uv venv src/ifcopenshell-python/.venv --python=3.10 --allow-existing"},
|
||||||
|
{cmd = "uv pip install -r src/ifcopenshell-python/type-check-requirements.txt --python=src/ifcopenshell-python/.venv"},
|
||||||
|
]
|
||||||
|
|
||||||
format.sequence = ["black", "ruff-main", "ruff-old"]
|
format.sequence = ["black", "ruff-main", "ruff-old"]
|
||||||
|
|
||||||
cmake-format = "gersemi . --in-place"
|
cmake-format = "gersemi . --in-place"
|
||||||
|
|
||||||
|
[tool.poe.tasks.ty-ios]
|
||||||
|
# --ignore unresolved-reference: walrus operator false positives in ty.
|
||||||
|
cmd = """
|
||||||
|
ty check
|
||||||
|
src/bcf
|
||||||
|
src/bsdd
|
||||||
|
src/ifc2ca
|
||||||
|
src/ifc4d
|
||||||
|
src/ifc5d
|
||||||
|
src/ifccityjson
|
||||||
|
src/ifcclash
|
||||||
|
src/ifccsv
|
||||||
|
src/ifcdiff
|
||||||
|
src/ifcfm
|
||||||
|
src/ifcopenshell-python
|
||||||
|
src/ifcpatch
|
||||||
|
src/ifctester
|
||||||
|
--python=src/ifcopenshell-python/.venv
|
||||||
|
--ignore unresolved-reference
|
||||||
|
"""
|
||||||
|
|
||||||
|
[tool.poe.tasks.bonsai-deps]
|
||||||
|
help = "Clone or update Bonsai external dependencies."
|
||||||
|
cmd = "python src/bonsai/scripts/bonsai_deps.py"
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ client_id, client_secret = "", ""
|
|||||||
class OAuthReceiver(http.server.BaseHTTPRequestHandler):
|
class OAuthReceiver(http.server.BaseHTTPRequestHandler):
|
||||||
def do_GET(self) -> None:
|
def do_GET(self) -> None:
|
||||||
query = urllib.parse.parse_qs(urllib.parse.urlparse(self.path).query)
|
query = urllib.parse.parse_qs(urllib.parse.urlparse(self.path).query)
|
||||||
self.server.auth_code = query.get("code", [""])[0] # type: ignore
|
self.server.auth_code = query.get("code", [""])[0]
|
||||||
self.server.auth_state = query.get("state", [""])[0] # type: ignore
|
self.server.auth_state = query.get("state", [""])[0]
|
||||||
self.send_response(200)
|
self.send_response(200)
|
||||||
self.send_header("Content-type", "text/plain")
|
self.send_header("Content-type", "text/plain")
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
@@ -255,7 +255,7 @@ class BcfClient:
|
|||||||
project_id: str = "",
|
project_id: str = "",
|
||||||
topics: str = "",
|
topics: str = "",
|
||||||
query_string: Optional[str] = None,
|
query_string: Optional[str] = None,
|
||||||
) -> list[Any]:
|
) -> None:
|
||||||
# return self.get(
|
# return self.get(
|
||||||
# f"/projects/{project_id}/topics",
|
# f"/projects/{project_id}/topics",
|
||||||
# {
|
# {
|
||||||
|
|||||||
@@ -173,16 +173,17 @@ def assert_viewpoints(viewpoints):
|
|||||||
assert viewpoint.snapshot is not None
|
assert viewpoint.snapshot is not None
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: dead code - ported from v2 but buildingSMART/BCF-XML has no v3 MaximumInformation.bcf equivalent
|
||||||
def assert_second_viewpoint(viewpoint, expected_selection, expected_exception, expected_coloring) -> None:
|
def assert_second_viewpoint(viewpoint, expected_selection, expected_exception, expected_coloring) -> None:
|
||||||
expected_vp = mdl.VisualizationInfo(
|
expected_vp = mdl.VisualizationInfo(
|
||||||
components=mdl.Components(
|
components=mdl.Components(
|
||||||
view_setup_hints=mdl.ViewSetupHints(
|
|
||||||
spaces_visible=False,
|
|
||||||
space_boundaries_visible=False,
|
|
||||||
openings_visible=False,
|
|
||||||
),
|
|
||||||
selection=expected_selection,
|
selection=expected_selection,
|
||||||
visibility=mdl.ComponentVisibility(
|
visibility=mdl.ComponentVisibility(
|
||||||
|
view_setup_hints=mdl.ViewSetupHints(
|
||||||
|
spaces_visible=False,
|
||||||
|
space_boundaries_visible=False,
|
||||||
|
openings_visible=False,
|
||||||
|
),
|
||||||
exceptions=expected_exception,
|
exceptions=expected_exception,
|
||||||
default_visibility=False,
|
default_visibility=False,
|
||||||
),
|
),
|
||||||
@@ -193,6 +194,7 @@ def assert_second_viewpoint(viewpoint, expected_selection, expected_exception, e
|
|||||||
camera_direction=mdl.Direction(x=0.6745243072509766, y=-0.6599355936050415, z=-0.33091068267822266),
|
camera_direction=mdl.Direction(x=0.6745243072509766, y=-0.6599355936050415, z=-0.33091068267822266),
|
||||||
camera_up_vector=mdl.Direction(x=0.2271970510482788, y=-0.24091780185699463, z=0.9435783624649048),
|
camera_up_vector=mdl.Direction(x=0.2271970510482788, y=-0.24091780185699463, z=0.9435783624649048),
|
||||||
field_of_view=60,
|
field_of_view=60,
|
||||||
|
aspect_ratio=1.0,
|
||||||
),
|
),
|
||||||
guid="21dd4807-e9af-439e-a980-04d913a6b1ce",
|
guid="21dd4807-e9af-439e-a980-04d913a6b1ce",
|
||||||
)
|
)
|
||||||
@@ -200,16 +202,17 @@ def assert_second_viewpoint(viewpoint, expected_selection, expected_exception, e
|
|||||||
assert viewpoint.snapshot is not None
|
assert viewpoint.snapshot is not None
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: dead code - ported from v2 but buildingSMART/BCF-XML has no v3 MaximumInformation.bcf equivalent
|
||||||
def assert_third_viewpoint(viewpoint, expected_selection, expected_exception, expected_coloring) -> None:
|
def assert_third_viewpoint(viewpoint, expected_selection, expected_exception, expected_coloring) -> None:
|
||||||
expected_vp = mdl.VisualizationInfo(
|
expected_vp = mdl.VisualizationInfo(
|
||||||
components=mdl.Components(
|
components=mdl.Components(
|
||||||
view_setup_hints=mdl.ViewSetupHints(
|
|
||||||
spaces_visible=False,
|
|
||||||
space_boundaries_visible=False,
|
|
||||||
openings_visible=True,
|
|
||||||
),
|
|
||||||
selection=expected_selection,
|
selection=expected_selection,
|
||||||
visibility=mdl.ComponentVisibility(
|
visibility=mdl.ComponentVisibility(
|
||||||
|
view_setup_hints=mdl.ViewSetupHints(
|
||||||
|
spaces_visible=False,
|
||||||
|
space_boundaries_visible=False,
|
||||||
|
openings_visible=True,
|
||||||
|
),
|
||||||
exceptions=expected_exception,
|
exceptions=expected_exception,
|
||||||
default_visibility=True,
|
default_visibility=True,
|
||||||
),
|
),
|
||||||
@@ -220,6 +223,7 @@ def assert_third_viewpoint(viewpoint, expected_selection, expected_exception, ex
|
|||||||
camera_direction=mdl.Direction(x=0.7232745289802551, y=0.5967116951942444, z=-0.3475759029388428),
|
camera_direction=mdl.Direction(x=0.7232745289802551, y=0.5967116951942444, z=-0.3475759029388428),
|
||||||
camera_up_vector=mdl.Direction(x=0.27662187814712524, y=0.21082592010498047, z=0.937567412853241),
|
camera_up_vector=mdl.Direction(x=0.27662187814712524, y=0.21082592010498047, z=0.937567412853241),
|
||||||
field_of_view=60,
|
field_of_view=60,
|
||||||
|
aspect_ratio=1.0,
|
||||||
),
|
),
|
||||||
guid="81daa431-bf01-4a49-80a2-1ab07c177717",
|
guid="81daa431-bf01-4a49-80a2-1ab07c177717",
|
||||||
)
|
)
|
||||||
|
|||||||
+8
-26
@@ -48,6 +48,7 @@ VERSION_PATCH:=$(shell cat '../../VERSION' | cut -d '.' -f 3)
|
|||||||
VERSION_DATE:=$(shell date '+%y%m%d')
|
VERSION_DATE:=$(shell date '+%y%m%d')
|
||||||
LAST_COMMIT_HASH:=$(shell git rev-parse HEAD)
|
LAST_COMMIT_HASH:=$(shell git rev-parse HEAD)
|
||||||
LAST_COMMIT_DATE:=$(shell git show -s --format=%cI)
|
LAST_COMMIT_DATE:=$(shell git show -s --format=%cI)
|
||||||
|
LAST_GIT_BRANCH:=$(shell git rev-parse --abbrev-ref HEAD)
|
||||||
PYPI_IMP:=cp
|
PYPI_IMP:=cp
|
||||||
|
|
||||||
ifdef PYVERSION
|
ifdef PYVERSION
|
||||||
@@ -63,6 +64,7 @@ PYNUMBER:=3$(PYMINOR)
|
|||||||
PYPI_VERSION:=3.$(PYMINOR)
|
PYPI_VERSION:=3.$(PYMINOR)
|
||||||
endif # def PYVERSION
|
endif # def PYVERSION
|
||||||
|
|
||||||
|
IFCMERGE_VERSION:=2026-04-02
|
||||||
|
|
||||||
ifdef PLATFORM
|
ifdef PLATFORM
|
||||||
SUPPORTED_PLATFORMS := linux macos macosm1 win
|
SUPPORTED_PLATFORMS := linux macos macosm1 win
|
||||||
@@ -223,19 +225,8 @@ endif
|
|||||||
cd build/bonsai/bim/data/gantt/ && wget https://raw.githubusercontent.com/jsGanttImproved/jsgantt-improved/master/dist/jsgantt.css
|
cd build/bonsai/bim/data/gantt/ && wget https://raw.githubusercontent.com/jsGanttImproved/jsgantt-improved/master/dist/jsgantt.css
|
||||||
|
|
||||||
# Provides IFCJSON functionality
|
# Provides IFCJSON functionality
|
||||||
cd build && wget -O ifc2json.zip https://github.com/IFCJSON-Team/IFC2JSON_python/archive/refs/heads/master.zip
|
# TODO: Use official repo, once https://github.com/IFCJSON-Team/IFC2JSON_python/pull/8 is merged.
|
||||||
cd build && unzip ifc2json.zip && rm ifc2json.zip
|
cd build && . env/$(VENV_ACTIVATE) && $(PYTHON) -m pip wheel "git+https://github.com/Andrej730/IFC2JSON_python.git@pyproject_toml" --no-deps -w wheels/
|
||||||
# IFCJSON doesn't have pyproject.toml, so we use python command.
|
|
||||||
cd build && . env/$(VENV_ACTIVATE) && cd IFC2JSON_python-*/file_converters && \
|
|
||||||
$(PYTHON) -c "from setuptools import setup; \
|
|
||||||
setup( \
|
|
||||||
name='ifcjson', \
|
|
||||||
version='0.0.1', \
|
|
||||||
author='Jan Brouwer', \
|
|
||||||
author_email='jan@brewsky.nl', \
|
|
||||||
packages=['ifcjson'], \
|
|
||||||
)" bdist_wheel
|
|
||||||
cp -r build/IFC2JSON_python-*/file_converters/dist/*.whl build/wheels/
|
|
||||||
|
|
||||||
# Brickschema requires pkg_resources which is provided by Blender.
|
# Brickschema requires pkg_resources which is provided by Blender.
|
||||||
# Provides Brickschema functionality
|
# Provides Brickschema functionality
|
||||||
@@ -243,26 +234,16 @@ endif
|
|||||||
cd build/bonsai/bim/data/brick/ && wget https://github.com/BrickSchema/Brick/releases/download/nightly/Brick.ttl
|
cd build/bonsai/bim/data/brick/ && wget https://github.com/BrickSchema/Brick/releases/download/nightly/Brick.ttl
|
||||||
|
|
||||||
# Required for hipped roof generation
|
# Required for hipped roof generation
|
||||||
cd build && wget https://github.com/prochitecture/bpypolyskel/archive/refs/heads/master.zip
|
cd build && . env/$(VENV_ACTIVATE) && $(PYTHON) -m pip wheel "git+https://github.com/prochitecture/bpypolyskel" --no-deps -w wheels/
|
||||||
cd build && unzip master.zip && rm master.zip
|
|
||||||
cd build && . env/$(VENV_ACTIVATE) && cd bpypolyskel-master && \
|
|
||||||
$(PYTHON) -c "from setuptools import setup; \
|
|
||||||
setup( \
|
|
||||||
name='bpypolyskel', \
|
|
||||||
version='0.0.0', \
|
|
||||||
packages=['bpypolyskel'], \
|
|
||||||
)" bdist_wheel
|
|
||||||
cp -r build/bpypolyskel-master/dist/*.whl build/wheels/
|
|
||||||
|
|
||||||
# folder for executable files
|
# folder for executable files
|
||||||
mkdir -p build/bonsai/libs/bin
|
mkdir -p build/bonsai/libs/bin
|
||||||
|
|
||||||
# required for three-way git merging
|
# required for three-way git merging
|
||||||
ifeq ($(PLATFORM), win)
|
ifeq ($(PLATFORM), win)
|
||||||
cd build/bonsai/libs/bin && wget https://github.com/brunopostle/ifcmerge/releases/download/2025-01-26/ifcmerge.zip
|
cd build/bonsai/libs/bin && wget https://github.com/brunopostle/ifcmerge/releases/download/$(IFCMERGE_VERSION)/ifcmerge.exe
|
||||||
cd build/bonsai/libs/bin && unzip ifcmerge.zip && rm ifcmerge.zip
|
|
||||||
else
|
else
|
||||||
cd build/bonsai/libs/bin && wget https://raw.githubusercontent.com/brunopostle/ifcmerge/main/ifcmerge && chmod +x ifcmerge
|
cd build/bonsai/libs/bin && wget https://raw.githubusercontent.com/brunopostle/ifcmerge/$(IFCMERGE_VERSION)/ifcmerge && chmod +x ifcmerge
|
||||||
endif
|
endif
|
||||||
|
|
||||||
# Generate translations module for Bonsai build
|
# Generate translations module for Bonsai build
|
||||||
@@ -281,6 +262,7 @@ else
|
|||||||
$(SED) "s/0.0.0/$(VERSION)-alpha$(VERSION_DATE)/" build/bonsai/blender_manifest.toml
|
$(SED) "s/0.0.0/$(VERSION)-alpha$(VERSION_DATE)/" build/bonsai/blender_manifest.toml
|
||||||
$(SED) "s/8888888/$(LAST_COMMIT_HASH)/" build/bonsai/__init__.py
|
$(SED) "s/8888888/$(LAST_COMMIT_HASH)/" build/bonsai/__init__.py
|
||||||
$(SED) "s/9999999/$(LAST_COMMIT_DATE)/" build/bonsai/__init__.py
|
$(SED) "s/9999999/$(LAST_COMMIT_DATE)/" build/bonsai/__init__.py
|
||||||
|
$(SED) "s/7777777/$(LAST_GIT_BRANCH)/" build/bonsai/__init__.py
|
||||||
$(SED) 's/version = "0.0.0"/version = "$(VERSION)-alpha$(VERSION_DATE)"/' build/pyproject.toml
|
$(SED) 's/version = "0.0.0"/version = "$(VERSION)-alpha$(VERSION_DATE)"/' build/pyproject.toml
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
|||||||
@@ -35,15 +35,15 @@ IN_PACKAGE = __package__ == "bonsai"
|
|||||||
import platform
|
import platform
|
||||||
import re
|
import re
|
||||||
import traceback
|
import traceback
|
||||||
import uuid
|
|
||||||
import webbrowser
|
import webbrowser
|
||||||
from collections import deque
|
from collections import deque
|
||||||
from collections.abc import Generator
|
from collections.abc import Generator
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Union
|
from typing import TYPE_CHECKING, Any, Union
|
||||||
|
|
||||||
last_commit_hash = "8888888"
|
last_commit_hash = "8888888"
|
||||||
last_commit_date = "9999999"
|
last_commit_date = "9999999"
|
||||||
|
last_git_branch = "7777777"
|
||||||
|
|
||||||
|
|
||||||
def get_last_commit_hash() -> Union[str, None]:
|
def get_last_commit_hash() -> Union[str, None]:
|
||||||
@@ -61,6 +61,15 @@ def get_last_commit_date() -> Union[str, None]:
|
|||||||
return last_commit_date
|
return last_commit_date
|
||||||
|
|
||||||
|
|
||||||
|
def get_git_branch() -> Union[str, None]:
|
||||||
|
# Using this weird way to write 7777777,
|
||||||
|
# so makefile won't accidentally replace it here
|
||||||
|
# we'll be able to distinguish branch from placeholder value.
|
||||||
|
if last_git_branch == str(7_777777):
|
||||||
|
return None
|
||||||
|
return last_git_branch
|
||||||
|
|
||||||
|
|
||||||
# Accessed from bonsai extension:
|
# Accessed from bonsai extension:
|
||||||
bbim_semver: dict[str, Any] = {}
|
bbim_semver: dict[str, Any] = {}
|
||||||
|
|
||||||
@@ -72,6 +81,21 @@ REINSTALLED_BBIM_VERSION: Union[str, None] = None
|
|||||||
REGISTERED_BBIM_PACKAGE: str
|
REGISTERED_BBIM_PACKAGE: str
|
||||||
|
|
||||||
|
|
||||||
|
def is_registering() -> bool:
|
||||||
|
"""
|
||||||
|
During addon registration ``bpy.context`` and ``bpy.data`` are restricted
|
||||||
|
and you can't access their properties.
|
||||||
|
"""
|
||||||
|
import bpy
|
||||||
|
|
||||||
|
if TYPE_CHECKING or bpy.app.version >= (5, 0, 0):
|
||||||
|
import _bpy_restrict_state as bpy_restrict_state
|
||||||
|
else:
|
||||||
|
import bpy_restrict_state
|
||||||
|
|
||||||
|
return isinstance(bpy.context, bpy_restrict_state._RestrictContext)
|
||||||
|
|
||||||
|
|
||||||
def initialize_bbim_semver():
|
def initialize_bbim_semver():
|
||||||
"""Initialize `bbim_semver` dictionary.
|
"""Initialize `bbim_semver` dictionary.
|
||||||
|
|
||||||
@@ -93,9 +117,13 @@ def initialize_bbim_semver():
|
|||||||
bbim_semver["version"] = version_str
|
bbim_semver["version"] = version_str
|
||||||
|
|
||||||
|
|
||||||
def get_debug_info():
|
def get_debug_info(*, bonsai_failed_to_load: bool = False) -> dict[str, Any]:
|
||||||
|
import bpy
|
||||||
|
|
||||||
bbim_version = bbim_semver["version"]
|
bbim_version = bbim_semver["version"]
|
||||||
|
|
||||||
|
# All data here should be gettable even in case of `bpy.context` and `bpy.data` being inaccessible
|
||||||
|
# and Bonsai completely failed to load.
|
||||||
debug_info = {
|
debug_info = {
|
||||||
"os": platform.system(),
|
"os": platform.system(),
|
||||||
"os_version": platform.version(),
|
"os_version": platform.version(),
|
||||||
@@ -107,10 +135,19 @@ def get_debug_info():
|
|||||||
"bonsai_version": bbim_version,
|
"bonsai_version": bbim_version,
|
||||||
"bonsai_commit_hash": get_last_commit_hash(),
|
"bonsai_commit_hash": get_last_commit_hash(),
|
||||||
"bonsai_commit_date": get_last_commit_date(),
|
"bonsai_commit_date": get_last_commit_date(),
|
||||||
|
"bonsai_git_branch": get_git_branch(),
|
||||||
"last_actions": last_actions,
|
"last_actions": last_actions,
|
||||||
"last_error": last_error,
|
"last_error": last_error,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Can't access blend data or context during registration.
|
||||||
|
# If Bonsai failed to load we cannot safely access any of its properties or its tools
|
||||||
|
# as they may not be registered yet and acessing them will break Bonsai Fatal Error UI.
|
||||||
|
if is_registering() or bonsai_failed_to_load:
|
||||||
|
return debug_info
|
||||||
|
|
||||||
|
import bonsai.tool as tool
|
||||||
|
|
||||||
# Add .blend file save information
|
# Add .blend file save information
|
||||||
if bpy.data.is_saved:
|
if bpy.data.is_saved:
|
||||||
debug_info["blend_file_path"] = bpy.data.filepath
|
debug_info["blend_file_path"] = bpy.data.filepath
|
||||||
@@ -131,7 +168,7 @@ def get_debug_info():
|
|||||||
return debug_info
|
return debug_info
|
||||||
|
|
||||||
|
|
||||||
def format_debug_info(info: dict):
|
def format_debug_info(info: dict[str, Any]) -> str:
|
||||||
last_actions = ""
|
last_actions = ""
|
||||||
for action in info["last_actions"]:
|
for action in info["last_actions"]:
|
||||||
last_actions += f"\n# {action['type']}: {action['name']}"
|
last_actions += f"\n# {action['type']}: {action['name']}"
|
||||||
@@ -149,33 +186,10 @@ def get_binaries(path: Path) -> Generator[Path, None, None]:
|
|||||||
yield from path.glob("**/*.so")
|
yield from path.glob("**/*.so")
|
||||||
|
|
||||||
|
|
||||||
def safe_link_dlls() -> None:
|
# TODO: remove before 0.8.6 release.
|
||||||
# Blender 4.2+ has a problem on Windows for disabling/enabling/reinstalling extensions
|
# On Windows issues with removing extensions were resolved in Blender 4.3,
|
||||||
# with loaded binary dependencies (on Windows you can't remove a binary if it's loaded by some program).
|
# but we removed our workaround that was producing some junk only in 0.8.5 release.
|
||||||
# To avoid this issue we temporary hard link dlls to our temp directory on unregister()
|
# So we're temporarily keeping the part that's cleaning up outputs from previous releases.
|
||||||
# (unregister is executed before Blender will try to uninstall dependencies and the issue will arise).
|
|
||||||
# Then, Blender won't have a problem unlinking unloaded dlls as they are still linked somewhere.
|
|
||||||
# On register() we clean up our temp directory with binaries.
|
|
||||||
#
|
|
||||||
# TODO: If user uninstalls Bonsai to never use it again, temporary directory won't be cleared.
|
|
||||||
#
|
|
||||||
# See: https://projects.blender.org/blender/blender/issues/125049
|
|
||||||
import bpy
|
|
||||||
|
|
||||||
ext_path = Path(bpy.utils.user_resource("EXTENSIONS"))
|
|
||||||
local_path = ext_path / ".local"
|
|
||||||
|
|
||||||
# We use random hash subfolder as user may try to enable/disable addon multiple times.
|
|
||||||
random_hash = uuid.uuid4().hex[:8]
|
|
||||||
temp_local = ext_path / ".local_temp" / random_hash
|
|
||||||
temp_local.mkdir(parents=True)
|
|
||||||
|
|
||||||
for filepath in get_binaries(local_path):
|
|
||||||
dest_path = temp_local / filepath.relative_to(local_path)
|
|
||||||
dest_path.parent.mkdir(exist_ok=True, parents=True)
|
|
||||||
os.link(filepath, dest_path)
|
|
||||||
|
|
||||||
|
|
||||||
def clean_up_dlls_safe_links() -> None:
|
def clean_up_dlls_safe_links() -> None:
|
||||||
import bpy
|
import bpy
|
||||||
|
|
||||||
@@ -205,6 +219,8 @@ def clean_up_dlls_safe_links() -> None:
|
|||||||
|
|
||||||
|
|
||||||
if IN_BLENDER:
|
if IN_BLENDER:
|
||||||
|
import bpy
|
||||||
|
|
||||||
initialize_bbim_semver()
|
initialize_bbim_semver()
|
||||||
|
|
||||||
def get_binary_info() -> dict[str, Any]:
|
def get_binary_info() -> dict[str, Any]:
|
||||||
@@ -246,10 +262,12 @@ if IN_BLENDER:
|
|||||||
|
|
||||||
global last_commit_hash
|
global last_commit_hash
|
||||||
global last_commit_date
|
global last_commit_date
|
||||||
|
global last_git_branch
|
||||||
path = Path(__file__).resolve().parent
|
path = Path(__file__).resolve().parent
|
||||||
repo = git.Repo(str(path), search_parent_directories=True)
|
repo = git.Repo(str(path), search_parent_directories=True)
|
||||||
last_commit_hash = repo.head.object.hexsha
|
last_commit_hash = repo.head.object.hexsha
|
||||||
last_commit_date = repo.head.object.committed_datetime.isoformat()
|
last_commit_date = repo.head.object.committed_datetime.isoformat()
|
||||||
|
last_git_branch = repo.active_branch.name
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -296,9 +314,6 @@ if IN_BLENDER:
|
|||||||
purge_cache()
|
purge_cache()
|
||||||
|
|
||||||
def unregister():
|
def unregister():
|
||||||
if platform.system() == "Windows":
|
|
||||||
safe_link_dlls()
|
|
||||||
|
|
||||||
import bonsai.bim
|
import bonsai.bim
|
||||||
|
|
||||||
bonsai.bim.unregister()
|
bonsai.bim.unregister()
|
||||||
@@ -333,7 +348,7 @@ if IN_BLENDER:
|
|||||||
bl_context = "scene"
|
bl_context = "scene"
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
info = get_debug_info()
|
info = get_debug_info(bonsai_failed_to_load=True)
|
||||||
|
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
layout.alert = True
|
layout.alert = True
|
||||||
@@ -409,7 +424,7 @@ if IN_BLENDER:
|
|||||||
bl_description = "Copies debugging information to your clipboard for use in bugreports"
|
bl_description = "Copies debugging information to your clipboard for use in bugreports"
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
info = get_debug_info()
|
info = get_debug_info(bonsai_failed_to_load=True)
|
||||||
info.update(get_binary_info())
|
info.update(get_binary_info())
|
||||||
info = format_debug_info(info)
|
info = format_debug_info(info)
|
||||||
context.window_manager.clipboard = info
|
context.window_manager.clipboard = info
|
||||||
|
|||||||
@@ -72,9 +72,7 @@ class IfcExporter:
|
|||||||
|
|
||||||
def set_header(self):
|
def set_header(self):
|
||||||
self.file.header.file_name.name = os.path.basename(self.ifc_export_settings.output_file)
|
self.file.header.file_name.name = os.path.basename(self.ifc_export_settings.output_file)
|
||||||
self.file.header.file_name.time_stamp = (
|
self.file.header.file_name.time_stamp = datetime.datetime.now().astimezone().replace(microsecond=0).isoformat()
|
||||||
datetime.datetime.utcnow().replace(tzinfo=datetime.UTC).astimezone().replace(microsecond=0).isoformat()
|
|
||||||
)
|
|
||||||
self.file.header.file_name.preprocessor_version = "IfcOpenShell {}".format(ifcopenshell.version)
|
self.file.header.file_name.preprocessor_version = "IfcOpenShell {}".format(ifcopenshell.version)
|
||||||
self.file.header.file_name.originating_system = "{} {}".format(
|
self.file.header.file_name.originating_system = "{} {}".format(
|
||||||
self.get_application_name(), tool.Blender.get_bonsai_version()
|
self.get_application_name(), tool.Blender.get_bonsai_version()
|
||||||
|
|||||||
@@ -45,16 +45,19 @@ from bonsai.bim.module.nest.decorator import NestDecorator
|
|||||||
|
|
||||||
cwd = os.path.dirname(os.path.realpath(__file__))
|
cwd = os.path.dirname(os.path.realpath(__file__))
|
||||||
global_subscription_owner = object()
|
global_subscription_owner = object()
|
||||||
|
# Separate owner for per-object msgbus subscriptions (name, active_material_index).
|
||||||
|
# Using a dedicated owner allows clearing all per-object subscriptions at once
|
||||||
|
# during undo/redo without affecting other global subscriptions.
|
||||||
|
object_subscription_owner = object()
|
||||||
|
|
||||||
|
|
||||||
def name_callback(obj: Union[bpy.types.Object, bpy.types.Material], data: str) -> None:
|
def name_callback(obj: Union[bpy.types.Object, bpy.types.Material], data: str) -> None:
|
||||||
try:
|
try:
|
||||||
obj.name
|
obj.name
|
||||||
except:
|
except:
|
||||||
# The object is invalid but somehow still has a callback. Clear all
|
# The object is invalid but somehow still has a callback.
|
||||||
# msgbus subscriptions to prevent useless further triggers.
|
# This can occur during undo/redo when the Python wrapper is stale.
|
||||||
bpy.msgbus.clear_by_owner(obj)
|
return
|
||||||
return # In case the object RNA is gone during an undo / redo operation
|
|
||||||
# Blender names are up to 63 UTF-8 bytes
|
# Blender names are up to 63 UTF-8 bytes
|
||||||
if len(bytes(obj.name, "utf-8")) >= 63:
|
if len(bytes(obj.name, "utf-8")) >= 63:
|
||||||
return
|
return
|
||||||
@@ -189,7 +192,7 @@ def subscribe_to(obj: bpy.types.ID, data_path: str, callback: Callable[[bpy.type
|
|||||||
return
|
return
|
||||||
bpy.msgbus.subscribe_rna(
|
bpy.msgbus.subscribe_rna(
|
||||||
key=subscribe_to,
|
key=subscribe_to,
|
||||||
owner=obj,
|
owner=object_subscription_owner,
|
||||||
args=(
|
args=(
|
||||||
obj,
|
obj,
|
||||||
data_path,
|
data_path,
|
||||||
|
|||||||
@@ -316,11 +316,8 @@ class IfcStore:
|
|||||||
del IfcStore.id_map[data["id"]]
|
del IfcStore.id_map[data["id"]]
|
||||||
if "guid" in data:
|
if "guid" in data:
|
||||||
del IfcStore.guid_map[data["guid"]]
|
del IfcStore.guid_map[data["guid"]]
|
||||||
obj = IfcStore.get_object_by_name(data["obj"])
|
# Note: msgbus subscriptions are cleared globally during
|
||||||
if obj is None:
|
# rebuild_element_maps which runs after every undo/redo.
|
||||||
# obj was just created during this step and didn't existed before.
|
|
||||||
return
|
|
||||||
bpy.msgbus.clear_by_owner(obj)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def commit_link_element(data: OperationData) -> None:
|
def commit_link_element(data: OperationData) -> None:
|
||||||
@@ -367,10 +364,8 @@ class IfcStore:
|
|||||||
del IfcStore.id_map[data["id"]]
|
del IfcStore.id_map[data["id"]]
|
||||||
if "guid" in data:
|
if "guid" in data:
|
||||||
del IfcStore.guid_map[data["guid"]]
|
del IfcStore.guid_map[data["guid"]]
|
||||||
obj = IfcStore.get_object_by_name(data["obj"])
|
# Note: msgbus subscriptions are cleared globally during
|
||||||
# obj might be removed after unlink.
|
# rebuild_element_maps which runs after every undo/redo.
|
||||||
if not obj:
|
|
||||||
bpy.msgbus.clear_by_owner(obj)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def unlink_element(
|
def unlink_element(
|
||||||
|
|||||||
@@ -64,8 +64,8 @@ class MaterialCreator:
|
|||||||
mesh: Union[OBJECT_DATA_TYPE, None],
|
mesh: Union[OBJECT_DATA_TYPE, None],
|
||||||
shape_has_openings: bool,
|
shape_has_openings: bool,
|
||||||
) -> None:
|
) -> None:
|
||||||
if ((rep := getattr(element, "Representation", ...) is not ...) and not rep) or (
|
if ((rep := getattr(element, "Representation", ...)) is not ... and not rep) or (
|
||||||
(rep := getattr(element, "RepresentationMaps", ...) is not ...) and not rep
|
(rep := getattr(element, "RepresentationMaps", ...)) is not ... and not rep
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -747,6 +747,7 @@ class IfcImporter:
|
|||||||
self.update_progress((percent_average / 100 * progress_range) + start_progress)
|
self.update_progress((percent_average / 100 * progress_range) + start_progress)
|
||||||
shape = iterator.get()
|
shape = iterator.get()
|
||||||
if shape:
|
if shape:
|
||||||
|
assert isinstance(shape, W.TriangulationElement)
|
||||||
product = self.file.by_id(shape.id)
|
product = self.file.by_id(shape.id)
|
||||||
self.create_product(product, shape)
|
self.create_product(product, shape)
|
||||||
results.add(product)
|
results.add(product)
|
||||||
@@ -1020,7 +1021,8 @@ class IfcImporter:
|
|||||||
obj.hide_select = True
|
obj.hide_select = True
|
||||||
obj.hide_viewport = True
|
obj.hide_viewport = True
|
||||||
self.project["blender"].objects.link(obj)
|
self.project["blender"].objects.link(obj)
|
||||||
self.project["blender"].BIMCollectionProperties.obj = obj
|
collection_props = tool.Blender.get_collection_props(self.project["blender"])
|
||||||
|
collection_props.obj = obj
|
||||||
props = tool.Blender.get_object_bim_props(obj)
|
props = tool.Blender.get_object_bim_props(obj)
|
||||||
props.collection = self.collections[project.GlobalId] = self.project["blender"]
|
props.collection = self.collections[project.GlobalId] = self.project["blender"]
|
||||||
|
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ class AggregateDecorator:
|
|||||||
cls.is_installed = False
|
cls.is_installed = False
|
||||||
|
|
||||||
def dotted_line_shader(self):
|
def dotted_line_shader(self):
|
||||||
vert_out = gpu.types.GPUStageInterfaceInfo("my_interface")
|
vert_out = gpu.types.GPUStageInterfaceInfo("my_interface") # ty:ignore[too-many-positional-arguments]
|
||||||
vert_out.smooth("FLOAT", "v_ArcLength")
|
vert_out.smooth("FLOAT", "v_ArcLength")
|
||||||
|
|
||||||
shader_info = gpu.types.GPUShaderCreateInfo()
|
shader_info = gpu.types.GPUShaderCreateInfo()
|
||||||
|
|||||||
@@ -16,8 +16,9 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import bpy.props
|
from typing import TYPE_CHECKING
|
||||||
import bpy.types
|
|
||||||
|
import bpy
|
||||||
|
|
||||||
|
|
||||||
class AuginProperties(bpy.types.PropertyGroup):
|
class AuginProperties(bpy.types.PropertyGroup):
|
||||||
@@ -27,3 +28,11 @@ class AuginProperties(bpy.types.PropertyGroup):
|
|||||||
project_name: bpy.props.StringProperty(name="Project Name")
|
project_name: bpy.props.StringProperty(name="Project Name")
|
||||||
project_filename: bpy.props.StringProperty(name="IFC Filename")
|
project_filename: bpy.props.StringProperty(name="IFC Filename")
|
||||||
is_success: bpy.props.BoolProperty(name="Is Successful Upload", default=False)
|
is_success: bpy.props.BoolProperty(name="Is Successful Upload", default=False)
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
username: str
|
||||||
|
password: str
|
||||||
|
token: str
|
||||||
|
project_name: str
|
||||||
|
project_filename: str
|
||||||
|
is_success: bool
|
||||||
|
|||||||
@@ -1253,8 +1253,8 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
|||||||
else:
|
else:
|
||||||
obj.data.show_background_images = False
|
obj.data.show_background_images = False
|
||||||
|
|
||||||
area = next(area for area in context.screen.areas if area.type == "VIEW_3D")
|
assert (space := tool.Blender.get_view3d_space())
|
||||||
area.spaces[0].region_3d.view_perspective = "CAMERA"
|
space.region_3d.view_perspective = "CAMERA"
|
||||||
|
|
||||||
if self.file:
|
if self.file:
|
||||||
self.set_viewpoint_components(viewpoint, context)
|
self.set_viewpoint_components(viewpoint, context)
|
||||||
|
|||||||
@@ -230,7 +230,7 @@ class BcfTopic(PropertyGroup):
|
|||||||
|
|
||||||
|
|
||||||
def get_related_topics(self: "BCFProperties", context: bpy.types.Context) -> list[tuple[str, str, str]]:
|
def get_related_topics(self: "BCFProperties", context: bpy.types.Context) -> list[tuple[str, str, str]]:
|
||||||
global RELATED_TOPICS_ENUM_ITEMS
|
global RELATED_TOPICS_ENUM_ITEMS # ty: ignore[unresolved-global]
|
||||||
props = self
|
props = self
|
||||||
active_topic = props.active_topic
|
active_topic = props.active_topic
|
||||||
active_related_topics = active_topic.related_topics.keys()
|
active_related_topics = active_topic.related_topics.keys()
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import ifcopenshell.api
|
|||||||
import ifcopenshell.api.boundary
|
import ifcopenshell.api.boundary
|
||||||
import ifcopenshell.api.root
|
import ifcopenshell.api.root
|
||||||
import ifcopenshell.geom
|
import ifcopenshell.geom
|
||||||
|
import ifcopenshell.ifcopenshell_wrapper as W
|
||||||
import ifcopenshell.util.element
|
import ifcopenshell.util.element
|
||||||
import ifcopenshell.util.placement
|
import ifcopenshell.util.placement
|
||||||
import ifcopenshell.util.shape
|
import ifcopenshell.util.shape
|
||||||
@@ -376,6 +377,8 @@ class EnableEditingBoundary(bpy.types.Operator):
|
|||||||
obj = tool.Ifc.get_object(entity)
|
obj = tool.Ifc.get_object(entity)
|
||||||
if entity and obj:
|
if entity and obj:
|
||||||
setattr(bprops, blender_property, obj)
|
setattr(bprops, blender_property, obj)
|
||||||
|
bprops.physical_or_virtual = boundary.PhysicalOrVirtualBoundary or "NOTDEFINED"
|
||||||
|
bprops.internal_or_external = boundary.InternalOrExternalBoundary or "NOTDEFINED"
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
@@ -391,6 +394,8 @@ class DisableEditingBoundary(bpy.types.Operator):
|
|||||||
bprops.is_editing = False
|
bprops.is_editing = False
|
||||||
for ifc_attribute, blender_property in EDITABLE_ATTRIBUTES.items():
|
for ifc_attribute, blender_property in EDITABLE_ATTRIBUTES.items():
|
||||||
setattr(bprops, blender_property, None)
|
setattr(bprops, blender_property, None)
|
||||||
|
bprops.physical_or_virtual = "NOTDEFINED"
|
||||||
|
bprops.internal_or_external = "NOTDEFINED"
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
@@ -410,6 +415,8 @@ class EditBoundaryAttributes(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
obj = getattr(bprops, blender_property, None)
|
obj = getattr(bprops, blender_property, None)
|
||||||
entity = tool.Ifc.get_entity(obj)
|
entity = tool.Ifc.get_entity(obj)
|
||||||
attributes[blender_property] = entity
|
attributes[blender_property] = entity
|
||||||
|
attributes["physical_or_virtual"] = bprops.physical_or_virtual
|
||||||
|
attributes["internal_or_external"] = bprops.internal_or_external
|
||||||
ifcopenshell.api.boundary.edit_attributes(tool.Ifc.get(), entity=boundary, **attributes)
|
ifcopenshell.api.boundary.edit_attributes(tool.Ifc.get(), entity=boundary, **attributes)
|
||||||
bpy.ops.bim.disable_editing_boundary()
|
bpy.ops.bim.disable_editing_boundary()
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
@@ -701,6 +708,7 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
while True:
|
while True:
|
||||||
tree.add_element(iterator.get_native())
|
tree.add_element(iterator.get_native())
|
||||||
shape = iterator.get()
|
shape = iterator.get()
|
||||||
|
assert isinstance(shape, W.TriangulationElement)
|
||||||
shapes[shape.id] = {
|
shapes[shape.id] = {
|
||||||
"verts": ifcopenshell.util.shape.get_vertices(shape.geometry),
|
"verts": ifcopenshell.util.shape.get_vertices(shape.geometry),
|
||||||
"faces": ifcopenshell.util.shape.get_faces(shape.geometry),
|
"faces": ifcopenshell.util.shape.get_faces(shape.geometry),
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ from typing import TYPE_CHECKING, Union
|
|||||||
import bpy
|
import bpy
|
||||||
from bpy.props import (
|
from bpy.props import (
|
||||||
BoolProperty,
|
BoolProperty,
|
||||||
|
EnumProperty,
|
||||||
PointerProperty,
|
PointerProperty,
|
||||||
)
|
)
|
||||||
from bpy.types import PropertyGroup
|
from bpy.types import PropertyGroup
|
||||||
@@ -50,12 +51,43 @@ def element_filter(self: "BIMObjectBoundaryProperties", object: bpy.types.Object
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def get_internal_or_external_items(
|
||||||
|
self: "BIMObjectBoundaryProperties", context: bpy.types.Context | None
|
||||||
|
) -> list[tuple[str, str, str]]:
|
||||||
|
items = [
|
||||||
|
("INTERNAL", "Internal", ""),
|
||||||
|
("EXTERNAL", "External", ""),
|
||||||
|
]
|
||||||
|
ifc = tool.Ifc.get()
|
||||||
|
if not ifc or ifc.schema != "IFC2X3":
|
||||||
|
items += [
|
||||||
|
("EXTERNAL_EARTH", "External Earth", ""),
|
||||||
|
("EXTERNAL_WATER", "External Water", ""),
|
||||||
|
("EXTERNAL_FIRE", "External Fire", ""),
|
||||||
|
]
|
||||||
|
items.append(("NOTDEFINED", "Not Defined", ""))
|
||||||
|
return items
|
||||||
|
|
||||||
|
|
||||||
class BIMObjectBoundaryProperties(PropertyGroup):
|
class BIMObjectBoundaryProperties(PropertyGroup):
|
||||||
is_editing: BoolProperty(name="Is Editing")
|
is_editing: BoolProperty(name="Is Editing")
|
||||||
relating_space: PointerProperty(name="RelatingSpace", type=bpy.types.Object, poll=space_filter)
|
relating_space: PointerProperty(name="RelatingSpace", type=bpy.types.Object, poll=space_filter)
|
||||||
related_building_element: PointerProperty(name="RelatedBuildingElement", type=bpy.types.Object, poll=element_filter)
|
related_building_element: PointerProperty(name="RelatedBuildingElement", type=bpy.types.Object, poll=element_filter)
|
||||||
parent_boundary: PointerProperty(name="ParentBoundary", type=bpy.types.Object, poll=boundary_filter)
|
parent_boundary: PointerProperty(name="ParentBoundary", type=bpy.types.Object, poll=boundary_filter)
|
||||||
corresponding_boundary: PointerProperty(name="CorrespondingBoundary", type=bpy.types.Object, poll=boundary_filter)
|
corresponding_boundary: PointerProperty(name="CorrespondingBoundary", type=bpy.types.Object, poll=boundary_filter)
|
||||||
|
physical_or_virtual: EnumProperty(
|
||||||
|
name="PhysicalOrVirtualBoundary",
|
||||||
|
items=[
|
||||||
|
("PHYSICAL", "Physical", ""),
|
||||||
|
("VIRTUAL", "Virtual", ""),
|
||||||
|
("NOTDEFINED", "Not Defined", ""),
|
||||||
|
],
|
||||||
|
default="NOTDEFINED",
|
||||||
|
)
|
||||||
|
internal_or_external: EnumProperty(
|
||||||
|
name="InternalOrExternalBoundary",
|
||||||
|
items=get_internal_or_external_items,
|
||||||
|
)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
is_editing: bool
|
is_editing: bool
|
||||||
@@ -63,6 +95,8 @@ class BIMObjectBoundaryProperties(PropertyGroup):
|
|||||||
related_building_element: Union[bpy.types.Object, None]
|
related_building_element: Union[bpy.types.Object, None]
|
||||||
parent_boundary: Union[bpy.types.Object, None]
|
parent_boundary: Union[bpy.types.Object, None]
|
||||||
corresponding_boundary: Union[bpy.types.Object, None]
|
corresponding_boundary: Union[bpy.types.Object, None]
|
||||||
|
physical_or_virtual: str
|
||||||
|
internal_or_external: str # values depend on schema: IFC2X3 omits EXTERNAL_EARTH/WATER/FIRE
|
||||||
|
|
||||||
|
|
||||||
class BIMBoundaryProperties(PropertyGroup):
|
class BIMBoundaryProperties(PropertyGroup):
|
||||||
|
|||||||
@@ -77,6 +77,10 @@ class BIM_PT_Boundary(Panel):
|
|||||||
self.draw_relation_editor(boundary, "RelatedBuildingElement", "related_building_element")
|
self.draw_relation_editor(boundary, "RelatedBuildingElement", "related_building_element")
|
||||||
self.draw_relation_editor(boundary, "ParentBoundary", "parent_boundary")
|
self.draw_relation_editor(boundary, "ParentBoundary", "parent_boundary")
|
||||||
self.draw_relation_editor(boundary, "CorrespondingBoundary", "corresponding_boundary")
|
self.draw_relation_editor(boundary, "CorrespondingBoundary", "corresponding_boundary")
|
||||||
|
row = self.layout.row()
|
||||||
|
row.prop(self.bprops, "physical_or_virtual")
|
||||||
|
row = self.layout.row()
|
||||||
|
row.prop(self.bprops, "internal_or_external")
|
||||||
else:
|
else:
|
||||||
row = self.layout.row()
|
row = self.layout.row()
|
||||||
row.operator("bim.enable_editing_boundary", icon="GREASEPENCIL", text="Edit")
|
row.operator("bim.enable_editing_boundary", icon="GREASEPENCIL", text="Edit")
|
||||||
@@ -84,6 +88,8 @@ class BIM_PT_Boundary(Panel):
|
|||||||
self.draw_relation_data(boundary, "RelatedBuildingElement")
|
self.draw_relation_data(boundary, "RelatedBuildingElement")
|
||||||
self.draw_relation_data(boundary, "ParentBoundary")
|
self.draw_relation_data(boundary, "ParentBoundary")
|
||||||
self.draw_relation_data(boundary, "CorrespondingBoundary")
|
self.draw_relation_data(boundary, "CorrespondingBoundary")
|
||||||
|
self.draw_enum_data(boundary, "PhysicalOrVirtualBoundary")
|
||||||
|
self.draw_enum_data(boundary, "InternalOrExternalBoundary")
|
||||||
if hasattr(boundary, "InnerBoundaries"):
|
if hasattr(boundary, "InnerBoundaries"):
|
||||||
for i, inner_boundary in enumerate(getattr(boundary, "InnerBoundaries", ())):
|
for i, inner_boundary in enumerate(getattr(boundary, "InnerBoundaries", ())):
|
||||||
row = self.layout.row(align=True)
|
row = self.layout.row(align=True)
|
||||||
@@ -110,6 +116,11 @@ class BIM_PT_Boundary(Panel):
|
|||||||
else:
|
else:
|
||||||
row.label(text="")
|
row.label(text="")
|
||||||
|
|
||||||
|
def draw_enum_data(self, boundary, ifc_attribute: str):
|
||||||
|
row = self.layout.row(align=True)
|
||||||
|
row.label(text=ifc_attribute)
|
||||||
|
row.label(text=getattr(boundary, ifc_attribute, "") or "")
|
||||||
|
|
||||||
def draw_relation_editor(self, boundary, ifc_attribute: str, blender_property: str):
|
def draw_relation_editor(self, boundary, ifc_attribute: str, blender_property: str):
|
||||||
if hasattr(boundary, ifc_attribute):
|
if hasattr(boundary, ifc_attribute):
|
||||||
row = self.layout.row(align=True)
|
row = self.layout.row(align=True)
|
||||||
|
|||||||
@@ -46,26 +46,26 @@ def get_libraries(self, context):
|
|||||||
|
|
||||||
|
|
||||||
def get_namespaces(self, context):
|
def get_namespaces(self, context):
|
||||||
global NAMESPACES_ENUM_ITEMS
|
global NAMESPACES_ENUM_ITEMS # ty: ignore[unresolved-global]
|
||||||
NAMESPACES_ENUM_ITEMS = [(uri, f"{alias}: {uri}", "") for alias, uri in BrickStore.namespaces]
|
NAMESPACES_ENUM_ITEMS = [(uri, f"{alias}: {uri}", "") for alias, uri in BrickStore.namespaces]
|
||||||
return NAMESPACES_ENUM_ITEMS
|
return NAMESPACES_ENUM_ITEMS
|
||||||
|
|
||||||
|
|
||||||
def get_brick_entity_classes(self, context):
|
def get_brick_entity_classes(self, context):
|
||||||
global ENTITY_CLASSES_ENUM_ITEMS
|
global ENTITY_CLASSES_ENUM_ITEMS # ty: ignore[unresolved-global]
|
||||||
entity = self.brick_entity_create_type
|
entity = self.brick_entity_create_type
|
||||||
ENTITY_CLASSES_ENUM_ITEMS = [(uri, uri.split("#")[-1], "") for uri in BrickStore.entity_classes[entity]]
|
ENTITY_CLASSES_ENUM_ITEMS = [(uri, uri.split("#")[-1], "") for uri in BrickStore.entity_classes[entity]]
|
||||||
return ENTITY_CLASSES_ENUM_ITEMS
|
return ENTITY_CLASSES_ENUM_ITEMS
|
||||||
|
|
||||||
|
|
||||||
def get_brick_roots(self, context):
|
def get_brick_roots(self, context):
|
||||||
global BRICK_ROOTS_ENUM_ITEMS
|
global BRICK_ROOTS_ENUM_ITEMS # ty: ignore[unresolved-global]
|
||||||
BRICK_ROOTS_ENUM_ITEMS = [(root, root, "") for root in BrickStore.root_classes]
|
BRICK_ROOTS_ENUM_ITEMS = [(root, root, "") for root in BrickStore.root_classes]
|
||||||
return BRICK_ROOTS_ENUM_ITEMS
|
return BRICK_ROOTS_ENUM_ITEMS
|
||||||
|
|
||||||
|
|
||||||
def get_brick_relations(self, context):
|
def get_brick_relations(self, context):
|
||||||
global BRICK_RELATIONS_ENUM_ITEMS
|
global BRICK_RELATIONS_ENUM_ITEMS # ty: ignore[unresolved-global]
|
||||||
BRICK_RELATIONS_ENUM_ITEMS = [(uri, uri.split("#")[-1], "") for uri in BrickStore.relationships]
|
BRICK_RELATIONS_ENUM_ITEMS = [(uri, uri.split("#")[-1], "") for uri in BrickStore.relationships]
|
||||||
for relation in BrickschemaData.data["active_relations"]:
|
for relation in BrickschemaData.data["active_relations"]:
|
||||||
if relation["predicate_name"] == "label":
|
if relation["predicate_name"] == "label":
|
||||||
|
|||||||
@@ -16,9 +16,18 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
import bpy
|
||||||
from bpy.types import Panel, UIList
|
from bpy.types import Panel, UIList
|
||||||
|
|
||||||
import bonsai.tool as tool
|
import bonsai.tool as tool
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from bonsai.bim.module.brick.prop import Brick
|
||||||
|
|
||||||
from bonsai.bim.helper import prop_with_search
|
from bonsai.bim.helper import prop_with_search
|
||||||
from bonsai.bim.module.brick.data import BrickschemaData, BrickschemaReferencesData
|
from bonsai.bim.module.brick.data import BrickschemaData, BrickschemaReferencesData
|
||||||
from bonsai.tool.brick import BrickStore
|
from bonsai.tool.brick import BrickStore
|
||||||
@@ -274,7 +283,9 @@ class BIM_PT_brickschema_viewport(Panel):
|
|||||||
class BIM_UL_bricks(UIList):
|
class BIM_UL_bricks(UIList):
|
||||||
split_screen = False
|
split_screen = False
|
||||||
|
|
||||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
def draw_item(
|
||||||
|
self, context, layout: bpy.types.UILayout, data, item: Brick, icon, active_data, active_propname
|
||||||
|
) -> None:
|
||||||
if item:
|
if item:
|
||||||
split = layout.split(factor=0.85, align=True)
|
split = layout.split(factor=0.85, align=True)
|
||||||
row = split.row()
|
row = split.row()
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ messages = {
|
|||||||
class CadTrimExtend(bpy.types.Operator):
|
class CadTrimExtend(bpy.types.Operator):
|
||||||
bl_idname = "bim.cad_trim_extend"
|
bl_idname = "bim.cad_trim_extend"
|
||||||
bl_label = "CAD Trim / Extend"
|
bl_label = "CAD Trim / Extend"
|
||||||
|
bl_description = "Extends/reduces element to 3D cursor"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
@@ -82,6 +83,7 @@ class CadTrimExtend(bpy.types.Operator):
|
|||||||
class CadMitre(bpy.types.Operator):
|
class CadMitre(bpy.types.Operator):
|
||||||
bl_idname = "bim.cad_mitre"
|
bl_idname = "bim.cad_mitre"
|
||||||
bl_label = "CAD Mitre"
|
bl_label = "CAD Mitre"
|
||||||
|
bl_description = "Joins two non-parallel paths at their intersection"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
|
|||||||
@@ -106,23 +106,37 @@ class CadTool(WorkSpaceTool):
|
|||||||
)
|
)
|
||||||
|
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
add_layout_hotkey_operator(row, "Extend", "S_E", "Extends/reduces element to 3D cursor", ui_context)
|
|
||||||
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
|
|
||||||
add_layout_hotkey_operator(
|
add_layout_hotkey_operator(
|
||||||
row, "Join", "S_T", "Joins two non-parallel paths at their intersection", ui_context
|
row, "Extend", "S_E", bpy.ops.bim.cad_trim_extend.__doc__.split("\n", 1)[1].strip(), ui_context
|
||||||
)
|
)
|
||||||
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
|
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
|
||||||
add_layout_hotkey_operator(row, "Fillet", "S_F", bpy.ops.bim.add_ifcarcindex_fillet.__doc__, ui_context)
|
add_layout_hotkey_operator(
|
||||||
|
row, "Join", "S_T", bpy.ops.bim.cad_mitre.__doc__.split("\n", 1)[1].strip(), ui_context
|
||||||
|
)
|
||||||
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
|
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
|
||||||
add_layout_hotkey_operator(row, "Offset", "S_O", bpy.ops.bim.cad_offset.__doc__, ui_context)
|
add_layout_hotkey_operator(
|
||||||
|
row, "Fillet", "S_F", bpy.ops.bim.add_ifcarcindex_fillet.__doc__.split("\n", 1)[1].strip(), ui_context
|
||||||
|
)
|
||||||
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
|
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
|
||||||
add_layout_hotkey_operator(row, "Rectangle", "S_R", bpy.ops.bim.add_rectangle.__doc__, ui_context)
|
add_layout_hotkey_operator(
|
||||||
|
row, "Offset", "S_O", bpy.ops.bim.cad_offset.__doc__.split("\n", 1)[1].strip(), ui_context
|
||||||
|
)
|
||||||
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
|
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
|
||||||
add_layout_hotkey_operator(row, "Circle", "S_C", bpy.ops.bim.add_ifccircle.__doc__, ui_context)
|
add_layout_hotkey_operator(
|
||||||
|
row, "Rectangle", "S_R", bpy.ops.bim.add_rectangle.__doc__.split("\n", 1)[1].strip(), ui_context
|
||||||
|
)
|
||||||
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
|
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
|
||||||
add_layout_hotkey_operator(row, "3-Point Arc", "S_V", bpy.ops.bim.set_arc_index.__doc__, ui_context)
|
add_layout_hotkey_operator(
|
||||||
|
row, "Circle", "S_C", bpy.ops.bim.add_ifccircle.__doc__.split("\n", 1)[1].strip(), ui_context
|
||||||
|
)
|
||||||
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
|
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
|
||||||
add_layout_hotkey_operator(row, "Reset Vertex", "S_X", bpy.ops.bim.reset_vertex.__doc__, ui_context)
|
add_layout_hotkey_operator(
|
||||||
|
row, "3-Point Arc", "S_V", bpy.ops.bim.set_arc_index.__doc__.split("\n", 1)[1].strip(), ui_context
|
||||||
|
)
|
||||||
|
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
|
||||||
|
add_layout_hotkey_operator(
|
||||||
|
row, "Reset Vertex", "S_X", bpy.ops.bim.reset_vertex.__doc__.split("\n", 1)[1].strip(), ui_context
|
||||||
|
)
|
||||||
|
|
||||||
elif (
|
elif (
|
||||||
isinstance(data, tool.Geometry.TYPES_WITH_MESH_PROPERTIES)
|
isinstance(data, tool.Geometry.TYPES_WITH_MESH_PROPERTIES)
|
||||||
@@ -132,15 +146,21 @@ class CadTool(WorkSpaceTool):
|
|||||||
layout, "Edit Axis", "bim.edit_extrusion_axis", "bim.disable_editing_extrusion_axis", ui_context
|
layout, "Edit Axis", "bim.edit_extrusion_axis", "bim.disable_editing_extrusion_axis", ui_context
|
||||||
)
|
)
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
add_layout_hotkey_operator(row, "Extend", "S_E", "Extends/reduces element to 3D cursor", ui_context)
|
|
||||||
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
|
|
||||||
add_layout_hotkey_operator(
|
add_layout_hotkey_operator(
|
||||||
row, "Join", "S_T", "Joins two non-parallel paths at their intersection", ui_context
|
row, "Extend", "S_E", bpy.ops.bim.cad_trim_extend.__doc__.split("\n", 1)[1].strip(), ui_context
|
||||||
)
|
)
|
||||||
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
|
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
|
||||||
add_layout_hotkey_operator(row, "Fillet", "S_F", bpy.ops.bim.cad_fillet.__doc__, ui_context)
|
add_layout_hotkey_operator(
|
||||||
|
row, "Join", "S_T", bpy.ops.bim.cad_mitre.__doc__.split("\n", 1)[1].strip(), ui_context
|
||||||
|
)
|
||||||
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
|
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
|
||||||
add_layout_hotkey_operator(row, "Offset", "S_O", bpy.ops.bim.cad_offset.__doc__, ui_context)
|
add_layout_hotkey_operator(
|
||||||
|
row, "Fillet", "S_F", bpy.ops.bim.cad_fillet.__doc__.split("\n", 1)[1].strip(), ui_context
|
||||||
|
)
|
||||||
|
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
|
||||||
|
add_layout_hotkey_operator(
|
||||||
|
row, "Offset", "S_O", bpy.ops.bim.cad_offset.__doc__.split("\n", 1)[1].strip(), ui_context
|
||||||
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if (
|
if (
|
||||||
@@ -168,19 +188,37 @@ class CadTool(WorkSpaceTool):
|
|||||||
add_layout_hotkey_operator(row, "Set Gable Roof Angle", "S_R", "Set Gable Roof Angle", ui_context)
|
add_layout_hotkey_operator(row, "Set Gable Roof Angle", "S_R", "Set Gable Roof Angle", ui_context)
|
||||||
|
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
add_layout_hotkey_operator(row, "Extend", "S_E", "Extends/reduces element to 3D cursor", ui_context)
|
|
||||||
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
|
|
||||||
add_layout_hotkey_operator(
|
add_layout_hotkey_operator(
|
||||||
row, "Join", "S_T", "Joins two non-parallel paths at their intersection", ui_context
|
row, "Extend", "S_E", bpy.ops.bim.cad_trim_extend.__doc__.split("\n", 1)[1].strip(), ui_context
|
||||||
)
|
)
|
||||||
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
|
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
|
||||||
add_layout_hotkey_operator(row, "Fillet", "S_F", bpy.ops.bim.add_ifcarcindex_fillet.__doc__, ui_context)
|
add_layout_hotkey_operator(
|
||||||
|
row, "Join", "S_T", bpy.ops.bim.cad_mitre.__doc__.split("\n", 1)[1].strip(), ui_context
|
||||||
|
)
|
||||||
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
|
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
|
||||||
add_layout_hotkey_operator(row, "Offset", "S_O", bpy.ops.bim.cad_offset.__doc__, ui_context)
|
add_layout_hotkey_operator(
|
||||||
|
row, "Fillet", "S_F", bpy.ops.bim.add_ifcarcindex_fillet.__doc__.split("\n", 1)[1].strip(), ui_context
|
||||||
|
)
|
||||||
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
|
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
|
||||||
add_layout_hotkey_operator(row, "2-Point Arc", "S_C", bpy.ops.bim.cad_arc_from_2_points.__doc__, ui_context)
|
add_layout_hotkey_operator(
|
||||||
|
row, "Offset", "S_O", bpy.ops.bim.cad_offset.__doc__.split("\n", 1)[1].strip(), ui_context
|
||||||
|
)
|
||||||
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
|
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
|
||||||
add_layout_hotkey_operator(row, "3-Point Arc", "S_V", bpy.ops.bim.cad_arc_from_3_points.__doc__, ui_context)
|
add_layout_hotkey_operator(
|
||||||
|
row,
|
||||||
|
"2-Point Arc",
|
||||||
|
"S_C",
|
||||||
|
bpy.ops.bim.cad_arc_from_2_points.__doc__.split("\n", 1)[1].strip(),
|
||||||
|
ui_context,
|
||||||
|
)
|
||||||
|
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
|
||||||
|
add_layout_hotkey_operator(
|
||||||
|
row,
|
||||||
|
"3-Point Arc",
|
||||||
|
"S_V",
|
||||||
|
bpy.ops.bim.cad_arc_from_3_points.__doc__.split("\n", 1)[1].strip(),
|
||||||
|
ui_context,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class CadHotkey(bpy.types.Operator):
|
class CadHotkey(bpy.types.Operator):
|
||||||
|
|||||||
@@ -457,9 +457,7 @@ class HideClash(bpy.types.Operator):
|
|||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
ClashDecorator.uninstall()
|
ClashDecorator.uninstall()
|
||||||
for area in context.screen.areas:
|
tool.Blender.update_all_viewports(context)
|
||||||
if area.type == "VIEW_3D":
|
|
||||||
area.tag_redraw()
|
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -26,10 +26,11 @@ from bpy.types import Panel, UIList
|
|||||||
import bonsai.bim.helper
|
import bonsai.bim.helper
|
||||||
import bonsai.bim.module.cost.prop as CostProp
|
import bonsai.bim.module.cost.prop as CostProp
|
||||||
import bonsai.tool as tool
|
import bonsai.tool as tool
|
||||||
from bonsai.bim.module.cost.data import CostSchedulesData
|
from bonsai.bim.module.cost.data import CostItem, CostSchedulesData
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from bonsai.bim.module.cost.prop import BIMCostProperties, CostItemQuantity
|
from bonsai.bim.module.cost.prop import BIMCostProperties, CostItemQuantity
|
||||||
|
from bonsai.bim.prop import StrProperty
|
||||||
|
|
||||||
|
|
||||||
class BIM_PT_cost_schedules(Panel):
|
class BIM_PT_cost_schedules(Panel):
|
||||||
@@ -398,8 +399,8 @@ class BIM_PT_cost_item_types(Panel):
|
|||||||
op = row2.operator("bim.calculate_cost_item_resource_value", text="", icon="DISC")
|
op = row2.operator("bim.calculate_cost_item_resource_value", text="", icon="DISC")
|
||||||
op.cost_item = cost_item.ifc_definition_id
|
op.cost_item = cost_item.ifc_definition_id
|
||||||
|
|
||||||
rtprops = context.scene.BIMResourceTreeProperties
|
|
||||||
rprops = tool.Resource.get_resource_props()
|
rprops = tool.Resource.get_resource_props()
|
||||||
|
rtprops = rprops.tree
|
||||||
if rtprops.resources and rprops.active_resource_index < len(rtprops.resources):
|
if rtprops.resources and rprops.active_resource_index < len(rtprops.resources):
|
||||||
if has_quantity_names:
|
if has_quantity_names:
|
||||||
op = row2.operator("bim.assign_cost_item_quantity", text="", icon="PROPERTIES")
|
op = row2.operator("bim.assign_cost_item_quantity", text="", icon="PROPERTIES")
|
||||||
@@ -661,9 +662,18 @@ class BIM_UL_cost_items_trait:
|
|||||||
split2.alignment = "LEFT"
|
split2.alignment = "LEFT"
|
||||||
split2.label(text="Rate")
|
split2.label(text="Rate")
|
||||||
|
|
||||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
def draw_item(
|
||||||
|
self,
|
||||||
|
context,
|
||||||
|
layout: bpy.types.UILayout,
|
||||||
|
data: BIMCostProperties,
|
||||||
|
item: CostProp.CostItem,
|
||||||
|
icon,
|
||||||
|
active_data,
|
||||||
|
active_propname,
|
||||||
|
) -> None:
|
||||||
if item:
|
if item:
|
||||||
self.props = tool.Cost.get_cost_props()
|
self.props = data
|
||||||
cost_item = CostSchedulesData.data["cost_items"][item.ifc_definition_id]
|
cost_item = CostSchedulesData.data["cost_items"][item.ifc_definition_id]
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
|
|
||||||
@@ -694,7 +704,7 @@ class BIM_UL_cost_items_trait:
|
|||||||
|
|
||||||
# TODO: reimplement "bim.copy_cost_item_values" somewhere with better UX
|
# TODO: reimplement "bim.copy_cost_item_values" somewhere with better UX
|
||||||
|
|
||||||
def draw_parent_operator(self, row, cost_item_id):
|
def draw_parent_operator(self, row: bpy.types.UILayout, cost_item_id: int) -> None:
|
||||||
if self.props.active_cost_item_id:
|
if self.props.active_cost_item_id:
|
||||||
if self.props.active_cost_item_id != cost_item_id:
|
if self.props.active_cost_item_id != cost_item_id:
|
||||||
op = row.operator("bim.change_parent_cost_item", text="", icon="LINKED", emboss=False).new_parent = (
|
op = row.operator("bim.change_parent_cost_item", text="", icon="LINKED", emboss=False).new_parent = (
|
||||||
@@ -703,7 +713,7 @@ class BIM_UL_cost_items_trait:
|
|||||||
else:
|
else:
|
||||||
row.label(text="", icon="BLANK1")
|
row.label(text="", icon="BLANK1")
|
||||||
|
|
||||||
def draw_hierarchy(self, row, item):
|
def draw_hierarchy(self, row: bpy.types.UILayout, item: CostProp.CostItem) -> None:
|
||||||
for i in range(0, item.level_index):
|
for i in range(0, item.level_index):
|
||||||
row.label(text="", icon="BLANK1")
|
row.label(text="", icon="BLANK1")
|
||||||
if item.has_children:
|
if item.has_children:
|
||||||
@@ -749,7 +759,7 @@ class BIM_UL_cost_items_trait:
|
|||||||
else:
|
else:
|
||||||
row.label(text="-")
|
row.label(text="-")
|
||||||
|
|
||||||
def draw_value_column(self, layout, cost_item):
|
def draw_value_column(self, layout: bpy.types.UILayout, cost_item: CostItem) -> None:
|
||||||
if cost_item["TotalAppliedValue"]:
|
if cost_item["TotalAppliedValue"]:
|
||||||
text = "{0:,.2f}".format(cost_item["TotalAppliedValue"]).replace(",", " ")
|
text = "{0:,.2f}".format(cost_item["TotalAppliedValue"]).replace(",", " ")
|
||||||
if cost_item["UnitBasisValueComponent"] not in [None, 1]:
|
if cost_item["UnitBasisValueComponent"] not in [None, 1]:
|
||||||
@@ -760,13 +770,13 @@ class BIM_UL_cost_items_trait:
|
|||||||
else:
|
else:
|
||||||
layout.label(text="-")
|
layout.label(text="-")
|
||||||
|
|
||||||
def draw_total_cost_column(self, layout, cost_item):
|
def draw_total_cost_column(self, layout: bpy.types.UILayout, cost_item: CostItem) -> None:
|
||||||
format_numbers = "{0:,.2f}".format(cost_item["TotalCost"]).replace(",", " ")
|
format_numbers = "{0:,.2f}".format(cost_item["TotalCost"]).replace(",", " ")
|
||||||
currency = CostSchedulesData.data["currency"]
|
currency = CostSchedulesData.data["currency"]
|
||||||
text = "{} {}".format(format_numbers, currency["name"]) if currency else format_numbers
|
text = "{} {}".format(format_numbers, currency["name"]) if currency else format_numbers
|
||||||
layout.label(text=text)
|
layout.label(text=text)
|
||||||
|
|
||||||
def draw_order_operator(self, row, ifc_definition_id, cost_item):
|
def draw_order_operator(self, row: bpy.types.UILayout, ifc_definition_id: int, cost_item: CostItem) -> None:
|
||||||
if cost_item["NestingIndex"] is not None:
|
if cost_item["NestingIndex"] is not None:
|
||||||
if cost_item["NestingIndex"] == 0:
|
if cost_item["NestingIndex"] == 0:
|
||||||
op = row.operator("bim.reorder_cost_item_nesting", icon="TRIA_DOWN", text="")
|
op = row.operator("bim.reorder_cost_item_nesting", icon="TRIA_DOWN", text="")
|
||||||
@@ -793,12 +803,14 @@ class BIM_UL_cost_item_rates(BIM_UL_cost_items_trait, UIList):
|
|||||||
def draw_quantity_column(self, layout, cost_item):
|
def draw_quantity_column(self, layout, cost_item):
|
||||||
self.draw_uom_column(layout, cost_item)
|
self.draw_uom_column(layout, cost_item)
|
||||||
|
|
||||||
def draw_total_cost_column(self, layout, cost_item):
|
def draw_total_cost_column(self, layout: bpy.types.UILayout, cost_item: CostItem) -> None:
|
||||||
pass # No such thing as a total cost in a schedule of rates
|
pass # No such thing as a total cost in a schedule of rates
|
||||||
|
|
||||||
|
|
||||||
class BIM_UL_cost_columns(UIList):
|
class BIM_UL_cost_columns(UIList):
|
||||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
def draw_item(
|
||||||
|
self, context, layout: bpy.types.UILayout, data, item: StrProperty, icon, active_data, active_propname
|
||||||
|
) -> None:
|
||||||
if item:
|
if item:
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
row.prop(item, "name", emboss=False, text="")
|
row.prop(item, "name", emboss=False, text="")
|
||||||
@@ -806,9 +818,17 @@ class BIM_UL_cost_columns(UIList):
|
|||||||
|
|
||||||
|
|
||||||
class BIM_UL_cost_item_types(UIList):
|
class BIM_UL_cost_item_types(UIList):
|
||||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
def draw_item(
|
||||||
props = tool.Cost.get_cost_props()
|
self,
|
||||||
cost_item = props.cost_items[props.active_cost_item_index]
|
context,
|
||||||
|
layout: bpy.types.UILayout,
|
||||||
|
data: BIMCostProperties,
|
||||||
|
item: CostProp.CostItemType,
|
||||||
|
icon,
|
||||||
|
active_data,
|
||||||
|
active_propname,
|
||||||
|
) -> None:
|
||||||
|
cost_item = data.cost_items[data.active_cost_item_index]
|
||||||
|
|
||||||
if item:
|
if item:
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
@@ -844,7 +864,16 @@ class BIM_UL_cost_item_quantities(UIList):
|
|||||||
|
|
||||||
|
|
||||||
class BIM_UL_product_cost_items(UIList):
|
class BIM_UL_product_cost_items(UIList):
|
||||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
def draw_item(
|
||||||
|
self,
|
||||||
|
context,
|
||||||
|
layout: bpy.types.UILayout,
|
||||||
|
data,
|
||||||
|
item: CostItemQuantity,
|
||||||
|
icon,
|
||||||
|
active_data,
|
||||||
|
active_propname,
|
||||||
|
) -> None:
|
||||||
if item:
|
if item:
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
op = row.operator("bim.highlight_product_cost_item", text="", icon="STYLUS_PRESSURE")
|
op = row.operator("bim.highlight_product_cost_item", text="", icon="STYLUS_PRESSURE")
|
||||||
|
|||||||
@@ -98,8 +98,8 @@ class VisualiseDiff(bpy.types.Operator):
|
|||||||
obj.color = (0.0, 1.0, 0.0, 1.0)
|
obj.color = (0.0, 1.0, 0.0, 1.0)
|
||||||
elif global_id in diff["changed"]:
|
elif global_id in diff["changed"]:
|
||||||
obj.color = (0.0, 0.0, 1.0, 1.0)
|
obj.color = (0.0, 0.0, 1.0, 1.0)
|
||||||
area = next(area for area in context.screen.areas if area.type == "VIEW_3D")
|
assert (space := tool.Blender.get_view3d_space())
|
||||||
area.spaces[0].shading.color_type = "OBJECT"
|
space.shading.color_type = "OBJECT"
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
|
import ifcopenshell.util.element
|
||||||
|
|
||||||
import bonsai.core.document as core
|
import bonsai.core.document as core
|
||||||
import bonsai.tool as tool
|
import bonsai.tool as tool
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from typing import TYPE_CHECKING, Union
|
from typing import TYPE_CHECKING, Literal, Union
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
from bpy.props import (
|
from bpy.props import (
|
||||||
@@ -66,7 +66,7 @@ class Document(PropertyGroup):
|
|||||||
tree_depth: int
|
tree_depth: int
|
||||||
has_children: bool
|
has_children: bool
|
||||||
is_expanded: bool
|
is_expanded: bool
|
||||||
document_type: str
|
document_type: Literal["PROJECT", "INFORMATION", "REFERENCE"]
|
||||||
|
|
||||||
|
|
||||||
class DocumentObject(PropertyGroup):
|
class DocumentObject(PropertyGroup):
|
||||||
|
|||||||
@@ -16,9 +16,22 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
import bpy
|
||||||
from bpy.types import Panel, UIList
|
from bpy.types import Panel, UIList
|
||||||
|
|
||||||
import bonsai.tool as tool
|
import bonsai.tool as tool
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from bonsai.bim.module.document.prop import (
|
||||||
|
BIMDocumentProperties,
|
||||||
|
Document,
|
||||||
|
DocumentObject,
|
||||||
|
)
|
||||||
|
|
||||||
from bonsai.bim.helper import draw_attributes
|
from bonsai.bim.helper import draw_attributes
|
||||||
from bonsai.bim.module.document.data import DocumentData, ObjectDocumentData
|
from bonsai.bim.module.document.data import DocumentData, ObjectDocumentData
|
||||||
|
|
||||||
@@ -207,7 +220,16 @@ class BIM_PT_object_documents(Panel):
|
|||||||
|
|
||||||
|
|
||||||
class BIM_UL_documents(UIList):
|
class BIM_UL_documents(UIList):
|
||||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
def draw_item(
|
||||||
|
self,
|
||||||
|
context,
|
||||||
|
layout: bpy.types.UILayout,
|
||||||
|
data: BIMDocumentProperties,
|
||||||
|
item: Document,
|
||||||
|
icon,
|
||||||
|
active_data,
|
||||||
|
active_propname,
|
||||||
|
) -> None:
|
||||||
if item:
|
if item:
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
indent_depth = 0
|
indent_depth = 0
|
||||||
@@ -252,16 +274,22 @@ class BIM_UL_documents(UIList):
|
|||||||
|
|
||||||
|
|
||||||
class BIM_UL_document_objects(UIList):
|
class BIM_UL_document_objects(UIList):
|
||||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
def draw_item(
|
||||||
|
self,
|
||||||
|
context,
|
||||||
|
layout: bpy.types.UILayout,
|
||||||
|
data: BIMDocumentProperties,
|
||||||
|
item: DocumentObject,
|
||||||
|
icon,
|
||||||
|
active_data,
|
||||||
|
active_propname,
|
||||||
|
) -> None:
|
||||||
if item:
|
if item:
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
row.prop(item, "name", text="", emboss=False, icon="OBJECT_DATA")
|
row.prop(item, "name", text="", emboss=False, icon="OBJECT_DATA")
|
||||||
row.operator("bim.select_object", text="", icon="RESTRICT_SELECT_OFF").obj_name = item.name
|
row.operator("bim.select_object", text="", icon="RESTRICT_SELECT_OFF").obj_name = item.name
|
||||||
|
|
||||||
props = tool.Document.get_document_props()
|
if document := data.active_document:
|
||||||
if props.active_document:
|
|
||||||
document = props.active_document
|
|
||||||
|
|
||||||
op = row.operator("bim.unassign_document", text="", icon="X")
|
op = row.operator("bim.unassign_document", text="", icon="X")
|
||||||
op.document = document.ifc_definition_id
|
op.document = document.ifc_definition_id
|
||||||
op.obj = item.name
|
op.obj = item.name
|
||||||
|
|||||||
@@ -22,7 +22,9 @@ from pathlib import Path
|
|||||||
from typing import Any, Union
|
from typing import Any, Union
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
|
import ifcopenshell.util.classification
|
||||||
import ifcopenshell.util.element
|
import ifcopenshell.util.element
|
||||||
|
import ifcopenshell.util.placement
|
||||||
import ifcopenshell.util.unit
|
import ifcopenshell.util.unit
|
||||||
from natsort import natsorted
|
from natsort import natsorted
|
||||||
|
|
||||||
|
|||||||
@@ -70,9 +70,9 @@ class profile_consequential:
|
|||||||
cls.start_time = None
|
cls.start_time = None
|
||||||
lines = "\n".join(cls.lines)
|
lines = "\n".join(cls.lines)
|
||||||
print(lines)
|
print(lines)
|
||||||
import pyperclip
|
|
||||||
|
|
||||||
pyperclip.copy(lines)
|
assert (wm := bpy.context.window_manager)
|
||||||
|
wm.clipboard = lines
|
||||||
cls.lines = []
|
cls.lines = []
|
||||||
|
|
||||||
|
|
||||||
@@ -1787,7 +1787,7 @@ class CutDecorator:
|
|||||||
|
|
||||||
# Handle both old float64 and new float32 checksums for version compatibility
|
# Handle both old float64 and new float32 checksums for version compatibility
|
||||||
rot_checksum_bytes: bytes = eval(DecoratorData.camera_rotation_checksum)
|
rot_checksum_bytes: bytes = eval(DecoratorData.camera_rotation_checksum)
|
||||||
rot_check = tool.Blender.np_frombuffer_legacy(rot_checksum_bytes, 9)
|
rot_check = tool.Blender.np_frombuffer_legacy(rot_checksum_bytes, 9).reshape(3, 3)
|
||||||
rot_real = tool.Blender.np_array_legacy(obj.matrix_world.to_3x3())
|
rot_real = tool.Blender.np_array_legacy(obj.matrix_world.to_3x3())
|
||||||
rot_dot = np.dot(rot_check, rot_real.T)
|
rot_dot = np.dot(rot_check, rot_real.T)
|
||||||
angle_rad = np.arccos(np.clip((np.trace(rot_dot) - 1) / 2, -1, 1))
|
angle_rad = np.arccos(np.clip((np.trace(rot_dot) - 1) / 2, -1, 1))
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ from bpy_extras.view3d_utils import (
|
|||||||
)
|
)
|
||||||
from gpu_extras.batch import batch_for_shader
|
from gpu_extras.batch import batch_for_shader
|
||||||
from ifcopenshell.util.unit import si_conversions
|
from ifcopenshell.util.unit import si_conversions
|
||||||
from mathutils import Matrix, Vector
|
from mathutils import Matrix, Vector, geometry
|
||||||
from mathutils.geometry import intersect_line_line
|
from mathutils.geometry import intersect_line_line
|
||||||
from mathutils.kdtree import KDTree
|
from mathutils.kdtree import KDTree
|
||||||
|
|
||||||
@@ -1243,9 +1243,7 @@ class SnapManager:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def _redraw_viewport() -> None:
|
def _redraw_viewport() -> None:
|
||||||
"""Force 3D viewport redraw."""
|
"""Force 3D viewport redraw."""
|
||||||
for area in bpy.context.screen.areas:
|
tool.Blender.update_all_viewports()
|
||||||
if area.type == "VIEW_3D":
|
|
||||||
area.tag_redraw()
|
|
||||||
|
|
||||||
def build_snap_cache(
|
def build_snap_cache(
|
||||||
self, context: bpy.types.Context, active_obj: bpy.types.Object, include_active: bool = False
|
self, context: bpy.types.Context, active_obj: bpy.types.Object, include_active: bool = False
|
||||||
@@ -1287,7 +1285,7 @@ class SnapManager:
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
coords = np.empty(vertex_count * 3, dtype=np.float32)
|
coords = np.empty(vertex_count * 3, dtype=np.float32)
|
||||||
mesh.vertices.foreach_get("co", coords) # type: ignore[arg-type]
|
mesh.vertices.foreach_get("co", coords)
|
||||||
coords = coords.reshape(-1, 3)
|
coords = coords.reshape(-1, 3)
|
||||||
|
|
||||||
matrix = np.array(obj_eval.matrix_world, dtype=np.float32)
|
matrix = np.array(obj_eval.matrix_world, dtype=np.float32)
|
||||||
|
|||||||
@@ -456,7 +456,8 @@ def format_distance(
|
|||||||
tx_dist = fmt % d_cm
|
tx_dist = fmt % d_cm
|
||||||
|
|
||||||
else:
|
else:
|
||||||
tx_dist = fmt % value
|
assert f"Unexpected unit_system - '{unit_system}'."
|
||||||
|
# tx_dist = fmt % value
|
||||||
|
|
||||||
return tx_dist
|
return tx_dist
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ import bmesh
|
|||||||
import bpy
|
import bpy
|
||||||
import ifcopenshell
|
import ifcopenshell
|
||||||
import ifcopenshell.api.document
|
import ifcopenshell.api.document
|
||||||
|
import ifcopenshell.api.geometry
|
||||||
import ifcopenshell.api.pset
|
import ifcopenshell.api.pset
|
||||||
import ifcopenshell.api.style
|
import ifcopenshell.api.style
|
||||||
import ifcopenshell.geom
|
import ifcopenshell.geom
|
||||||
@@ -1425,6 +1426,7 @@ class CreateDrawing(bpy.types.Operator):
|
|||||||
"/Pset_.*Common/.Status",
|
"/Pset_.*Common/.Status",
|
||||||
"EPset_Status.Status",
|
"EPset_Status.Status",
|
||||||
"EPset_Status.UserDefinedStatus",
|
"EPset_Status.UserDefinedStatus",
|
||||||
|
"Material.Name",
|
||||||
]
|
]
|
||||||
|
|
||||||
group = root.find("{http://www.w3.org/2000/svg}g")
|
group = root.find("{http://www.w3.org/2000/svg}g")
|
||||||
@@ -3304,9 +3306,8 @@ class AddTextLiteral(bpy.types.Operator):
|
|||||||
attr.data_type = "string"
|
attr.data_type = "string"
|
||||||
attr.string_value = literal_attr_values[attr_name]
|
attr.string_value = literal_attr_values[attr_name]
|
||||||
|
|
||||||
box_alignment_mask = [False] * 9
|
literal_props.align_vertical = "bottom"
|
||||||
box_alignment_mask[6] = True # bottom_left box_alignment
|
literal_props.align_horizontal = "left"
|
||||||
literal_props.box_alignment = box_alignment_mask
|
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
@@ -3364,57 +3365,55 @@ class OrderTextLiteralDown(bpy.types.Operator):
|
|||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
# Ifc Operator is unnecessary, because suboperator is handling IFC changes.
|
class AssignSelectedObjectAsProduct(bpy.types.Operator, tool.Ifc.Operator):
|
||||||
class AssignSelectedObjectAsProduct(bpy.types.Operator):
|
|
||||||
bl_idname = "bim.assign_selected_as_product"
|
bl_idname = "bim.assign_selected_as_product"
|
||||||
bl_label = "Assign Selected Object As Product"
|
bl_label = "Assign Selected Object As Product"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
if len(context.selected_objects) != 2:
|
if len(context.selected_objects) < 2:
|
||||||
cls.poll_message_set("2 objects need to be selected")
|
cls.poll_message_set("At least 2 objects need to be selected")
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def execute(self, context):
|
def _execute(self, context):
|
||||||
assert bpy.context.view_layer
|
|
||||||
objs = context.selected_objects[:]
|
objs = context.selected_objects[:]
|
||||||
obj1, obj2 = objs
|
ifc_objs = [(o, tool.Ifc.get_entity(o)) for o in objs if tool.Ifc.get_entity(o)]
|
||||||
element1 = tool.Ifc.get_entity(obj1)
|
|
||||||
element2 = tool.Ifc.get_entity(obj2)
|
|
||||||
assert element1 and element2
|
|
||||||
|
|
||||||
# Check if at least one object is an IfcAnnotation
|
annotations = [(o, e) for o, e in ifc_objs if e.is_a("IfcAnnotation")]
|
||||||
is_annotation1 = element1.is_a("IfcAnnotation")
|
non_annotations = [(o, e) for o, e in ifc_objs if not e.is_a("IfcAnnotation")]
|
||||||
is_annotation2 = element2.is_a("IfcAnnotation")
|
|
||||||
|
|
||||||
if not (is_annotation1 or is_annotation2):
|
if not annotations:
|
||||||
self.report({"ERROR"}, "At least one of the selected objects must be IfcAnnotation.")
|
self.report({"ERROR"}, "At least one selected object must be an IfcAnnotation.")
|
||||||
return {"CANCELLED"}
|
return {"CANCELLED"}
|
||||||
|
|
||||||
# If both are annotations, use the currently active object as relating product
|
if len(non_annotations) == 1:
|
||||||
if is_annotation1 and is_annotation2:
|
# One product, one or more annotations — assign all annotations to the product.
|
||||||
|
product = non_annotations[0][1]
|
||||||
|
elif len(non_annotations) == 0 and len(annotations) == 2:
|
||||||
|
# Both objects are annotations — use the non-active one as the relating product.
|
||||||
active_obj = context.active_object
|
active_obj = context.active_object
|
||||||
if active_obj == obj1:
|
if annotations[0][0] == active_obj:
|
||||||
other_selected_object = obj1
|
annotation_obj, annotation = annotations[0]
|
||||||
bpy.context.view_layer.objects.active = obj2
|
product = annotations[1][1]
|
||||||
else:
|
else:
|
||||||
other_selected_object = obj2
|
annotation_obj, annotation = annotations[1]
|
||||||
bpy.context.view_layer.objects.active = obj1
|
product = annotations[0][1]
|
||||||
# If only one is an annotation, make it the active object
|
core.edit_assigned_product(tool.Ifc, tool.Drawing, obj=annotation_obj, product=product)
|
||||||
elif is_annotation1:
|
tool.Blender.update_viewport()
|
||||||
other_selected_object = obj2
|
return
|
||||||
bpy.context.view_layer.objects.active = obj1
|
|
||||||
else:
|
else:
|
||||||
other_selected_object = obj1
|
self.report(
|
||||||
bpy.context.view_layer.objects.active = obj2
|
{"ERROR"},
|
||||||
|
"Select exactly one product object and one or more IfcAnnotation objects.",
|
||||||
|
)
|
||||||
|
return {"CANCELLED"}
|
||||||
|
|
||||||
assert (active_obj := context.active_object)
|
for annotation_obj, _ in annotations:
|
||||||
props = tool.Drawing.get_object_assigned_product_props(active_obj)
|
core.edit_assigned_product(tool.Ifc, tool.Drawing, obj=annotation_obj, product=product)
|
||||||
props.relating_product = other_selected_object
|
|
||||||
bpy.ops.bim.edit_assigned_product()
|
tool.Blender.update_viewport()
|
||||||
return {"FINISHED"}
|
|
||||||
|
|
||||||
|
|
||||||
class EditAssignedProduct(bpy.types.Operator, tool.Ifc.Operator):
|
class EditAssignedProduct(bpy.types.Operator, tool.Ifc.Operator):
|
||||||
@@ -3886,8 +3885,7 @@ class AddReferenceImage(bpy.types.Operator, tool.Ifc.Operator, ImportHelper):
|
|||||||
image_filepath = Path(tool.Ifc.get_uri(self.filepath, use_relative_path=self.use_relative_path))
|
image_filepath = Path(tool.Ifc.get_uri(self.filepath, use_relative_path=self.use_relative_path))
|
||||||
ifc_file = tool.Ifc.get()
|
ifc_file = tool.Ifc.get()
|
||||||
|
|
||||||
params = {"check_existing": False}
|
image = load_image(abs_path.name, str(abs_path.parent), check_existing=False)
|
||||||
image = load_image(abs_path.name, str(abs_path.parent), **params)
|
|
||||||
|
|
||||||
mesh = bpy.data.meshes.new(image_filepath.stem)
|
mesh = bpy.data.meshes.new(image_filepath.stem)
|
||||||
obj = bpy.data.objects.new(image_filepath.stem, mesh)
|
obj = bpy.data.objects.new(image_filepath.stem, mesh)
|
||||||
@@ -4177,10 +4175,7 @@ class SelectSimilarTextLiteralValue(bpy.types.Operator):
|
|||||||
should_select = True
|
should_select = True
|
||||||
break
|
break
|
||||||
elif self.attribute_type == "box_alignment":
|
elif self.attribute_type == "box_alignment":
|
||||||
box_alignment_attr = next(
|
if literal.get_box_alignment() == self.literal_value:
|
||||||
(attr for attr in literal.attributes if attr.name == "BoxAlignment"), None
|
|
||||||
)
|
|
||||||
if box_alignment_attr and box_alignment_attr.string_value == self.literal_value:
|
|
||||||
should_select = True
|
should_select = True
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ import ifcopenshell.api.pset
|
|||||||
import ifcopenshell.util.element
|
import ifcopenshell.util.element
|
||||||
from bpy.props import (
|
from bpy.props import (
|
||||||
BoolProperty,
|
BoolProperty,
|
||||||
BoolVectorProperty,
|
|
||||||
CollectionProperty,
|
CollectionProperty,
|
||||||
EnumProperty,
|
EnumProperty,
|
||||||
FloatProperty,
|
FloatProperty,
|
||||||
@@ -673,20 +672,6 @@ class BIMCameraProperties(PropertyGroup):
|
|||||||
return ortho_scale, aspect_ratio
|
return ortho_scale, aspect_ratio
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_BOX_ALIGNMENT = [False] * 6 + [True] + [False] * 2
|
|
||||||
BOX_ALIGNMENT_POSITIONS = [
|
|
||||||
"top-left",
|
|
||||||
"top-middle",
|
|
||||||
"top-right",
|
|
||||||
"middle-left",
|
|
||||||
"center",
|
|
||||||
"middle-right",
|
|
||||||
"bottom-left",
|
|
||||||
"bottom-middle",
|
|
||||||
"bottom-right",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class ElementValueRow(PropertyGroup):
|
class ElementValueRow(PropertyGroup):
|
||||||
"""Represents a single element value row with category, key, and formatted value"""
|
"""Represents a single element value row with category, key, and formatted value"""
|
||||||
|
|
||||||
@@ -789,40 +774,38 @@ def get_category_items_with_counts(self, context):
|
|||||||
|
|
||||||
|
|
||||||
class LiteralProps(PropertyGroup):
|
class LiteralProps(PropertyGroup):
|
||||||
def set_box_alignment(self, new_value):
|
|
||||||
markers = new_value.count(True)
|
|
||||||
if not markers:
|
|
||||||
return
|
|
||||||
|
|
||||||
if markers > 1:
|
|
||||||
prev_value = self.get("box_alignment", DEFAULT_BOX_ALIGNMENT)
|
|
||||||
# looking for the first value changed to positive
|
|
||||||
first_changed_value = next((i for i in range(9) if new_value[i] and new_value[i] != prev_value[i]), None)
|
|
||||||
|
|
||||||
# if nothing have changed we just keep the previous value
|
|
||||||
if first_changed_value is None:
|
|
||||||
return
|
|
||||||
new_value = [False] * 9
|
|
||||||
new_value[first_changed_value] = True
|
|
||||||
|
|
||||||
self["box_alignment"] = new_value
|
|
||||||
position_string = BOX_ALIGNMENT_POSITIONS[next(i for i in range(9) if new_value[i])]
|
|
||||||
self.attributes["BoxAlignment"].set_value(position_string)
|
|
||||||
|
|
||||||
def get_box_alignment(self):
|
|
||||||
return self.get("box_alignment", DEFAULT_BOX_ALIGNMENT)
|
|
||||||
|
|
||||||
attributes: CollectionProperty(name="Attributes", type=Attribute)
|
attributes: CollectionProperty(name="Attributes", type=Attribute)
|
||||||
box_alignment: BoolVectorProperty(
|
|
||||||
name="Box alignment", size=9, set=set_box_alignment, get=get_box_alignment, default=DEFAULT_BOX_ALIGNMENT
|
|
||||||
)
|
|
||||||
ifc_definition_id: IntProperty(name="IFC definition ID", default=0)
|
ifc_definition_id: IntProperty(name="IFC definition ID", default=0)
|
||||||
|
align_horizontal: EnumProperty(
|
||||||
|
items=[
|
||||||
|
("left", "Left", "", "ALIGN_LEFT", 0),
|
||||||
|
("middle", "Middle", "", "ALIGN_CENTER", 1),
|
||||||
|
("right", "Right", "", "ALIGN_RIGHT", 2),
|
||||||
|
],
|
||||||
|
default="left",
|
||||||
|
name="Horizontal Alignment",
|
||||||
|
)
|
||||||
|
align_vertical: EnumProperty(
|
||||||
|
items=[
|
||||||
|
("top", "Top", "", "ALIGN_TOP", 0),
|
||||||
|
("middle", "Middle", "", "ALIGN_MIDDLE", 1),
|
||||||
|
("bottom", "Bottom", "", "ALIGN_BOTTOM", 2),
|
||||||
|
],
|
||||||
|
default="middle",
|
||||||
|
name="Vertical Alignment",
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_box_alignment(self) -> str:
|
||||||
|
alignment = self.align_vertical + "-" + self.align_horizontal
|
||||||
|
if alignment == "middle-middle":
|
||||||
|
alignment = "center"
|
||||||
|
return alignment
|
||||||
|
|
||||||
def get_literal_edited_data(self) -> dict[str, str]:
|
def get_literal_edited_data(self) -> dict[str, str]:
|
||||||
text_data = {
|
text_data = {
|
||||||
"CurrentValue": self.attributes["Literal"].string_value,
|
"CurrentValue": self.attributes["Literal"].string_value,
|
||||||
"Literal": self.attributes["Literal"].string_value,
|
"Literal": self.attributes["Literal"].string_value,
|
||||||
"BoxAlignment": self.attributes["BoxAlignment"].string_value,
|
"BoxAlignment": self.get_box_alignment(),
|
||||||
}
|
}
|
||||||
return text_data
|
return text_data
|
||||||
|
|
||||||
@@ -860,12 +843,19 @@ class LiteralProps(PropertyGroup):
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
attributes: bpy.types.bpy_prop_collection_idprop[Attribute]
|
attributes: bpy.types.bpy_prop_collection_idprop[Attribute]
|
||||||
value: str
|
value: str
|
||||||
box_alignment: tuple[bool, bool, bool, bool, bool, bool, bool, bool, bool]
|
|
||||||
ifc_definition_id: int
|
ifc_definition_id: int
|
||||||
|
align_horizontal: str
|
||||||
|
align_vertical: str
|
||||||
element_value_rows: bpy.types.bpy_prop_collection_idprop[ElementValueRow]
|
element_value_rows: bpy.types.bpy_prop_collection_idprop[ElementValueRow]
|
||||||
category_for_adding: str
|
category_for_adding: str
|
||||||
|
|
||||||
|
|
||||||
|
def update_text_alignment(self, context):
|
||||||
|
for literal_props in self.literals:
|
||||||
|
literal_props.align_horizontal = self.align_horizontal
|
||||||
|
literal_props.align_vertical = self.align_vertical
|
||||||
|
|
||||||
|
|
||||||
class BIMTextProperties(PropertyGroup):
|
class BIMTextProperties(PropertyGroup):
|
||||||
is_editing: BoolProperty(name="Is Editing", default=False)
|
is_editing: BoolProperty(name="Is Editing", default=False)
|
||||||
literals: CollectionProperty(name="Literals", type=LiteralProps)
|
literals: CollectionProperty(name="Literals", type=LiteralProps)
|
||||||
@@ -899,6 +889,7 @@ class BIMTextProperties(PropertyGroup):
|
|||||||
],
|
],
|
||||||
default="left",
|
default="left",
|
||||||
name="Horizontal Alignment",
|
name="Horizontal Alignment",
|
||||||
|
update=update_text_alignment,
|
||||||
)
|
)
|
||||||
align_vertical: EnumProperty(
|
align_vertical: EnumProperty(
|
||||||
items=[
|
items=[
|
||||||
@@ -908,6 +899,7 @@ class BIMTextProperties(PropertyGroup):
|
|||||||
],
|
],
|
||||||
default="middle",
|
default="middle",
|
||||||
name="Vertical Alignment",
|
name="Vertical Alignment",
|
||||||
|
update=update_text_alignment,
|
||||||
)
|
)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
|||||||
@@ -366,9 +366,6 @@ class BaseLinesShader(BaseShader):
|
|||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, gap_size=16):
|
|
||||||
super().__init__(gap_size=gap_size)
|
|
||||||
|
|
||||||
def glenable(self):
|
def glenable(self):
|
||||||
super().glenable()
|
super().glenable()
|
||||||
|
|
||||||
|
|||||||
@@ -781,33 +781,10 @@ class BIM_PT_text(Panel):
|
|||||||
if other_attributes:
|
if other_attributes:
|
||||||
bonsai.bim.helper.draw_attributes(other_attributes, box)
|
bonsai.bim.helper.draw_attributes(other_attributes, box)
|
||||||
|
|
||||||
row = box.row(align=True)
|
row = box.row()
|
||||||
cols = [row.column(align=True) for j in range(3)]
|
row.label(text="Alignment")
|
||||||
for j in range(9):
|
row.prop(literal_props, "align_horizontal", text="", expand=True)
|
||||||
cols[j % 3].prop(
|
row.prop(literal_props, "align_vertical", text="", expand=True)
|
||||||
literal_props,
|
|
||||||
"box_alignment",
|
|
||||||
text="",
|
|
||||||
index=j,
|
|
||||||
icon="RADIOBUT_ON" if literal_props.box_alignment[j] else "RADIOBUT_OFF",
|
|
||||||
)
|
|
||||||
|
|
||||||
col = row.column(align=True)
|
|
||||||
alignment_label_row = col.row(align=True)
|
|
||||||
alignment_label_row.label(text=" Text box alignment:")
|
|
||||||
|
|
||||||
box_alignment_value = (
|
|
||||||
literal_props.attributes[
|
|
||||||
next(
|
|
||||||
(idx for idx, attr in enumerate(literal_props.attributes) if attr.name == "BoxAlignment"),
|
|
||||||
-1,
|
|
||||||
)
|
|
||||||
].string_value
|
|
||||||
if any(attr.name == "BoxAlignment" for attr in literal_props.attributes)
|
|
||||||
else "N/A"
|
|
||||||
)
|
|
||||||
|
|
||||||
col.label(text=f" {box_alignment_value}")
|
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
obj = context.active_object
|
obj = context.active_object
|
||||||
@@ -839,7 +816,6 @@ class BIM_PT_text(Panel):
|
|||||||
|
|
||||||
for i, literal_data in enumerate(text_data["Literals"]):
|
for i, literal_data in enumerate(text_data["Literals"]):
|
||||||
box = self.layout.box()
|
box = self.layout.box()
|
||||||
box.label(text=f"Literal[{i}]:")
|
|
||||||
|
|
||||||
# Combine both approaches: clickable attributes from PR #7292 and display from PR #7106
|
# Combine both approaches: clickable attributes from PR #7292 and display from PR #7106
|
||||||
for attribute in literal_data:
|
for attribute in literal_data:
|
||||||
|
|||||||
@@ -1066,7 +1066,7 @@ class OverrideOutlinerDelete(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
cls.poll_message_set("Only available from Outliner.")
|
cls.poll_message_set("Only available from Outliner.")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context): # ty:ignore[override-of-final-method]
|
||||||
if len(getattr(context, "selected_ids", [])) == 0:
|
if len(getattr(context, "selected_ids", [])) == 0:
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
@@ -2289,7 +2289,7 @@ class OverrideModeSetEdit(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
elif obj in pprops.clipping_planes_objs:
|
elif obj in pprops.clipping_planes_objs:
|
||||||
self.report({"ERROR"}, "Clipping planes cannot be edited")
|
self.report({"ERROR"}, "Clipping planes cannot be edited")
|
||||||
elif element:
|
elif element:
|
||||||
if not obj.data:
|
if not obj.data or obj.type not in ("MESH", "CURVE"):
|
||||||
self.report({"INFO"}, "No geometry to edit")
|
self.report({"INFO"}, "No geometry to edit")
|
||||||
elif tool.Geometry.is_locked(element):
|
elif tool.Geometry.is_locked(element):
|
||||||
self.report({"ERROR"}, lock_error_message(obj.name))
|
self.report({"ERROR"}, lock_error_message(obj.name))
|
||||||
|
|||||||
@@ -515,6 +515,8 @@ class BIM_PT_derived_coordinates(Panel):
|
|||||||
return context.active_object is not None
|
return context.active_object is not None
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
|
assert context.active_object
|
||||||
|
props = tool.Model.get_model_props()
|
||||||
if not DerivedCoordinatesData.is_loaded:
|
if not DerivedCoordinatesData.is_loaded:
|
||||||
DerivedCoordinatesData.load()
|
DerivedCoordinatesData.load()
|
||||||
|
|
||||||
@@ -529,10 +531,8 @@ class BIM_PT_derived_coordinates(Panel):
|
|||||||
|
|
||||||
row = self.layout.row(align=True)
|
row = self.layout.row(align=True)
|
||||||
row.enabled = False
|
row.enabled = False
|
||||||
area_3d = next((area for area in context.screen.areas if area.type == "VIEW_3D"), None)
|
|
||||||
space_3d = next((space for space in area_3d.spaces if space.type == "VIEW_3D"), None)
|
|
||||||
|
|
||||||
if bpy.context.scene.BIMModelProperties.show_bounding_box:
|
if props.show_bounding_box:
|
||||||
for axis, icon, idx in [("X", "STRIP_COLOR_01", 0), ("Y", "STRIP_COLOR_04", 1), ("Z", "STRIP_COLOR_05", 2)]:
|
for axis, icon, idx in [("X", "STRIP_COLOR_01", 0), ("Y", "STRIP_COLOR_04", 1), ("Z", "STRIP_COLOR_05", 2)]:
|
||||||
row.label(text="", icon=icon)
|
row.label(text="", icon=icon)
|
||||||
row.prop(context.active_object, "dimensions", text=axis, index=idx)
|
row.prop(context.active_object, "dimensions", text=axis, index=idx)
|
||||||
|
|||||||
@@ -139,7 +139,9 @@ def update_local_coordinates(self: "BIMGeoreferenceProperties", context: bpy.typ
|
|||||||
tool.Georeference.set_coordinates(
|
tool.Georeference.set_coordinates(
|
||||||
"blender",
|
"blender",
|
||||||
ifcopenshell.util.geolocation.enh2xyz(
|
ifcopenshell.util.geolocation.enh2xyz(
|
||||||
*local_coordinates,
|
local_coordinates[0],
|
||||||
|
local_coordinates[1],
|
||||||
|
local_coordinates[2],
|
||||||
float(props.blender_offset_x),
|
float(props.blender_offset_x),
|
||||||
float(props.blender_offset_y),
|
float(props.blender_offset_y),
|
||||||
float(props.blender_offset_z),
|
float(props.blender_offset_z),
|
||||||
@@ -162,7 +164,9 @@ def update_map_coordinates(self: "BIMGeoreferenceProperties", context: bpy.types
|
|||||||
tool.Georeference.set_coordinates(
|
tool.Georeference.set_coordinates(
|
||||||
"blender",
|
"blender",
|
||||||
ifcopenshell.util.geolocation.enh2xyz(
|
ifcopenshell.util.geolocation.enh2xyz(
|
||||||
*local_coordinates,
|
local_coordinates[0],
|
||||||
|
local_coordinates[1],
|
||||||
|
local_coordinates[2],
|
||||||
float(props.blender_offset_x),
|
float(props.blender_offset_x),
|
||||||
float(props.blender_offset_y),
|
float(props.blender_offset_y),
|
||||||
float(props.blender_offset_z),
|
float(props.blender_offset_z),
|
||||||
@@ -267,6 +271,8 @@ class BIMGeoreferenceProperties(PropertyGroup):
|
|||||||
x_axis_ordinate: str
|
x_axis_ordinate: str
|
||||||
x_axis_is_null: bool
|
x_axis_is_null: bool
|
||||||
|
|
||||||
|
model_is_georeferenced: bool
|
||||||
|
model_crs: str
|
||||||
model_origin: str
|
model_origin: str
|
||||||
model_origin_si: str
|
model_origin_si: str
|
||||||
model_project_north: str
|
model_project_north: str
|
||||||
|
|||||||
@@ -16,6 +16,9 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
import bpy
|
||||||
from bpy.props import BoolProperty, CollectionProperty, EnumProperty, StringProperty
|
from bpy.props import BoolProperty, CollectionProperty, EnumProperty, StringProperty
|
||||||
from bpy.types import PropertyGroup
|
from bpy.types import PropertyGroup
|
||||||
|
|
||||||
@@ -24,7 +27,7 @@ from bonsai.bim.prop import StrProperty
|
|||||||
|
|
||||||
class BIMCityJsonProperties(PropertyGroup):
|
class BIMCityJsonProperties(PropertyGroup):
|
||||||
def get_lods(self, context):
|
def get_lods(self, context):
|
||||||
global LODS_ENUM_ITEMS
|
global LODS_ENUM_ITEMS # ty: ignore[unresolved-global]
|
||||||
LODS_ENUM_ITEMS = [(item.name, "LOD" + item.name, "Level of Detail " + item.name) for item in self.lods]
|
LODS_ENUM_ITEMS = [(item.name, "LOD" + item.name, "Level of Detail " + item.name) for item in self.lods]
|
||||||
return LODS_ENUM_ITEMS
|
return LODS_ENUM_ITEMS
|
||||||
|
|
||||||
@@ -37,3 +40,13 @@ class BIMCityJsonProperties(PropertyGroup):
|
|||||||
lod: EnumProperty(name="LOD", description="", items=get_lods, options={"ANIMATABLE"}, default=None)
|
lod: EnumProperty(name="LOD", description="", items=get_lods, options={"ANIMATABLE"}, default=None)
|
||||||
is_lod_found: BoolProperty(name="Is LOD Found", default=False)
|
is_lod_found: BoolProperty(name="Is LOD Found", default=False)
|
||||||
load_after_convert: BoolProperty(name="Load After Converting", default=True)
|
load_after_convert: BoolProperty(name="Load After Converting", default=True)
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
input: str
|
||||||
|
output: str
|
||||||
|
name: str
|
||||||
|
split_lod: bool
|
||||||
|
lods: bpy.types.bpy_prop_collection_idprop[StrProperty]
|
||||||
|
lod: str
|
||||||
|
is_lod_found: bool
|
||||||
|
load_after_convert: bool
|
||||||
|
|||||||
@@ -21,65 +21,71 @@ class IfcGitData:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def load(cls):
|
def load(cls):
|
||||||
|
repo = None
|
||||||
|
if bool(tool.Ifc.get()):
|
||||||
|
path_ifc = tool.Ifc.get_path()
|
||||||
|
if os.path.isfile(path_ifc):
|
||||||
|
repo = tool.IfcGit.repo_from_path(path_ifc)
|
||||||
|
|
||||||
cls.data = {
|
cls.data = {
|
||||||
"repo": cls.repo(),
|
"repo": repo,
|
||||||
"remotes": cls.remotes(),
|
"remotes": repo.remotes if repo else None,
|
||||||
"branch_names": cls.branch_names(),
|
"branch_names": cls.branch_names(repo),
|
||||||
"remote_names": cls.remote_names(),
|
"tag_names": cls.tag_names(repo),
|
||||||
"remote_urls": cls.remote_urls(),
|
"remote_names": cls.remote_names(repo),
|
||||||
|
"remote_urls": {r.name: r.url for r in repo.remotes} if repo else {},
|
||||||
"path_ifc": cls.path_ifc(),
|
"path_ifc": cls.path_ifc(),
|
||||||
"branches_by_hexsha": cls.branches_by_hexsha(),
|
"branches_by_hexsha": cls.branches_by_hexsha(),
|
||||||
"tags_by_hexsha": cls.tags_by_hexsha(),
|
"tags_by_hexsha": cls.tags_by_hexsha(),
|
||||||
"name_ifc": cls.name_ifc(),
|
"name_ifc": cls.name_ifc(repo),
|
||||||
"dir_name": cls.dir_name(),
|
"dir_name": cls.dir_name(),
|
||||||
"base_name": cls.base_name(),
|
"base_name": cls.base_name(),
|
||||||
"working_dir": cls.working_dir(),
|
"working_dir": repo.working_dir if repo else None,
|
||||||
"untracked_files": cls.untracked_files(),
|
"ifc_is_untracked": cls.ifc_is_untracked(repo),
|
||||||
"is_detached": cls.is_detached(),
|
"is_detached": repo.head.is_detached if repo else None,
|
||||||
"active_branch_name": cls.active_branch_name(),
|
"active_branch_name": repo.active_branch.name if repo and not repo.head.is_detached else None,
|
||||||
"is_dirty": cls.is_dirty(),
|
"is_dirty": cls.is_dirty(repo),
|
||||||
"commit": cls.commit(),
|
"current_revision": cls.current_revision(repo),
|
||||||
"current_revision": cls.current_revision(),
|
|
||||||
"git_exe": cls.git_exe(),
|
"git_exe": cls.git_exe(),
|
||||||
"ifcmerge_exe": cls.ifcmerge_exe(),
|
"ifcmerge_exe": cls.ifcmerge_exe(),
|
||||||
}
|
}
|
||||||
cls.is_loaded = True
|
cls.is_loaded = True
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def repo(cls):
|
def branch_names(cls, repo):
|
||||||
if bool(tool.Ifc.get()):
|
if not repo or not repo.heads:
|
||||||
path_ifc = tool.Ifc.get_path()
|
return []
|
||||||
if os.path.isfile(path_ifc):
|
names = sorted([b.name for b in repo.branches])
|
||||||
return tool.IfcGit.repo_from_path(path_ifc)
|
if "main" in names:
|
||||||
return None
|
names.remove("main")
|
||||||
|
names = ["main"] + names
|
||||||
|
if repo.remotes:
|
||||||
|
for remote in repo.remotes:
|
||||||
|
for ref in remote.refs:
|
||||||
|
names.append(ref.name)
|
||||||
|
return names
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def remotes(cls):
|
def tag_names(cls, repo):
|
||||||
if cls.repo():
|
if not repo:
|
||||||
return cls.repo().remotes
|
return []
|
||||||
return None
|
return [t.name for t in repo.tags]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def branch_names(cls):
|
def remote_names(cls, repo):
|
||||||
return []
|
if not repo:
|
||||||
|
return []
|
||||||
@classmethod
|
names = sorted([r.name for r in repo.remotes])
|
||||||
def remote_names(cls):
|
if "origin" in names:
|
||||||
return []
|
names.remove("origin")
|
||||||
|
names = ["origin"] + names
|
||||||
@classmethod
|
return names
|
||||||
def remote_urls(cls):
|
|
||||||
result = {}
|
|
||||||
if cls.repo():
|
|
||||||
for remote in cls.repo().remotes:
|
|
||||||
result[remote.name] = remote.url
|
|
||||||
return result
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def path_ifc(cls):
|
def path_ifc(cls):
|
||||||
path_ifc = tool.Ifc.get_path()
|
path_ifc = tool.Ifc.get_path()
|
||||||
if os.path.isfile(path_ifc):
|
if os.path.isfile(path_ifc):
|
||||||
return tool.Ifc.get_path()
|
return path_ifc
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -88,7 +94,8 @@ class IfcGitData:
|
|||||||
if tool.IfcGitRepo.repo.branches:
|
if tool.IfcGitRepo.repo.branches:
|
||||||
return tool.IfcGit.branches_by_hexsha(tool.IfcGitRepo.repo)
|
return tool.IfcGit.branches_by_hexsha(tool.IfcGitRepo.repo)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
return {}
|
pass
|
||||||
|
return {}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def tags_by_hexsha(cls):
|
def tags_by_hexsha(cls):
|
||||||
@@ -97,12 +104,11 @@ class IfcGitData:
|
|||||||
return {}
|
return {}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def name_ifc(cls):
|
def name_ifc(cls, repo):
|
||||||
if bool(tool.Ifc.get()):
|
if bool(tool.Ifc.get()) and repo:
|
||||||
path_ifc = tool.Ifc.get_path()
|
path_ifc = tool.Ifc.get_path()
|
||||||
if tool.IfcGitRepo.repo and os.path.isfile(path_ifc):
|
if os.path.isfile(path_ifc):
|
||||||
working_dir = tool.IfcGitRepo.repo.working_dir
|
return os.path.relpath(path_ifc, repo.working_dir)
|
||||||
return os.path.relpath(path_ifc, working_dir)
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -122,49 +128,28 @@ class IfcGitData:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def working_dir(cls):
|
def ifc_is_untracked(cls, repo):
|
||||||
if cls.repo():
|
"""Return True if the IFC file exists in the repo but has not been added to git."""
|
||||||
return cls.repo().working_dir
|
if not repo:
|
||||||
|
return False
|
||||||
|
path_ifc = tool.Ifc.get_path()
|
||||||
|
if not os.path.isfile(path_ifc):
|
||||||
|
return False
|
||||||
|
return not bool(repo.git.ls_files(path_ifc))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def untracked_files(cls):
|
def is_dirty(cls, repo):
|
||||||
if cls.repo():
|
if repo and cls.git_exe():
|
||||||
return cls.repo().untracked_files
|
|
||||||
return []
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def is_detached(cls):
|
|
||||||
if cls.repo():
|
|
||||||
return cls.repo().head.is_detached
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def active_branch_name(cls):
|
|
||||||
if cls.repo() and not cls.is_detached():
|
|
||||||
return cls.repo().active_branch.name
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def is_dirty(cls):
|
|
||||||
if cls.repo() and cls.git_exe():
|
|
||||||
path_ifc = tool.Ifc.get_path()
|
path_ifc = tool.Ifc.get_path()
|
||||||
if os.path.isfile(path_ifc):
|
if os.path.isfile(path_ifc):
|
||||||
return cls.repo().is_dirty(path=path_ifc)
|
return repo.is_dirty(path=path_ifc)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def commit(cls):
|
def current_revision(cls, repo):
|
||||||
props = tool.IfcGit.get_ifcgit_props()
|
props = tool.IfcGit.get_ifcgit_props()
|
||||||
if cls.repo() and len(props.ifcgit_commits) > 0:
|
if repo and repo.head.is_valid() and len(props.ifcgit_commits) > 0:
|
||||||
item = props.ifcgit_commits[props.commit_index]
|
return repo.commit()
|
||||||
try:
|
|
||||||
return cls.repo().commit(rev=item.hexsha)
|
|
||||||
except ValueError:
|
|
||||||
return
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def current_revision(cls):
|
|
||||||
props = tool.IfcGit.get_ifcgit_props()
|
|
||||||
if cls.repo() and cls.repo().head.is_valid() and len(props.ifcgit_commits) > 0:
|
|
||||||
return tool.IfcGitRepo.repo.commit()
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def git_exe(cls):
|
def git_exe(cls):
|
||||||
|
|||||||
@@ -120,11 +120,11 @@ class CommitChanges(bpy.types.Operator):
|
|||||||
if props.commit_message == "":
|
if props.commit_message == "":
|
||||||
return False
|
return False
|
||||||
if repo:
|
if repo:
|
||||||
if props.new_branch_name in [branch.name for branch in repo.branches]:
|
if props.new_branch_name in IfcGitData.data["branch_names"]:
|
||||||
cls.poll_message_set("Branch already exists!")
|
cls.poll_message_set("Branch already exists!")
|
||||||
return False
|
return False
|
||||||
elif not tool.IfcGit.is_valid_ref_format(props.new_branch_name):
|
elif not tool.IfcGit.is_valid_ref_format(props.new_branch_name):
|
||||||
if repo.head.is_detached:
|
if IfcGitData.data["is_detached"]:
|
||||||
cls.poll_message_set("Branch name is invalid or empty!")
|
cls.poll_message_set("Branch name is invalid or empty!")
|
||||||
return False
|
return False
|
||||||
elif props.new_branch_name != "":
|
elif props.new_branch_name != "":
|
||||||
@@ -134,10 +134,17 @@ class CommitChanges(bpy.types.Operator):
|
|||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
|
|
||||||
repo = IfcGitData.data["repo"]
|
props = tool.IfcGit.get_ifcgit_props()
|
||||||
core.commit_changes(tool.IfcGit, tool.Ifc, repo)
|
commit_message = props.commit_message
|
||||||
core.refresh_revision_list(tool.IfcGit, repo, tool.Ifc)
|
new_branch_name = props.new_branch_name
|
||||||
|
core.commit_changes(tool.IfcGit, tool.Ifc, commit_message, new_branch_name)
|
||||||
|
props.new_branch_name = ""
|
||||||
|
props.commit_message = ""
|
||||||
|
core.refresh_revision_list(tool.IfcGit, tool.Ifc)
|
||||||
refresh()
|
refresh()
|
||||||
|
IfcGitData.load()
|
||||||
|
if new_branch_name:
|
||||||
|
props.display_branch = new_branch_name
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
@@ -157,7 +164,7 @@ class AddTag(bpy.types.Operator):
|
|||||||
repo = IfcGitData.data["repo"]
|
repo = IfcGitData.data["repo"]
|
||||||
if repo and (
|
if repo and (
|
||||||
not tool.IfcGit.is_valid_ref_format(props.new_tag_name)
|
not tool.IfcGit.is_valid_ref_format(props.new_tag_name)
|
||||||
or props.new_tag_name in [tag.name for tag in repo.tags]
|
or props.new_tag_name in IfcGitData.data["tag_names"]
|
||||||
):
|
):
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
@@ -165,8 +172,12 @@ class AddTag(bpy.types.Operator):
|
|||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
|
|
||||||
repo = IfcGitData.data["repo"]
|
repo = IfcGitData.data["repo"]
|
||||||
core.add_tag(tool.IfcGit, repo)
|
props = tool.IfcGit.get_ifcgit_props()
|
||||||
core.refresh_revision_list(tool.IfcGit, repo, tool.Ifc)
|
item = props.ifcgit_commits[props.commit_index]
|
||||||
|
core.add_tag(tool.IfcGit, repo, item.hexsha, props.new_tag_name, props.new_tag_message)
|
||||||
|
props.new_tag_name = ""
|
||||||
|
props.new_tag_message = ""
|
||||||
|
core.refresh_revision_list(tool.IfcGit, tool.Ifc)
|
||||||
refresh()
|
refresh()
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
@@ -183,7 +194,7 @@ class DeleteTag(bpy.types.Operator):
|
|||||||
|
|
||||||
repo = IfcGitData.data["repo"]
|
repo = IfcGitData.data["repo"]
|
||||||
core.delete_tag(tool.IfcGit, repo, self.tag_name)
|
core.delete_tag(tool.IfcGit, repo, self.tag_name)
|
||||||
core.refresh_revision_list(tool.IfcGit, repo, tool.Ifc)
|
core.refresh_revision_list(tool.IfcGit, tool.Ifc)
|
||||||
refresh()
|
refresh()
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
@@ -205,8 +216,7 @@ class RefreshGit(bpy.types.Operator):
|
|||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
|
|
||||||
repo = IfcGitData.data["repo"]
|
core.refresh_revision_list(tool.IfcGit, tool.Ifc)
|
||||||
core.refresh_revision_list(tool.IfcGit, repo, tool.Ifc)
|
|
||||||
refresh()
|
refresh()
|
||||||
tool.IfcGit.decolourise()
|
tool.IfcGit.decolourise()
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
@@ -284,7 +294,7 @@ class Merge(bpy.types.Operator):
|
|||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
|
|
||||||
if core.merge_branch(tool.IfcGit, tool.Ifc, self):
|
if core.merge_branch(tool.IfcGit, tool.Ifc, self) is not False:
|
||||||
refresh()
|
refresh()
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
else:
|
else:
|
||||||
@@ -314,9 +324,9 @@ class Fetch(bpy.types.Operator):
|
|||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
props = tool.IfcGit.get_ifcgit_props()
|
props = tool.IfcGit.get_ifcgit_props()
|
||||||
repo = IfcGitData.data["repo"]
|
core.fetch(tool.IfcGit, props.select_remote)
|
||||||
remote = repo.remotes[props.select_remote]
|
core.refresh_revision_list(tool.IfcGit, tool.Ifc)
|
||||||
remote.fetch()
|
refresh()
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
@@ -336,7 +346,7 @@ class AddRemote(bpy.types.Operator):
|
|||||||
not repo
|
not repo
|
||||||
or not tool.IfcGit.is_valid_ref_format(props.remote_name)
|
or not tool.IfcGit.is_valid_ref_format(props.remote_name)
|
||||||
or not props.remote_url
|
or not props.remote_url
|
||||||
or props.remote_name in [remote.name for remote in repo.remotes]
|
or props.remote_name in IfcGitData.data["remote_names"]
|
||||||
):
|
):
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
@@ -344,8 +354,11 @@ class AddRemote(bpy.types.Operator):
|
|||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
|
|
||||||
repo = IfcGitData.data["repo"]
|
repo = IfcGitData.data["repo"]
|
||||||
core.add_remote(tool.IfcGit, repo)
|
props = tool.IfcGit.get_ifcgit_props()
|
||||||
core.refresh_revision_list(tool.IfcGit, repo, tool.Ifc)
|
core.add_remote(tool.IfcGit, repo, props.remote_name, props.remote_url)
|
||||||
|
props.remote_name = ""
|
||||||
|
props.remote_url = ""
|
||||||
|
core.refresh_revision_list(tool.IfcGit, tool.Ifc)
|
||||||
refresh()
|
refresh()
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
@@ -360,8 +373,19 @@ class DeleteRemote(bpy.types.Operator):
|
|||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
|
|
||||||
repo = IfcGitData.data["repo"]
|
repo = IfcGitData.data["repo"]
|
||||||
core.delete_remote(tool.IfcGit, repo)
|
props = tool.IfcGit.get_ifcgit_props()
|
||||||
core.refresh_revision_list(tool.IfcGit, repo, tool.Ifc)
|
remote_name = props.select_remote
|
||||||
|
if props.display_branch.startswith(remote_name + "/"):
|
||||||
|
active = IfcGitData.data["active_branch_name"]
|
||||||
|
if active:
|
||||||
|
props.display_branch = active
|
||||||
|
else:
|
||||||
|
local_branches = [b for b in IfcGitData.data["branch_names"] if "/" not in b]
|
||||||
|
if local_branches:
|
||||||
|
props.display_branch = local_branches[0]
|
||||||
|
core.delete_remote(tool.IfcGit, repo, remote_name)
|
||||||
|
tool.IfcGit.select_first_remote()
|
||||||
|
core.refresh_revision_list(tool.IfcGit, tool.Ifc)
|
||||||
refresh()
|
refresh()
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
@@ -375,8 +399,8 @@ class ObjectLog(bpy.types.Operator):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
if not (obj := context.active_object):
|
if not (obj := context.active_object) or not obj.select_get():
|
||||||
cls.poll_message_set("No Active Object")
|
cls.poll_message_set("No selected object")
|
||||||
elif not tool.Blender.get_ifc_definition_id(obj):
|
elif not tool.Blender.get_ifc_definition_id(obj):
|
||||||
cls.poll_message_set("Active Object doesn't have an IFC definition")
|
cls.poll_message_set("Active Object doesn't have an IFC definition")
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -17,28 +17,14 @@ from bonsai.bim.module.ifcgit.data import IfcGitData
|
|||||||
def git_branches(self: "IfcGitProperties", context: bpy.types.Context) -> tool.Blender.BLENDER_ENUM_ITEMS:
|
def git_branches(self: "IfcGitProperties", context: bpy.types.Context) -> tool.Blender.BLENDER_ENUM_ITEMS:
|
||||||
# NOTE "Python must keep a reference to the strings returned by
|
# NOTE "Python must keep a reference to the strings returned by
|
||||||
# the callback or Blender will misbehave or even crash"
|
# the callback or Blender will misbehave or even crash"
|
||||||
IfcGitData.data["branch_names"] = sorted([branch.name for branch in IfcGitData.data["repo"].heads])
|
# Branch list (local + remote, main first) is computed once in IfcGitData.load()
|
||||||
|
IfcGitData.make_sure_is_loaded()
|
||||||
if "main" in IfcGitData.data["branch_names"]:
|
return [(name, name, name) for name in IfcGitData.data["branch_names"]]
|
||||||
IfcGitData.data["branch_names"].remove("main")
|
|
||||||
IfcGitData.data["branch_names"] = ["main"] + IfcGitData.data["branch_names"]
|
|
||||||
|
|
||||||
if IfcGitData.data["remotes"]:
|
|
||||||
for remote in IfcGitData.data["remotes"]:
|
|
||||||
for remote_branch in remote.refs:
|
|
||||||
IfcGitData.data["branch_names"].append(remote_branch.name)
|
|
||||||
|
|
||||||
return [(myname, myname, myname) for myname in IfcGitData.data["branch_names"]]
|
|
||||||
|
|
||||||
|
|
||||||
def git_remotes(self: "IfcGitProperties", context: bpy.types.Context) -> tool.Blender.BLENDER_ENUM_ITEMS:
|
def git_remotes(self: "IfcGitProperties", context: bpy.types.Context) -> tool.Blender.BLENDER_ENUM_ITEMS:
|
||||||
IfcGitData.data["remote_names"] = sorted([remote.name for remote in IfcGitData.data["remotes"]])
|
IfcGitData.make_sure_is_loaded()
|
||||||
|
return [(name, name, name) for name in IfcGitData.data["remote_names"]]
|
||||||
if "origin" in IfcGitData.data["remote_names"]:
|
|
||||||
IfcGitData.data["remote_names"].remove("origin")
|
|
||||||
IfcGitData.data["remote_names"] = ["origin"] + IfcGitData.data["remote_names"]
|
|
||||||
|
|
||||||
return [(myname, myname, myname) for myname in IfcGitData.data["remote_names"]]
|
|
||||||
|
|
||||||
|
|
||||||
def update_revlist(self: "IfcGitProperties", context: bpy.types.Context) -> None:
|
def update_revlist(self: "IfcGitProperties", context: bpy.types.Context) -> None:
|
||||||
@@ -90,6 +76,7 @@ class IfcGitListItem(PropertyGroup):
|
|||||||
name="Commit Message",
|
name="Commit Message",
|
||||||
default="",
|
default="",
|
||||||
)
|
)
|
||||||
|
committed_date: IntProperty(name="Committed Date", default=0)
|
||||||
tags: CollectionProperty(type=IfcGitTag, name="List of revision tags")
|
tags: CollectionProperty(type=IfcGitTag, name="List of revision tags")
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@@ -98,6 +85,7 @@ class IfcGitListItem(PropertyGroup):
|
|||||||
author_name: str
|
author_name: str
|
||||||
author_email: str
|
author_email: str
|
||||||
message: str
|
message: str
|
||||||
|
committed_date: int
|
||||||
tags: bpy.types.bpy_prop_collection_idprop[IfcGitTag]
|
tags: bpy.types.bpy_prop_collection_idprop[IfcGitTag]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ class IFCGIT_PT_panel(bpy.types.Panel):
|
|||||||
if IfcGitData.data["repo"] and os.path.exists(IfcGitData.data["repo"].git_dir):
|
if IfcGitData.data["repo"] and os.path.exists(IfcGitData.data["repo"].git_dir):
|
||||||
name_ifc = IfcGitData.data["name_ifc"]
|
name_ifc = IfcGitData.data["name_ifc"]
|
||||||
row.label(text=IfcGitData.data["working_dir"], icon="SYSTEM")
|
row.label(text=IfcGitData.data["working_dir"], icon="SYSTEM")
|
||||||
if name_ifc in IfcGitData.data["untracked_files"]:
|
if IfcGitData.data["ifc_is_untracked"]:
|
||||||
row.operator(
|
row.operator(
|
||||||
"ifcgit.addfile",
|
"ifcgit.addfile",
|
||||||
text="Add '" + name_ifc + "' to repository",
|
text="Add '" + name_ifc + "' to repository",
|
||||||
@@ -216,13 +216,7 @@ class COMMIT_UL_List(bpy.types.UIList):
|
|||||||
):
|
):
|
||||||
|
|
||||||
current_revision = IfcGitData.data["current_revision"]
|
current_revision = IfcGitData.data["current_revision"]
|
||||||
|
current_hexsha = current_revision.hexsha if current_revision else None
|
||||||
# TODO Figure how this "item" can be acesse in "data.py"
|
|
||||||
# so it's possible to move the ".commit"
|
|
||||||
try:
|
|
||||||
commit = IfcGitData.data["repo"].commit(rev=item.hexsha)
|
|
||||||
except ValueError:
|
|
||||||
return
|
|
||||||
|
|
||||||
lookup = IfcGitData.data["branches_by_hexsha"]
|
lookup = IfcGitData.data["branches_by_hexsha"]
|
||||||
refs = ""
|
refs = ""
|
||||||
@@ -236,11 +230,11 @@ class COMMIT_UL_List(bpy.types.UIList):
|
|||||||
for tag in lookup[item.hexsha]:
|
for tag in lookup[item.hexsha]:
|
||||||
refs += "{" + tag.name + "} "
|
refs += "{" + tag.name + "} "
|
||||||
|
|
||||||
if commit == current_revision:
|
if item.hexsha == current_hexsha:
|
||||||
layout.label(text="[HEAD] " + refs + commit.message.split("\n")[0], icon="DECORATE_KEYFRAME")
|
layout.label(text="[HEAD] " + refs + item.message.split("\n")[0], icon="DECORATE_KEYFRAME")
|
||||||
else:
|
else:
|
||||||
layout.label(text=refs + commit.message.split("\n")[0], icon="DECORATE_ANIMATE")
|
layout.label(text=refs + item.message.split("\n")[0], icon="DECORATE_ANIMATE")
|
||||||
layout.label(text=time.strftime("%c", time.localtime(commit.committed_date)))
|
layout.label(text=time.strftime("%c", time.localtime(item.committed_date)))
|
||||||
|
|
||||||
def draw_filter(self, context, layout):
|
def draw_filter(self, context, layout):
|
||||||
|
|
||||||
|
|||||||
@@ -102,7 +102,6 @@ class MaterialsData:
|
|||||||
if (style_name := s.Name) is not None
|
if (style_name := s.Name) is not None
|
||||||
]
|
]
|
||||||
results = natsorted(results, key=lambda i: i[1])
|
results = natsorted(results, key=lambda i: i[1])
|
||||||
results.insert(0, ("-", "No Surface Style", ""))
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@@ -210,14 +210,15 @@ class AssignMaterialToSelected(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
bl_idname = "bim.assign_material_to_selected"
|
bl_idname = "bim.assign_material_to_selected"
|
||||||
bl_label = "Assign Material To Selected"
|
bl_label = "Assign Material To Selected"
|
||||||
bl_description = (
|
bl_description = (
|
||||||
"Assign currently selected material in Materials UI to the selected objects.\n\n"
|
"Assign currently selected material in Materials UI to the selected objects.\n"
|
||||||
"ALT+CLICK to assign material as a usage."
|
"Occurrences automatically get usages for layer/profile sets.\n\n"
|
||||||
|
"ALT+CLICK to assign without a usage."
|
||||||
)
|
)
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
material: bpy.props.IntProperty(name="Material IFC ID")
|
material: bpy.props.IntProperty(name="Material IFC ID")
|
||||||
assign_as_usage: bpy.props.BoolProperty(
|
should_auto_assign_usage: bpy.props.BoolProperty(
|
||||||
name="Assign Material As A Usage",
|
name="Auto Assign Usage",
|
||||||
default=False,
|
default=True,
|
||||||
options={"SKIP_SAVE"},
|
options={"SKIP_SAVE"},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -230,25 +231,19 @@ class AssignMaterialToSelected(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
if event.type == "LEFTMOUSE" and event.alt:
|
if event.type == "LEFTMOUSE" and event.alt:
|
||||||
material_class = tool.Ifc.get().by_id(self.material).is_a()
|
self.should_auto_assign_usage = False
|
||||||
if material_class not in ("IfcMaterialProfileSet", "IfcMaterialLayerSet"):
|
|
||||||
self.report({"ERROR"}, f"{material_class} cannot be assigned as a usage.")
|
|
||||||
return {"CANCELLED"}
|
|
||||||
self.assign_as_usage = True
|
|
||||||
return self.execute(context)
|
return self.execute(context)
|
||||||
|
|
||||||
def _execute(self, context):
|
def _execute(self, context):
|
||||||
material = tool.Ifc.get().by_id(self.material)
|
material = tool.Ifc.get().by_id(self.material)
|
||||||
objects = tool.Blender.get_selected_objects()
|
objects = tool.Blender.get_selected_objects()
|
||||||
material_type = material.is_a()
|
|
||||||
if self.assign_as_usage:
|
|
||||||
material_type += "Usage"
|
|
||||||
core.assign_material(
|
core.assign_material(
|
||||||
tool.Ifc,
|
tool.Ifc,
|
||||||
tool.Material,
|
tool.Material,
|
||||||
material_type=material_type,
|
material_type=material.is_a(),
|
||||||
objects=objects,
|
objects=objects,
|
||||||
material=material,
|
material=material,
|
||||||
|
should_auto_assign_usage=self.should_auto_assign_usage,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -722,7 +717,11 @@ class EnableEditingMaterialSetItem(bpy.types.Operator):
|
|||||||
self.props.material_set_item_material = str(material_set_item.Material.id())
|
self.props.material_set_item_material = str(material_set_item.Material.id())
|
||||||
|
|
||||||
self.props.material_set_item_attributes.clear()
|
self.props.material_set_item_attributes.clear()
|
||||||
bonsai.bim.helper.import_attributes(material_set_item, self.props.material_set_item_attributes)
|
bonsai.bim.helper.import_attributes(
|
||||||
|
material_set_item,
|
||||||
|
self.props.material_set_item_attributes,
|
||||||
|
callback=self.import_attributes_callback,
|
||||||
|
)
|
||||||
|
|
||||||
if material_set_item.is_a("IfcMaterialProfile"):
|
if material_set_item.is_a("IfcMaterialProfile"):
|
||||||
if material_set_item.Profile and material_set_item.Profile.ProfileName:
|
if material_set_item.Profile and material_set_item.Profile.ProfileName:
|
||||||
@@ -730,6 +729,29 @@ class EnableEditingMaterialSetItem(bpy.types.Operator):
|
|||||||
|
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
def import_attributes_callback(
|
||||||
|
self, name: str, prop: Union["Attribute", None], data: dict[str, Any]
|
||||||
|
) -> None | Literal[True]:
|
||||||
|
if data["type"] != "IfcMaterialLayer" or name != "IsVentilated" or not prop:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Keep null semantics unchanged on export, but avoid an empty UI selection.
|
||||||
|
prop.data_type = "enum"
|
||||||
|
prop.special_type = "LOGICAL"
|
||||||
|
prop.enum_items = json.dumps(("TRUE", "FALSE", "UNKNOWN"))
|
||||||
|
|
||||||
|
value = data[name]
|
||||||
|
if value == "UNKNOWN":
|
||||||
|
prop.enum_value = "UNKNOWN"
|
||||||
|
elif value is None:
|
||||||
|
# Keep visible default as FALSE, but preserve null semantics on save.
|
||||||
|
prop.enum_value = "FALSE"
|
||||||
|
prop.is_null = True
|
||||||
|
else:
|
||||||
|
prop.enum_value = "TRUE" if value else "FALSE"
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
class DisableEditingMaterialSetItem(bpy.types.Operator):
|
class DisableEditingMaterialSetItem(bpy.types.Operator):
|
||||||
bl_idname = "bim.disable_editing_material_set_item"
|
bl_idname = "bim.disable_editing_material_set_item"
|
||||||
|
|||||||
@@ -118,12 +118,17 @@ class BIM_PT_materials(Panel):
|
|||||||
row.operator("bim.edit_material", text="Save Material", icon="CHECKMARK").material = ifc_definition_id
|
row.operator("bim.edit_material", text="Save Material", icon="CHECKMARK").material = ifc_definition_id
|
||||||
row.operator("bim.disable_editing_material", text="", icon="CANCEL")
|
row.operator("bim.disable_editing_material", text="", icon="CANCEL")
|
||||||
elif self.props.editing_material_type == "STYLE":
|
elif self.props.editing_material_type == "STYLE":
|
||||||
row = self.layout.row(align=True)
|
if MaterialsData.data["styles"]:
|
||||||
row.prop(self.props, "contexts", text="")
|
row = self.layout.row(align=True)
|
||||||
prop_with_search(row, self.props, "styles", text="")
|
row.prop(self.props, "contexts", text="")
|
||||||
row = self.layout.row(align=True)
|
prop_with_search(row, self.props, "styles", text="")
|
||||||
row.operator("bim.edit_material_style", text="Assign Style", icon="CHECKMARK")
|
row = self.layout.row(align=True)
|
||||||
row.operator("bim.disable_editing_material", text="", icon="CANCEL")
|
row.operator("bim.edit_material_style", text="Assign Style", icon="CHECKMARK")
|
||||||
|
row.operator("bim.disable_editing_material", text="", icon="CANCEL")
|
||||||
|
else:
|
||||||
|
row = self.layout.row(align=True)
|
||||||
|
row.label(text="No Styles Found")
|
||||||
|
row.operator("bim.disable_editing_material", text="", icon="CANCEL")
|
||||||
|
|
||||||
|
|
||||||
class BIM_PT_object_material(Panel):
|
class BIM_PT_object_material(Panel):
|
||||||
|
|||||||
@@ -21,6 +21,11 @@ import bpy
|
|||||||
from . import operator, prop, ui
|
from . import operator, prop, ui
|
||||||
|
|
||||||
classes = (
|
classes = (
|
||||||
|
operator.ImportQuickFavorites,
|
||||||
|
operator.RemoveQuickFavoritesItem,
|
||||||
|
operator.MoveQuickFavoritesItem,
|
||||||
|
operator.AddQuickFavoritesItem,
|
||||||
|
operator.ConfirmQuickFavoriteOperator,
|
||||||
operator.DrawSystemArrows,
|
operator.DrawSystemArrows,
|
||||||
operator.GetConnectedSystemElements,
|
operator.GetConnectedSystemElements,
|
||||||
operator.IfcSverchokUseBonsaiFile,
|
operator.IfcSverchokUseBonsaiFile,
|
||||||
@@ -28,8 +33,12 @@ classes = (
|
|||||||
operator.SetOverrideColour,
|
operator.SetOverrideColour,
|
||||||
operator.SnapSpacesTogether,
|
operator.SnapSpacesTogether,
|
||||||
operator.SplitAlongEdge,
|
operator.SplitAlongEdge,
|
||||||
|
prop.QuickFavoriteEnumItem,
|
||||||
|
prop.QuickFavoriteProperty,
|
||||||
|
prop.QuickFavoritesItem,
|
||||||
prop.BIMMiscProperties,
|
prop.BIMMiscProperties,
|
||||||
ui.BIM_PT_misc_utilities,
|
ui.BIM_PT_misc_utilities,
|
||||||
|
ui.BIM_PT_quick_favorites_manager,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
# Bonsai - OpenBIM Blender Add-on
|
||||||
|
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||||
|
#
|
||||||
|
# This file is part of Bonsai.
|
||||||
|
#
|
||||||
|
# Bonsai is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Bonsai is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
|
||||||
|
|
||||||
|
def refresh() -> None:
|
||||||
|
QuickFavoritesData.is_loaded = False
|
||||||
|
|
||||||
|
|
||||||
|
class QuickFavoritesData:
|
||||||
|
data: dict[str, Any] = {}
|
||||||
|
is_loaded = False
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def load(cls) -> None:
|
||||||
|
cls.data = {
|
||||||
|
"operators": cls.operators(),
|
||||||
|
}
|
||||||
|
cls.is_loaded = True
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def operators(cls) -> list[str]:
|
||||||
|
items: list[str] = []
|
||||||
|
for module_name in dir(bpy.ops):
|
||||||
|
module = getattr(bpy.ops, module_name)
|
||||||
|
for op_name in dir(module):
|
||||||
|
op = getattr(module, op_name)
|
||||||
|
bl_label = op.get_rna_type().name
|
||||||
|
items.append(f"{module_name}.{op_name} - {bl_label}")
|
||||||
|
return items
|
||||||
@@ -30,6 +30,9 @@ import bonsai.core.misc as core
|
|||||||
import bonsai.core.root
|
import bonsai.core.root
|
||||||
import bonsai.tool as tool
|
import bonsai.tool as tool
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from bpy.stub_internal import rna_enums
|
||||||
|
|
||||||
|
|
||||||
class SetOverrideColour(bpy.types.Operator):
|
class SetOverrideColour(bpy.types.Operator):
|
||||||
bl_idname = "bim.set_override_colour"
|
bl_idname = "bim.set_override_colour"
|
||||||
@@ -41,10 +44,11 @@ class SetOverrideColour(bpy.types.Operator):
|
|||||||
return context.selected_objects
|
return context.selected_objects
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
|
props = tool.Misc.get_misc_props()
|
||||||
for obj in context.selected_objects:
|
for obj in context.selected_objects:
|
||||||
obj.color = context.scene.BIMMiscProperties.override_colour
|
obj.color = props.override_colour
|
||||||
area = next(area for area in context.screen.areas if area.type == "VIEW_3D")
|
assert (space := tool.Blender.get_view3d_space())
|
||||||
area.spaces[0].shading.color_type = "OBJECT"
|
space.shading.color_type = "OBJECT"
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
@@ -351,6 +355,151 @@ class DrawSystemArrows(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
return matrix
|
return matrix
|
||||||
|
|
||||||
|
|
||||||
|
class ConfirmQuickFavoriteOperator(bpy.types.Operator):
|
||||||
|
bl_idname = "bim.confirm_quick_favorite_operator"
|
||||||
|
bl_label = "Confirm Operator"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
index: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
index: int
|
||||||
|
|
||||||
|
def execute(self, context) -> set["rna_enums.OperatorReturnItems"]:
|
||||||
|
props = tool.Misc.get_misc_props()
|
||||||
|
fav = props.quick_favorites[self.index]
|
||||||
|
rna = fav.get_searched_operator()
|
||||||
|
|
||||||
|
if rna is None:
|
||||||
|
self.report({"INFO"}, "No operator entered for search.")
|
||||||
|
return {"CANCELLED"}
|
||||||
|
|
||||||
|
fav.operator_id = tool.Blender.operator_idname_to_py(rna.identifier)
|
||||||
|
fav.label = rna.name
|
||||||
|
fav.properties.clear()
|
||||||
|
has_skipped = False
|
||||||
|
for p in rna.properties:
|
||||||
|
# skip silently, e.g. `rna_type` is a PointerProperty
|
||||||
|
if isinstance(p, bpy.types.PointerProperty):
|
||||||
|
continue
|
||||||
|
if isinstance(p, (bpy.types.FloatProperty, bpy.types.BoolProperty, bpy.types.IntProperty)) and p.is_array:
|
||||||
|
print(f"Array property '{p.identifier}' is not supported, skipping.")
|
||||||
|
has_skipped = True
|
||||||
|
continue
|
||||||
|
item = fav.properties.add()
|
||||||
|
item.name = p.identifier
|
||||||
|
item.display_name = p.name
|
||||||
|
if isinstance(p, bpy.types.FloatProperty):
|
||||||
|
item.value_prop = "float_value"
|
||||||
|
item.float_value = p.default
|
||||||
|
elif isinstance(p, bpy.types.BoolProperty):
|
||||||
|
item.value_prop = "bool_value"
|
||||||
|
item.bool_value = p.default
|
||||||
|
elif isinstance(p, bpy.types.IntProperty):
|
||||||
|
item.value_prop = "int_value"
|
||||||
|
item.int_value = p.default
|
||||||
|
elif isinstance(p, bpy.types.EnumProperty):
|
||||||
|
item.value_prop = "enum_value"
|
||||||
|
item.set_enum_items([(e.identifier, e.name, e.description) for e in p.enum_items])
|
||||||
|
item.enum_value = p.default
|
||||||
|
elif isinstance(p, bpy.types.StringProperty):
|
||||||
|
item.value_prop = "string_value"
|
||||||
|
item.string_value = p.default
|
||||||
|
else:
|
||||||
|
print(f"Unhandled property type {type(p).__name__} for '{p.identifier}', skipping.")
|
||||||
|
has_skipped = True
|
||||||
|
if has_skipped:
|
||||||
|
self.report({"WARNING"}, "Some properties were skipped, see the system console for details.")
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
class ImportQuickFavorites(bpy.types.Operator):
|
||||||
|
bl_idname = "bim.import_quick_favorites"
|
||||||
|
bl_label = "Import Quick Favorites"
|
||||||
|
bl_description = "Import operators from Blender's Quick Favorites menu, including their configured properties"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
|
def execute(self, context) -> set["rna_enums.OperatorReturnItems"]:
|
||||||
|
props = tool.Misc.get_misc_props()
|
||||||
|
props.quick_favorites.clear()
|
||||||
|
|
||||||
|
has_missing_props = False
|
||||||
|
for i, qf in enumerate(tool.Misc.QuickFavorites.get_quick_favorites()):
|
||||||
|
fav = props.quick_favorites.add()
|
||||||
|
fav.label = qf.ui_name
|
||||||
|
fav.search = qf.op_idname_py
|
||||||
|
bpy.ops.bim.confirm_quick_favorite_operator(index=i)
|
||||||
|
fav.label = qf.ui_name or fav.label
|
||||||
|
|
||||||
|
for prop in fav.properties:
|
||||||
|
prop.is_active = prop.name in qf.props
|
||||||
|
|
||||||
|
for key, value in qf.props.items():
|
||||||
|
if key not in fav.properties:
|
||||||
|
print(f"Property '{key}' not found in operator '{qf.op_idname_py}'.")
|
||||||
|
has_missing_props = True
|
||||||
|
continue
|
||||||
|
item = fav.properties[key]
|
||||||
|
item.set_value(value)
|
||||||
|
|
||||||
|
if has_missing_props:
|
||||||
|
self.report(
|
||||||
|
{"WARNING"}, "Some properties were not found during import, see the system console for details."
|
||||||
|
)
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
class MoveQuickFavoritesItem(bpy.types.Operator):
|
||||||
|
bl_idname = "bim.move_quick_favorites_item"
|
||||||
|
bl_label = "Move Quick Favorites Item"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
index: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
|
||||||
|
direction: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
|
||||||
|
items=[("UP", "Up", ""), ("DOWN", "Down", "")]
|
||||||
|
)
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
index: int
|
||||||
|
direction: Literal["UP", "DOWN"]
|
||||||
|
|
||||||
|
def execute(self, context) -> set["rna_enums.OperatorReturnItems"]:
|
||||||
|
props = tool.Misc.get_misc_props()
|
||||||
|
total = len(props.quick_favorites)
|
||||||
|
new_index = self.index - 1 if self.direction == "UP" else self.index + 1
|
||||||
|
if 0 <= new_index < total:
|
||||||
|
props.quick_favorites.move(self.index, new_index)
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
class RemoveQuickFavoritesItem(bpy.types.Operator):
|
||||||
|
bl_idname = "bim.remove_quick_favorites_item"
|
||||||
|
bl_label = "Remove Quick Favorites Item"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
index: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
index: int
|
||||||
|
|
||||||
|
def execute(self, context) -> set["rna_enums.OperatorReturnItems"]:
|
||||||
|
props = tool.Misc.get_misc_props()
|
||||||
|
props.quick_favorites.remove(self.index)
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
class AddQuickFavoritesItem(bpy.types.Operator):
|
||||||
|
bl_idname = "bim.add_quick_favorites_item"
|
||||||
|
bl_label = "Add Quick Favorites Item"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
|
def execute(self, context) -> set["rna_enums.OperatorReturnItems"]:
|
||||||
|
props = tool.Misc.get_misc_props()
|
||||||
|
fav = props.quick_favorites.add()
|
||||||
|
fav.search = "bim.select_query_elements"
|
||||||
|
index = len(props.quick_favorites) - 1
|
||||||
|
bpy.ops.bim.confirm_quick_favorite_operator(index=index)
|
||||||
|
fav.properties["query"].string_value = "IfcWall"
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
class IfcSverchokUseBonsaiFile(bpy.types.Operator, tool.Ifc.Operator):
|
class IfcSverchokUseBonsaiFile(bpy.types.Operator, tool.Ifc.Operator):
|
||||||
bl_idname = "bim.ifcsverchok_use_bonsai_file"
|
bl_idname = "bim.ifcsverchok_use_bonsai_file"
|
||||||
bl_label = "Use Bonsai IFC File"
|
bl_label = "Use Bonsai IFC File"
|
||||||
|
|||||||
@@ -16,19 +16,140 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING, Any, Literal, cast, get_args
|
||||||
|
|
||||||
|
import bpy
|
||||||
from bpy.props import (
|
from bpy.props import (
|
||||||
|
BoolProperty,
|
||||||
|
CollectionProperty,
|
||||||
|
EnumProperty,
|
||||||
|
FloatProperty,
|
||||||
FloatVectorProperty,
|
FloatVectorProperty,
|
||||||
IntProperty,
|
IntProperty,
|
||||||
|
StringProperty,
|
||||||
)
|
)
|
||||||
from bpy.types import PropertyGroup
|
from bpy.types import PropertyGroup
|
||||||
|
|
||||||
|
from bonsai.bim.module.misc.data import QuickFavoritesData
|
||||||
|
|
||||||
|
QuickFavoriteValueType = Literal["float_value", "bool_value", "int_value", "string_value", "enum_value"]
|
||||||
|
|
||||||
|
|
||||||
|
class QuickFavoriteEnumItem(PropertyGroup):
|
||||||
|
name: StringProperty(name="Name", default="") # pyright: ignore[reportRedeclaration]
|
||||||
|
display_name: StringProperty(name="Display Name", default="") # pyright: ignore[reportRedeclaration]
|
||||||
|
description: StringProperty(name="Description", default="") # pyright: ignore[reportRedeclaration]
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
name: str
|
||||||
|
display_name: str
|
||||||
|
description: str
|
||||||
|
|
||||||
|
|
||||||
|
def get_enum_items(self: "QuickFavoriteProperty", context: bpy.types.Context | None) -> list[tuple[str, str, str]]:
|
||||||
|
return [(item.name, item.display_name, item.description) for item in self.enum_items]
|
||||||
|
|
||||||
|
|
||||||
|
class QuickFavoriteProperty(PropertyGroup):
|
||||||
|
name: StringProperty(name="Name", default="") # pyright: ignore[reportRedeclaration]
|
||||||
|
display_name: StringProperty(name="Display Name", default="") # pyright: ignore[reportRedeclaration]
|
||||||
|
value_prop: EnumProperty( # pyright: ignore[reportRedeclaration]
|
||||||
|
name="Value Prop",
|
||||||
|
items=tuple((v, v, "") for v in get_args(QuickFavoriteValueType)),
|
||||||
|
)
|
||||||
|
string_value: StringProperty(name="String Value", default="") # pyright: ignore[reportRedeclaration]
|
||||||
|
float_value: FloatProperty(name="Float Value", default=0.0) # pyright: ignore[reportRedeclaration]
|
||||||
|
int_value: IntProperty(name="Int Value", default=0) # pyright: ignore[reportRedeclaration]
|
||||||
|
bool_value: BoolProperty(name="Bool Value", default=False) # pyright: ignore[reportRedeclaration]
|
||||||
|
enum_value: EnumProperty(name="Enum Value", items=get_enum_items) # pyright: ignore[reportRedeclaration]
|
||||||
|
enum_items: CollectionProperty(type=QuickFavoriteEnumItem) # pyright: ignore[reportRedeclaration]
|
||||||
|
is_active: BoolProperty( # pyright: ignore[reportRedeclaration]
|
||||||
|
name="Is Active",
|
||||||
|
description="Only active properties will be added to the operator when invoked from Quick Favorites",
|
||||||
|
default=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
def set_value(self, value: Any) -> None:
|
||||||
|
setattr(self, self.value_prop, value)
|
||||||
|
|
||||||
|
def set_enum_items(self, items: list[tuple[str, str, str]]) -> None:
|
||||||
|
self.enum_items.clear()
|
||||||
|
for identifier, name, description in items:
|
||||||
|
item = self.enum_items.add()
|
||||||
|
item.name = identifier
|
||||||
|
item.display_name = name
|
||||||
|
item.description = description
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
name: str
|
||||||
|
display_name: str
|
||||||
|
value_prop: QuickFavoriteValueType
|
||||||
|
string_value: str
|
||||||
|
float_value: float
|
||||||
|
int_value: int
|
||||||
|
bool_value: bool
|
||||||
|
enum_value: str
|
||||||
|
enum_items: bpy.types.bpy_prop_collection_idprop[QuickFavoriteEnumItem]
|
||||||
|
is_active: bool
|
||||||
|
|
||||||
|
|
||||||
|
def get_operator_suggestions(self: "QuickFavoritesItem", context: bpy.types.Context, edit_text: str) -> list[str]:
|
||||||
|
if not QuickFavoritesData.is_loaded:
|
||||||
|
QuickFavoritesData.load()
|
||||||
|
return QuickFavoritesData.data["operators"]
|
||||||
|
|
||||||
|
|
||||||
|
class QuickFavoritesItem(PropertyGroup):
|
||||||
|
is_expanded: BoolProperty(name="Is Expanded", default=False) # pyright: ignore[reportRedeclaration]
|
||||||
|
search: StringProperty( # pyright: ignore[reportRedeclaration]
|
||||||
|
name="Search",
|
||||||
|
default="",
|
||||||
|
search=get_operator_suggestions,
|
||||||
|
# Resetting `search_options`, allowing users only to use suggestions.
|
||||||
|
search_options=set(),
|
||||||
|
)
|
||||||
|
properties: CollectionProperty(type=QuickFavoriteProperty) # pyright: ignore[reportRedeclaration]
|
||||||
|
operator_id: StringProperty( # pyright: ignore[reportRedeclaration]
|
||||||
|
name="Operator ID",
|
||||||
|
default="",
|
||||||
|
)
|
||||||
|
label: StringProperty( # pyright: ignore[reportRedeclaration]
|
||||||
|
name="Label",
|
||||||
|
description="Label that will be used in Quick Favorites for this operator",
|
||||||
|
default="",
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_searched_operator(self) -> bpy.types.Struct | None:
|
||||||
|
if not self.search:
|
||||||
|
return None
|
||||||
|
search_label = self.search
|
||||||
|
name = search_label.split(" - ", 1)[0]
|
||||||
|
module, func = name.split(".", 1)
|
||||||
|
op = getattr(getattr(bpy.ops, module), func)
|
||||||
|
rna = cast(bpy.types.Struct, op.get_rna_type())
|
||||||
|
return rna
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
is_expanded: bool
|
||||||
|
search: str
|
||||||
|
"""Internal property set when confirming results of the search field"""
|
||||||
|
properties: bpy.types.bpy_prop_collection_idprop[QuickFavoriteProperty]
|
||||||
|
operator_id: str
|
||||||
|
label: str
|
||||||
|
|
||||||
|
|
||||||
class BIMMiscProperties(PropertyGroup):
|
class BIMMiscProperties(PropertyGroup):
|
||||||
total_storeys: IntProperty(
|
total_storeys: IntProperty( # pyright: ignore[reportRedeclaration]
|
||||||
name="Total Storeys",
|
name="Total Storeys",
|
||||||
description="Number of storeys above object's storey to take into account for resizing",
|
description="Number of storeys above object's storey to take into account for resizing",
|
||||||
default=1,
|
default=1,
|
||||||
)
|
)
|
||||||
override_colour: FloatVectorProperty(
|
override_colour: FloatVectorProperty( # pyright: ignore[reportRedeclaration]
|
||||||
name="Override Colour", subtype="COLOR", default=(1, 0, 0, 1), min=0.0, max=1.0, size=4
|
name="Override Colour", subtype="COLOR", default=(1, 0, 0, 1), min=0.0, max=1.0, size=4
|
||||||
)
|
)
|
||||||
|
quick_favorites: CollectionProperty(type=QuickFavoritesItem) # pyright: ignore[reportRedeclaration]
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
total_storeys: int
|
||||||
|
override_colour: tuple[float, float, float, float]
|
||||||
|
quick_favorites: bpy.types.bpy_prop_collection_idprop[QuickFavoritesItem]
|
||||||
|
|||||||
@@ -18,6 +18,8 @@
|
|||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
|
|
||||||
|
import bonsai.tool as tool
|
||||||
|
|
||||||
|
|
||||||
class BIM_PT_misc_utilities(bpy.types.Panel):
|
class BIM_PT_misc_utilities(bpy.types.Panel):
|
||||||
bl_idname = "BIM_PT_misc_utilities"
|
bl_idname = "BIM_PT_misc_utilities"
|
||||||
@@ -30,7 +32,8 @@ class BIM_PT_misc_utilities(bpy.types.Panel):
|
|||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
props = context.scene.BIMMiscProperties
|
assert layout
|
||||||
|
props = tool.Misc.get_misc_props()
|
||||||
row = layout.split(factor=0.2, align=True)
|
row = layout.split(factor=0.2, align=True)
|
||||||
row.prop(props, "override_colour", text="")
|
row.prop(props, "override_colour", text="")
|
||||||
row.operator("bim.set_override_colour")
|
row.operator("bim.set_override_colour")
|
||||||
@@ -56,3 +59,72 @@ class BIM_PT_misc_utilities(bpy.types.Panel):
|
|||||||
row.operator("bim.disable_editing_sketch_extrusion_profile", text="", icon="CANCEL")
|
row.operator("bim.disable_editing_sketch_extrusion_profile", text="", icon="CANCEL")
|
||||||
row = layout.row()
|
row = layout.row()
|
||||||
row.operator("bim.import_plot", text="Import Plot Coordinates", icon="FILE_FOLDER")
|
row.operator("bim.import_plot", text="Import Plot Coordinates", icon="FILE_FOLDER")
|
||||||
|
|
||||||
|
|
||||||
|
class BIM_PT_quick_favorites_manager(bpy.types.Panel):
|
||||||
|
bl_idname = "BIM_PT_quick_favorites_manager"
|
||||||
|
bl_label = "Quick Favorites Manager"
|
||||||
|
bl_space_type = "PROPERTIES"
|
||||||
|
bl_region_type = "WINDOW"
|
||||||
|
bl_context = "output"
|
||||||
|
bl_options = {"DEFAULT_CLOSED"}
|
||||||
|
bl_parent_id = "BIM_PT_tab_sandbox"
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
assert layout
|
||||||
|
props = tool.Misc.get_misc_props()
|
||||||
|
|
||||||
|
row = layout.row(align=True)
|
||||||
|
row.label(text="Quick Favorites:")
|
||||||
|
row.operator("bim.add_quick_favorites_item", text="", icon="ADD")
|
||||||
|
row.operator("bim.import_quick_favorites", text="", icon="BLENDER")
|
||||||
|
op = row.operator("bim.show_description", text="", icon="INFO")
|
||||||
|
op.attr_name = "Quick Favorites Manager"
|
||||||
|
op.description = (
|
||||||
|
"Blender does not support editing Quick Favorites natively. "
|
||||||
|
"This manager allows you to load existing Quick Favorites operators, "
|
||||||
|
"configure their properties and labels, and re-add them to the menu with customized settings."
|
||||||
|
)
|
||||||
|
|
||||||
|
for fav in props.quick_favorites:
|
||||||
|
if fav.operator_id:
|
||||||
|
row = layout.row()
|
||||||
|
op = row.operator(fav.operator_id, text=fav.label)
|
||||||
|
for item in fav.properties:
|
||||||
|
if item.is_active:
|
||||||
|
setattr(op, item.name, getattr(item, item.value_prop))
|
||||||
|
|
||||||
|
layout.separator()
|
||||||
|
|
||||||
|
for i, fav in enumerate(props.quick_favorites):
|
||||||
|
box = layout.box()
|
||||||
|
row = box.row(align=True)
|
||||||
|
row.prop(fav, "is_expanded", text="", icon="TRIA_DOWN" if fav.is_expanded else "TRIA_RIGHT", emboss=False)
|
||||||
|
row.prop(fav, "label", text="")
|
||||||
|
if i > 0:
|
||||||
|
up = row.operator("bim.move_quick_favorites_item", text="", icon="TRIA_UP")
|
||||||
|
up.index = i
|
||||||
|
up.direction = "UP"
|
||||||
|
if i < len(props.quick_favorites) - 1:
|
||||||
|
down = row.operator("bim.move_quick_favorites_item", text="", icon="TRIA_DOWN")
|
||||||
|
down.index = i
|
||||||
|
down.direction = "DOWN"
|
||||||
|
row.operator("bim.remove_quick_favorites_item", text="", icon="X").index = i
|
||||||
|
if not fav.is_expanded:
|
||||||
|
continue
|
||||||
|
row = box.row(align=True)
|
||||||
|
row.prop(fav, "search", text="")
|
||||||
|
row.operator("bim.confirm_quick_favorite_operator", text="", icon="VIEWZOOM").index = i
|
||||||
|
if not fav.operator_id:
|
||||||
|
continue
|
||||||
|
layout.separator()
|
||||||
|
if fav.properties:
|
||||||
|
box.label(text="Properties:")
|
||||||
|
prop_box = box.box()
|
||||||
|
for item in fav.properties:
|
||||||
|
row = prop_box.row(align=True)
|
||||||
|
row.prop(item, item.value_prop, text=item.display_name)
|
||||||
|
row.prop(item, "is_active", text="", icon="RADIOBUT_ON" if item.is_active else "RADIOBUT_OFF")
|
||||||
|
else:
|
||||||
|
box.label(text="No Properties.")
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import math
|
import math
|
||||||
from math import cos, radians, sin, tan
|
from math import cos, pi, radians, sin, tan
|
||||||
from typing import Any, Literal
|
from typing import Any, Literal
|
||||||
|
|
||||||
import blf
|
import blf
|
||||||
@@ -27,6 +27,10 @@ import bmesh
|
|||||||
import bpy
|
import bpy
|
||||||
import gpu
|
import gpu
|
||||||
import ifcopenshell
|
import ifcopenshell
|
||||||
|
import ifcopenshell.geom
|
||||||
|
import ifcopenshell.util.element
|
||||||
|
import ifcopenshell.util.representation
|
||||||
|
import ifcopenshell.util.unit
|
||||||
import mathutils
|
import mathutils
|
||||||
from bpy.types import SpaceView3D
|
from bpy.types import SpaceView3D
|
||||||
from bpy_extras import view3d_utils
|
from bpy_extras import view3d_utils
|
||||||
@@ -35,6 +39,7 @@ from gpu_extras.batch import batch_for_shader
|
|||||||
from gpu_extras.presets import draw_circle_2d
|
from gpu_extras.presets import draw_circle_2d
|
||||||
from mathutils import Matrix, Quaternion, Vector
|
from mathutils import Matrix, Quaternion, Vector
|
||||||
|
|
||||||
|
import bonsai.core.geometry
|
||||||
import bonsai.tool as tool
|
import bonsai.tool as tool
|
||||||
from bonsai.bim.module.drawing.helper import format_distance
|
from bonsai.bim.module.drawing.helper import format_distance
|
||||||
|
|
||||||
@@ -1566,7 +1571,7 @@ class ProductDecorator:
|
|||||||
obj_type,
|
obj_type,
|
||||||
representation,
|
representation,
|
||||||
)
|
)
|
||||||
context.view_layer.update()
|
bpy.context.view_layer.update()
|
||||||
break
|
break
|
||||||
|
|
||||||
translate_mouse = Matrix.Translation(mouse_point)
|
translate_mouse = Matrix.Translation(mouse_point)
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ def add_object(self: "BIM_OT_add_object", context: bpy.types.Context) -> None:
|
|||||||
|
|
||||||
|
|
||||||
class BIM_OT_add_object(Operator, tool.Ifc.Operator):
|
class BIM_OT_add_object(Operator, tool.Ifc.Operator):
|
||||||
bl_idname = "mesh.add_grid"
|
bl_idname = "bim.add_grid"
|
||||||
bl_label = "Grid"
|
bl_label = "Grid"
|
||||||
bl_description = "Add IfcGrid."
|
bl_description = "Add IfcGrid."
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|||||||
@@ -227,7 +227,7 @@ class FitFlowSegments(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
is_parallel21 = tool.Cad.is_x(angle21, (0, 180), tolerance=0.001)
|
is_parallel21 = tool.Cad.is_x(angle21, (0, 180), tolerance=0.001)
|
||||||
is_parallel23 = tool.Cad.is_x(angle23, (0, 180), tolerance=0.001)
|
is_parallel23 = tool.Cad.is_x(angle23, (0, 180), tolerance=0.001)
|
||||||
|
|
||||||
if not all(is_parallel12, is_parallel13, is_parallel21, is_parallel23):
|
if not all([is_parallel12, is_parallel13, is_parallel21, is_parallel23]):
|
||||||
fitting_type = "WYE"
|
fitting_type = "WYE"
|
||||||
|
|
||||||
if not fitting_type:
|
if not fitting_type:
|
||||||
@@ -903,7 +903,7 @@ class MEPAddBend(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
start_segment_id: bpy.props.IntProperty(name="Start Segment Element ID", default=0)
|
start_segment_id: bpy.props.IntProperty(name="Start Segment Element ID", default=0)
|
||||||
end_segment_id: bpy.props.IntProperty(name="End Segment Element ID", default=0)
|
end_segment_id: bpy.props.IntProperty(name="End Segment Element ID", default=0)
|
||||||
radius: bpy.props.FloatProperty(
|
radius: bpy.props.FloatProperty(
|
||||||
"Bend Inner Radius", description="Bend inner radius in SI units", default=0.2, subtype="DISTANCE", min=0
|
name="Bend Inner Radius", description="Bend inner radius in SI units", default=0.2, subtype="DISTANCE", min=0
|
||||||
)
|
)
|
||||||
|
|
||||||
def _execute(self, context):
|
def _execute(self, context):
|
||||||
|
|||||||
@@ -151,29 +151,11 @@ class FilledOpeningGenerator:
|
|||||||
existing_opening_occurrence, "Model", "Body", "MODEL_VIEW"
|
existing_opening_occurrence, "Model", "Body", "MODEL_VIEW"
|
||||||
)
|
)
|
||||||
assert representation
|
assert representation
|
||||||
|
representation = ifcopenshell.util.representation.resolve_representation(representation)
|
||||||
# Check if mapped representation - PRESERVE the mapping structure
|
else:
|
||||||
if (
|
representation = self.generate_opening_from_filling(
|
||||||
representation.RepresentationType == "MappedRepresentation"
|
filling, filling_obj, opening_thickness_si=opening_thickness_si
|
||||||
and len(representation.Items) == 1
|
)
|
||||||
and representation.Items[0].is_a("IfcMappedItem")
|
|
||||||
):
|
|
||||||
# Store the existing RepresentationMap to reuse it
|
|
||||||
existing_mapping_source = representation.Items[0].MappingSource
|
|
||||||
reuse_mapped_representation = True
|
|
||||||
else:
|
|
||||||
representation = ifcopenshell.util.representation.resolve_representation(representation)
|
|
||||||
|
|
||||||
if not reuse_mapped_representation:
|
|
||||||
# Check for library template before generating from filling
|
|
||||||
template_rep = self.get_opening_template_from_type(filling)
|
|
||||||
|
|
||||||
if template_rep:
|
|
||||||
representation = template_rep
|
|
||||||
else:
|
|
||||||
representation = self.generate_opening_from_filling(
|
|
||||||
filling, filling_obj, opening_thickness_si=opening_thickness_si
|
|
||||||
)
|
|
||||||
|
|
||||||
# Create mapped representation
|
# Create mapped representation
|
||||||
if reuse_mapped_representation:
|
if reuse_mapped_representation:
|
||||||
@@ -247,109 +229,38 @@ class FilledOpeningGenerator:
|
|||||||
voided_element = opening.VoidsElements[0].RelatingBuildingElement
|
voided_element = opening.VoidsElements[0].RelatingBuildingElement
|
||||||
|
|
||||||
opening_rep = ifcopenshell.util.representation.get_representation(opening, "Model", "Body", "MODEL_VIEW")
|
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")
|
|
||||||
):
|
|
||||||
# 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"],
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
# For direct representations (non-mapped), copy them too
|
|
||||||
preserved_representation = ifcopenshell.util.element.copy_deep(
|
|
||||||
tool.Ifc.get(), opening_rep, exclude=["IfcGeometricRepresentationContext"]
|
|
||||||
)
|
|
||||||
|
|
||||||
ifcopenshell.api.geometry.unassign_representation(tool.Ifc.get(), product=opening, representation=opening_rep)
|
ifcopenshell.api.geometry.unassign_representation(tool.Ifc.get(), product=opening, representation=opening_rep)
|
||||||
ifcopenshell.api.geometry.remove_representation(tool.Ifc.get(), 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)
|
existing_opening_occurrence = self.get_existing_opening_occurrence_if_any(filling)
|
||||||
|
|
||||||
# Priority order for choosing representation:
|
|
||||||
# 1. Existing occurrence with MappedRepresentation (preserve mapping!)
|
|
||||||
# 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
|
|
||||||
reuse_mapped_representation = False
|
|
||||||
existing_mapping_source = None
|
|
||||||
|
|
||||||
if existing_opening_occurrence:
|
if existing_opening_occurrence:
|
||||||
representation = ifcopenshell.util.representation.get_representation(
|
representation = ifcopenshell.util.representation.get_representation(
|
||||||
existing_opening_occurrence, "Model", "Body", "MODEL_VIEW"
|
existing_opening_occurrence, "Model", "Body", "MODEL_VIEW"
|
||||||
)
|
)
|
||||||
|
representation = ifcopenshell.util.representation.resolve_representation(representation)
|
||||||
if (
|
mapped_representation = ifcopenshell.api.geometry.map_representation(
|
||||||
representation
|
tool.Ifc.get(), representation=representation
|
||||||
and representation.RepresentationType == "MappedRepresentation"
|
)
|
||||||
and len(representation.Items) == 1
|
ifcopenshell.api.geometry.assign_representation(
|
||||||
and representation.Items[0].is_a("IfcMappedItem")
|
tool.Ifc.get(), product=opening, representation=mapped_representation
|
||||||
):
|
)
|
||||||
# PRESERVE the mapped structure - reuse the same RepresentationMap
|
else:
|
||||||
existing_mapping_source = representation.Items[0].MappingSource
|
|
||||||
reuse_mapped_representation = True
|
|
||||||
else:
|
|
||||||
representation_to_use = ifcopenshell.util.representation.resolve_representation(representation)
|
|
||||||
|
|
||||||
if not representation_to_use and not reuse_mapped_representation:
|
|
||||||
template_rep = self.get_opening_template_from_type(filling)
|
|
||||||
if template_rep and template_rep.RepresentationType == "Tessellation":
|
|
||||||
representation_to_use = template_rep
|
|
||||||
|
|
||||||
if not representation_to_use and not reuse_mapped_representation and preserved_representation:
|
|
||||||
representation_to_use = preserved_representation
|
|
||||||
|
|
||||||
if not representation_to_use and not reuse_mapped_representation:
|
|
||||||
opening_obj = tool.Ifc.get_object(opening)
|
opening_obj = tool.Ifc.get_object(opening)
|
||||||
if opening_obj:
|
if opening_obj:
|
||||||
tool.Ifc.unlink(element=opening)
|
tool.Ifc.unlink(element=opening)
|
||||||
tool.Blender.remove_data_blocks([opening_obj], remove_unused_data=True)
|
tool.Blender.remove_data_blocks([opening_obj], remove_unused_data=True)
|
||||||
|
|
||||||
filling_obj = tool.Ifc.get_object(filling)
|
filling_obj = tool.Ifc.get_object(filling)
|
||||||
representation_to_use = self.generate_opening_from_filling(filling, filling_obj)
|
representation = self.generate_opening_from_filling(filling, filling_obj)
|
||||||
|
|
||||||
# Create the mapped representation
|
|
||||||
if reuse_mapped_representation:
|
|
||||||
# Reuse existing RepresentationMap - don't create a new one!
|
|
||||||
context = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW")
|
|
||||||
new_mapped_item = tool.Ifc.get().create_entity(
|
|
||||||
"IfcMappedItem",
|
|
||||||
MappingSource=existing_mapping_source,
|
|
||||||
MappingTarget=tool.Ifc.get().create_entity(
|
|
||||||
"IfcCartesianTransformationOperator3D",
|
|
||||||
Axis1=tool.Ifc.get().create_entity("IfcDirection", DirectionRatios=(1.0, 0.0, 0.0)),
|
|
||||||
Axis2=tool.Ifc.get().create_entity("IfcDirection", DirectionRatios=(0.0, 1.0, 0.0)),
|
|
||||||
LocalOrigin=tool.Ifc.get().create_entity("IfcCartesianPoint", Coordinates=(0.0, 0.0, 0.0)),
|
|
||||||
Scale=1.0,
|
|
||||||
Axis3=tool.Ifc.get().create_entity("IfcDirection", DirectionRatios=(0.0, 0.0, 1.0)),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
mapped_representation = tool.Ifc.get().create_entity(
|
|
||||||
"IfcShapeRepresentation",
|
|
||||||
ContextOfItems=context,
|
|
||||||
RepresentationIdentifier="Body",
|
|
||||||
RepresentationType="MappedRepresentation",
|
|
||||||
Items=[new_mapped_item],
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
mapped_representation = ifcopenshell.api.geometry.map_representation(
|
mapped_representation = ifcopenshell.api.geometry.map_representation(
|
||||||
tool.Ifc.get(), representation=representation_to_use
|
tool.Ifc.get(), representation=representation
|
||||||
|
)
|
||||||
|
ifcopenshell.api.geometry.assign_representation(
|
||||||
|
tool.Ifc.get(), product=opening, representation=mapped_representation
|
||||||
)
|
)
|
||||||
|
|
||||||
ifcopenshell.api.geometry.assign_representation(
|
# update voided object representation or all it's parts if it's an aggregate
|
||||||
tool.Ifc.get(), product=opening, representation=mapped_representation
|
|
||||||
)
|
|
||||||
|
|
||||||
# update voided object representation...
|
|
||||||
voided_elements = ifcopenshell.util.element.get_parts(voided_element) or [voided_element]
|
voided_elements = ifcopenshell.util.element.get_parts(voided_element) or [voided_element]
|
||||||
for voided_element in voided_elements:
|
for voided_element in voided_elements:
|
||||||
voided_obj = tool.Ifc.get_object(voided_element)
|
voided_obj = tool.Ifc.get_object(voided_element)
|
||||||
@@ -363,36 +274,6 @@ class FilledOpeningGenerator:
|
|||||||
representation=representation,
|
representation=representation,
|
||||||
)
|
)
|
||||||
|
|
||||||
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"]
|
|
||||||
)
|
|
||||||
return copied
|
|
||||||
except:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def generate_opening_from_filling(
|
def generate_opening_from_filling(
|
||||||
self,
|
self,
|
||||||
filling: ifcopenshell.entity_instance,
|
filling: ifcopenshell.entity_instance,
|
||||||
@@ -659,6 +540,16 @@ class AddBoolean(Operator, tool.Ifc.Operator):
|
|||||||
booleans = ifcopenshell.api.geometry.add_boolean(tool.Ifc.get(), first_item, second_items, props.operator)
|
booleans = ifcopenshell.api.geometry.add_boolean(tool.Ifc.get(), first_item, second_items, props.operator)
|
||||||
|
|
||||||
rep_obj = tool.Geometry.get_geometry_props().representation_obj
|
rep_obj = tool.Geometry.get_geometry_props().representation_obj
|
||||||
|
if booleans:
|
||||||
|
# Users typically select two top-level items and expect the
|
||||||
|
# operand to be absorbed into the boolean, not remain as a
|
||||||
|
# standalone item alongside it.
|
||||||
|
representation = tool.Geometry.get_active_representation(rep_obj)
|
||||||
|
representation = ifcopenshell.util.representation.resolve_representation(representation)
|
||||||
|
second_items_set = set(second_items)
|
||||||
|
new_items = [i for i in representation.Items if i not in second_items_set]
|
||||||
|
if new_items:
|
||||||
|
representation.Items = new_items
|
||||||
rep_element = tool.Ifc.get_entity(rep_obj)
|
rep_element = tool.Ifc.get_entity(rep_obj)
|
||||||
tool.Model.mark_manual_booleans(rep_element, booleans)
|
tool.Model.mark_manual_booleans(rep_element, booleans)
|
||||||
tool.Geometry.reload_representation(rep_obj)
|
tool.Geometry.reload_representation(rep_obj)
|
||||||
|
|||||||
@@ -421,7 +421,7 @@ class PolylineOperator:
|
|||||||
tool.Polyline.calculate_x_y_and_z(context, self.input_ui, self.tool_state)
|
tool.Polyline.calculate_x_y_and_z(context, self.input_ui, self.tool_state)
|
||||||
|
|
||||||
tool.Blender.update_viewport()
|
tool.Blender.update_viewport()
|
||||||
return {"RUNNING_MODAL"}
|
return {"RUNNING_MODAL"}
|
||||||
|
|
||||||
def set_offset(self, context: bpy.types.Context, relating_type: ifcopenshell.entity_instance) -> None:
|
def set_offset(self, context: bpy.types.Context, relating_type: ifcopenshell.entity_instance) -> None:
|
||||||
props = tool.Model.get_model_props()
|
props = tool.Model.get_model_props()
|
||||||
@@ -461,6 +461,7 @@ class PolylineOperator:
|
|||||||
self.tool_state.axis_method = None
|
self.tool_state.axis_method = None
|
||||||
self.tool_state.plane_method = None
|
self.tool_state.plane_method = None
|
||||||
self.tool_state.mode = "Mouse"
|
self.tool_state.mode = "Mouse"
|
||||||
|
tool.Raycast.clear_snap_objs()
|
||||||
self.visible_objs = tool.Raycast.get_visible_objects(context)
|
self.visible_objs = tool.Raycast.get_visible_objects(context)
|
||||||
for obj in self.visible_objs:
|
for obj in self.visible_objs:
|
||||||
if bbox_2d := tool.Raycast.get_on_screen_2d_bounding_boxes(context, obj):
|
if bbox_2d := tool.Raycast.get_on_screen_2d_bounding_boxes(context, obj):
|
||||||
|
|||||||
@@ -694,10 +694,14 @@ def generate_box(usecase_path: str, ifc_file: ifcopenshell.file, settings: dict[
|
|||||||
|
|
||||||
new_settings = settings.copy()
|
new_settings = settings.copy()
|
||||||
new_settings["context"] = box_context
|
new_settings["context"] = box_context
|
||||||
new_box = ifcopenshell.api.geometry.add_representation(ifc_file, should_run_listeners=False, **new_settings)
|
new_box = ifcopenshell.api.geometry.add_representation(
|
||||||
|
ifc_file,
|
||||||
|
should_run_listeners=False, # ty:ignore[unknown-argument]
|
||||||
|
**new_settings,
|
||||||
|
)
|
||||||
ifcopenshell.api.geometry.assign_representation(
|
ifcopenshell.api.geometry.assign_representation(
|
||||||
ifc_file,
|
ifc_file,
|
||||||
should_run_listeners=False,
|
should_run_listeners=False, # ty:ignore[unknown-argument]
|
||||||
product=product,
|
product=product,
|
||||||
representation=new_box,
|
representation=new_box,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
import copy
|
import copy
|
||||||
from math import atan2, degrees, pi, radians
|
from math import atan2, degrees, pi, radians
|
||||||
from typing import Any, Literal, Optional, Union
|
from typing import TYPE_CHECKING, Any, Literal, Optional, Union
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
import ifcopenshell
|
import ifcopenshell
|
||||||
@@ -49,7 +49,7 @@ ProfileFrom2PointsReturn = Union[dict[str, Any], None]
|
|||||||
|
|
||||||
|
|
||||||
class DumbProfileGenerator:
|
class DumbProfileGenerator:
|
||||||
def __init__(self, relating_type):
|
def __init__(self, relating_type: ifcopenshell.entity_instance):
|
||||||
self.relating_type = relating_type
|
self.relating_type = relating_type
|
||||||
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||||
|
|
||||||
@@ -201,7 +201,7 @@ class DumbProfileGenerator:
|
|||||||
|
|
||||||
|
|
||||||
class DumbProfileRegenerator:
|
class DumbProfileRegenerator:
|
||||||
def regenerate_from_profile_def(self, profile):
|
def regenerate_from_profile_def(self, profile: ifcopenshell.entity_instance) -> None:
|
||||||
self.file = tool.Ifc.get()
|
self.file = tool.Ifc.get()
|
||||||
objs = []
|
objs = []
|
||||||
if not profile:
|
if not profile:
|
||||||
@@ -221,7 +221,7 @@ class DumbProfileRegenerator:
|
|||||||
for element in self.get_element_types_using_profile(profile):
|
for element in self.get_element_types_using_profile(profile):
|
||||||
tool.Model.mark_thumbnail_for_update(element)
|
tool.Model.mark_thumbnail_for_update(element)
|
||||||
|
|
||||||
def regenerate_from_profile(self, usecase_path, ifc_file, settings):
|
def regenerate_from_profile(self, usecase_path: str, ifc_file: ifcopenshell.file, settings: dict[str, Any]) -> None:
|
||||||
self.file = ifc_file
|
self.file = ifc_file
|
||||||
objs = []
|
objs = []
|
||||||
profile = settings["profile"].Profile
|
profile = settings["profile"].Profile
|
||||||
@@ -233,7 +233,7 @@ class DumbProfileRegenerator:
|
|||||||
objs.append(obj)
|
objs.append(obj)
|
||||||
DumbProfileRecalculator().recalculate(objs)
|
DumbProfileRecalculator().recalculate(objs)
|
||||||
|
|
||||||
def get_elements_using_profile(self, profile):
|
def get_elements_using_profile(self, profile: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
|
||||||
results = []
|
results = []
|
||||||
profile_sets = [
|
profile_sets = [
|
||||||
mp.ToMaterialProfileSet[0] for mp in self.file.get_inverse(profile) if mp.is_a("IfcMaterialProfile")
|
mp.ToMaterialProfileSet[0] for mp in self.file.get_inverse(profile) if mp.is_a("IfcMaterialProfile")
|
||||||
@@ -252,7 +252,9 @@ class DumbProfileRegenerator:
|
|||||||
results.extend(rel.RelatedObjects)
|
results.extend(rel.RelatedObjects)
|
||||||
return results
|
return results
|
||||||
|
|
||||||
def get_element_types_using_profile(self, profile):
|
def get_element_types_using_profile(
|
||||||
|
self, profile: ifcopenshell.entity_instance
|
||||||
|
) -> list[ifcopenshell.entity_instance]:
|
||||||
results = []
|
results = []
|
||||||
profile_sets = [
|
profile_sets = [
|
||||||
mp.ToMaterialProfileSet[0] for mp in self.file.get_inverse(profile) if mp.is_a("IfcMaterialProfile")
|
mp.ToMaterialProfileSet[0] for mp in self.file.get_inverse(profile) if mp.is_a("IfcMaterialProfile")
|
||||||
@@ -269,12 +271,18 @@ class ExtendProfile(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
bl_idname = "bim.extend_profile"
|
bl_idname = "bim.extend_profile"
|
||||||
bl_label = "Extend Profile"
|
bl_label = "Extend Profile"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
join_type: bpy.props.StringProperty()
|
join_type: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
|
||||||
|
items=[("-", "Unjoin", ""), ("L", "L", ""), ("V", "V", ""), ("T", "T", "")],
|
||||||
|
default="-",
|
||||||
|
)
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
join_type: Literal["-", "L", "V", "T"]
|
||||||
|
|
||||||
def _execute(self, context):
|
def _execute(self, context):
|
||||||
selected_objs = context.selected_objects
|
selected_objs = context.selected_objects
|
||||||
joiner = DumbProfileJoiner()
|
joiner = DumbProfileJoiner()
|
||||||
if not self.join_type:
|
if self.join_type == "-":
|
||||||
for obj in selected_objs:
|
for obj in selected_objs:
|
||||||
joiner.unjoin(obj)
|
joiner.unjoin(obj)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
@@ -626,11 +634,15 @@ class DumbProfileJoiner:
|
|||||||
if connection1 == "ATEND":
|
if connection1 == "ATEND":
|
||||||
if tool.Cad.is_x(abs(xy_angle), (0, 90, 180), tolerance=0.001) and is_orthogonal:
|
if tool.Cad.is_x(abs(xy_angle), (0, 90, 180), tolerance=0.001) and is_orthogonal:
|
||||||
plane = self.get_profile_plane(profile2, furthest_plane)
|
plane = self.get_profile_plane(profile2, furthest_plane)
|
||||||
intersect = mathutils.geometry.intersect_line_plane(*axis1, plane.translation, plane.col[2].to_3d())
|
intersect = mathutils.geometry.intersect_line_plane(
|
||||||
|
axis1[0], axis1[1], plane.translation, plane.col[2].to_3d()
|
||||||
|
)
|
||||||
self.body[1] = intersect
|
self.body[1] = intersect
|
||||||
else:
|
else:
|
||||||
plane = self.get_profile_plane(profile2, furthest_plane, z_inwards=False)
|
plane = self.get_profile_plane(profile2, furthest_plane, z_inwards=False)
|
||||||
intersect = mathutils.geometry.intersect_line_plane(*axis1, plane.translation, plane.col[2].to_3d())
|
intersect = mathutils.geometry.intersect_line_plane(
|
||||||
|
axis1[0], axis1[1], plane.translation, plane.col[2].to_3d()
|
||||||
|
)
|
||||||
max_dim = self.get_max_bound_box_dimension(profile1)
|
max_dim = self.get_max_bound_box_dimension(profile1)
|
||||||
self.body[1] = intersect + profile1.matrix_world.to_quaternion() @ Vector((0, 0, max_dim))
|
self.body[1] = intersect + profile1.matrix_world.to_quaternion() @ Vector((0, 0, max_dim))
|
||||||
|
|
||||||
@@ -673,11 +685,15 @@ class DumbProfileJoiner:
|
|||||||
elif connection1 == "ATSTART":
|
elif connection1 == "ATSTART":
|
||||||
if tool.Cad.is_x(abs(xy_angle), (0, 90, 180), tolerance=0.001) and is_orthogonal:
|
if tool.Cad.is_x(abs(xy_angle), (0, 90, 180), tolerance=0.001) and is_orthogonal:
|
||||||
plane = self.get_profile_plane(profile2, furthest_plane)
|
plane = self.get_profile_plane(profile2, furthest_plane)
|
||||||
intersect = mathutils.geometry.intersect_line_plane(*axis1, plane.translation, plane.col[2].to_3d())
|
intersect = mathutils.geometry.intersect_line_plane(
|
||||||
|
axis1[0], axis1[1], plane.translation, plane.col[2].to_3d()
|
||||||
|
)
|
||||||
self.body[0] = intersect
|
self.body[0] = intersect
|
||||||
else:
|
else:
|
||||||
plane = self.get_profile_plane(profile2, furthest_plane, z_inwards=False)
|
plane = self.get_profile_plane(profile2, furthest_plane, z_inwards=False)
|
||||||
intersect = mathutils.geometry.intersect_line_plane(*axis1, plane.translation, plane.col[2].to_3d())
|
intersect = mathutils.geometry.intersect_line_plane(
|
||||||
|
axis1[0], axis1[1], plane.translation, plane.col[2].to_3d()
|
||||||
|
)
|
||||||
max_dim = self.get_max_bound_box_dimension(profile1)
|
max_dim = self.get_max_bound_box_dimension(profile1)
|
||||||
self.body[0] = intersect - profile1.matrix_world.to_quaternion() @ Vector((0, 0, max_dim))
|
self.body[0] = intersect - profile1.matrix_world.to_quaternion() @ Vector((0, 0, max_dim))
|
||||||
|
|
||||||
@@ -721,7 +737,9 @@ class DumbProfileJoiner:
|
|||||||
if connection1 == "ATEND":
|
if connection1 == "ATEND":
|
||||||
if tool.Cad.is_x(abs(xy_angle), (0, 90, 180), tolerance=0.001) and is_orthogonal:
|
if tool.Cad.is_x(abs(xy_angle), (0, 90, 180), tolerance=0.001) and is_orthogonal:
|
||||||
plane = self.get_profile_plane(profile2, furthest_plane if is_relating else closest_plane)
|
plane = self.get_profile_plane(profile2, furthest_plane if is_relating else closest_plane)
|
||||||
intersect = mathutils.geometry.intersect_line_plane(*axis1, plane.translation, plane.col[2].to_3d())
|
intersect = mathutils.geometry.intersect_line_plane(
|
||||||
|
axis1[0], axis1[1], plane.translation, plane.col[2].to_3d()
|
||||||
|
)
|
||||||
self.body[1] = intersect
|
self.body[1] = intersect
|
||||||
else:
|
else:
|
||||||
plane = self.get_profile_plane(
|
plane = self.get_profile_plane(
|
||||||
@@ -729,7 +747,9 @@ class DumbProfileJoiner:
|
|||||||
furthest_plane if is_relating else closest_plane,
|
furthest_plane if is_relating else closest_plane,
|
||||||
z_inwards=False if is_relating else True,
|
z_inwards=False if is_relating else True,
|
||||||
)
|
)
|
||||||
intersect = mathutils.geometry.intersect_line_plane(*axis1, plane.translation, plane.col[2].to_3d())
|
intersect = mathutils.geometry.intersect_line_plane(
|
||||||
|
axis1[0], axis1[1], plane.translation, plane.col[2].to_3d()
|
||||||
|
)
|
||||||
max_dim = self.get_max_bound_box_dimension(profile1)
|
max_dim = self.get_max_bound_box_dimension(profile1)
|
||||||
self.body[1] = intersect + profile1.matrix_world.to_quaternion() @ Vector((0, 0, max_dim))
|
self.body[1] = intersect + profile1.matrix_world.to_quaternion() @ Vector((0, 0, max_dim))
|
||||||
self.clippings.append(
|
self.clippings.append(
|
||||||
@@ -742,7 +762,9 @@ class DumbProfileJoiner:
|
|||||||
elif connection1 == "ATSTART":
|
elif connection1 == "ATSTART":
|
||||||
if tool.Cad.is_x(abs(xy_angle), (0, 90, 180), tolerance=0.001) and is_orthogonal:
|
if tool.Cad.is_x(abs(xy_angle), (0, 90, 180), tolerance=0.001) and is_orthogonal:
|
||||||
plane = self.get_profile_plane(profile2, furthest_plane if is_relating else closest_plane)
|
plane = self.get_profile_plane(profile2, furthest_plane if is_relating else closest_plane)
|
||||||
intersect = mathutils.geometry.intersect_line_plane(*axis1, plane.translation, plane.col[2].to_3d())
|
intersect = mathutils.geometry.intersect_line_plane(
|
||||||
|
axis1[0], axis1[1], plane.translation, plane.col[2].to_3d()
|
||||||
|
)
|
||||||
self.body[0] = intersect
|
self.body[0] = intersect
|
||||||
else:
|
else:
|
||||||
plane = self.get_profile_plane(
|
plane = self.get_profile_plane(
|
||||||
@@ -750,7 +772,9 @@ class DumbProfileJoiner:
|
|||||||
furthest_plane if is_relating else closest_plane,
|
furthest_plane if is_relating else closest_plane,
|
||||||
z_inwards=False if is_relating else True,
|
z_inwards=False if is_relating else True,
|
||||||
)
|
)
|
||||||
intersect = mathutils.geometry.intersect_line_plane(*axis1, plane.translation, plane.col[2].to_3d())
|
intersect = mathutils.geometry.intersect_line_plane(
|
||||||
|
axis1[0], axis1[1], plane.translation, plane.col[2].to_3d()
|
||||||
|
)
|
||||||
max_dim = self.get_max_bound_box_dimension(profile1)
|
max_dim = self.get_max_bound_box_dimension(profile1)
|
||||||
self.body[0] = intersect - profile1.matrix_world.to_quaternion() @ Vector((0, 0, max_dim))
|
self.body[0] = intersect - profile1.matrix_world.to_quaternion() @ Vector((0, 0, max_dim))
|
||||||
self.clippings.append(
|
self.clippings.append(
|
||||||
|
|||||||
@@ -733,6 +733,9 @@ class BIMStairProperties(PropertyGroup):
|
|||||||
class BIMSverchokProperties(PropertyGroup):
|
class BIMSverchokProperties(PropertyGroup):
|
||||||
node_group: bpy.props.PointerProperty(name="Node Group", type=NodeTree)
|
node_group: bpy.props.PointerProperty(name="Node Group", type=NodeTree)
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
node_group: bpy.types.NodeTree | None
|
||||||
|
|
||||||
|
|
||||||
def window_type_prop_update(self, context):
|
def window_type_prop_update(self, context):
|
||||||
number_of_panels, panels_data = self.window_types_panels[self.window_type]
|
number_of_panels, panels_data = self.window_types_panels[self.window_type]
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ import bonsai.tool as tool
|
|||||||
|
|
||||||
def update_sverchok_modifier(context):
|
def update_sverchok_modifier(context):
|
||||||
obj = context.active_object
|
obj = context.active_object
|
||||||
props = obj.BIMSverchokProperties
|
props = tool.Model.get_sverchok_props(obj)
|
||||||
element = tool.Ifc.get_entity(obj)
|
element = tool.Ifc.get_entity(obj)
|
||||||
psets = ifcopenshell.util.element.get_psets(element)
|
psets = ifcopenshell.util.element.get_psets(element)
|
||||||
pset = psets.get("BBIM_Sverchok", None)
|
pset = psets.get("BBIM_Sverchok", None)
|
||||||
@@ -69,10 +69,10 @@ class CreateNewSverchokGraph(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
bl_options = {"REGISTER"}
|
bl_options = {"REGISTER"}
|
||||||
|
|
||||||
def _execute(self, context):
|
def _execute(self, context):
|
||||||
import sverchok
|
import sverchok.ui.sv_temporal_viewers
|
||||||
|
|
||||||
obj = context.active_object
|
obj = context.active_object
|
||||||
props = obj.BIMSverchokProperties
|
props = tool.Model.get_sverchok_props(obj)
|
||||||
|
|
||||||
node_group = bpy.data.node_groups.new("IfcNodeTree", type="SverchCustomTreeType")
|
node_group = bpy.data.node_groups.new("IfcNodeTree", type="SverchCustomTreeType")
|
||||||
plane = node_group.nodes.new(type="SvPlaneNodeMk3")
|
plane = node_group.nodes.new(type="SvPlaneNodeMk3")
|
||||||
@@ -96,7 +96,7 @@ class DeleteSverchokGraph(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
def _execute(self, context):
|
def _execute(self, context):
|
||||||
obj = context.active_object
|
obj = context.active_object
|
||||||
element = tool.Ifc.get_entity(obj)
|
element = tool.Ifc.get_entity(obj)
|
||||||
props = obj.BIMSverchokProperties
|
props = tool.Model.get_sverchok_props(obj)
|
||||||
bpy.data.node_groups.remove(props.node_group)
|
bpy.data.node_groups.remove(props.node_group)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
@@ -113,7 +113,8 @@ class UpdateDataFromSverchok(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
bl_options = {"REGISTER"}
|
bl_options = {"REGISTER"}
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
if not context.active_object.BIMSverchokProperties.node_group:
|
props = tool.Model.get_sverchok_props(context.active_object)
|
||||||
|
if not props.node_group:
|
||||||
return context.window_manager.invoke_props_dialog(self)
|
return context.window_manager.invoke_props_dialog(self)
|
||||||
return self._execute(context)
|
return self._execute(context)
|
||||||
|
|
||||||
@@ -124,7 +125,7 @@ class UpdateDataFromSverchok(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
def _execute(self, context):
|
def _execute(self, context):
|
||||||
obj = context.active_object
|
obj = context.active_object
|
||||||
element = tool.Ifc.get_entity(obj)
|
element = tool.Ifc.get_entity(obj)
|
||||||
props = obj.BIMSverchokProperties
|
props = tool.Model.get_sverchok_props(obj)
|
||||||
node_group = props.node_group
|
node_group = props.node_group
|
||||||
|
|
||||||
if node_group:
|
if node_group:
|
||||||
@@ -192,11 +193,11 @@ class ImportSverchokGraph(bpy.types.Operator, tool.Ifc.Operator, ImportHelper):
|
|||||||
filename_ext = ".json"
|
filename_ext = ".json"
|
||||||
|
|
||||||
def _execute(self, context):
|
def _execute(self, context):
|
||||||
import sverchok
|
import sverchok.utils.sv_json_import
|
||||||
|
|
||||||
importer = sverchok.utils.sv_json_import.JSONImporter.init_from_path(self.filepath)
|
importer = sverchok.utils.sv_json_import.JSONImporter.init_from_path(self.filepath)
|
||||||
obj = context.active_object
|
obj = context.active_object
|
||||||
props = obj.BIMSverchokProperties
|
props = tool.Model.get_sverchok_props(obj)
|
||||||
|
|
||||||
node_group = context.scene.io_panel_properties.import_tree
|
node_group = context.scene.io_panel_properties.import_tree
|
||||||
if not node_group:
|
if not node_group:
|
||||||
@@ -231,10 +232,10 @@ class ExportSverchokGraph(bpy.types.Operator, tool.Ifc.Operator, ExportHelper):
|
|||||||
compress: bpy.props.BoolProperty()
|
compress: bpy.props.BoolProperty()
|
||||||
|
|
||||||
def _execute(self, context):
|
def _execute(self, context):
|
||||||
import sverchok
|
import sverchok.utils.sv_json_export
|
||||||
|
|
||||||
obj = context.active_object
|
obj = context.active_object
|
||||||
props = obj.BIMSverchokProperties
|
props = tool.Model.get_sverchok_props(obj)
|
||||||
ng = props.node_group
|
ng = props.node_group
|
||||||
destination_path = self.filepath
|
destination_path = self.filepath
|
||||||
if not destination_path.lower().endswith(".json"):
|
if not destination_path.lower().endswith(".json"):
|
||||||
@@ -273,7 +274,8 @@ class ExportSverchokGraph(bpy.types.Operator, tool.Ifc.Operator, ExportHelper):
|
|||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
graph_name = context.active_object.BIMSverchokProperties.node_group.name
|
props = tool.Model.get_sverchok_props(context.active_object)
|
||||||
|
graph_name = props.node_group.name
|
||||||
self.layout.label(text=f'Save node tree "{graph_name}" into json:')
|
self.layout.label(text=f'Save node tree "{graph_name}" into json:')
|
||||||
|
|
||||||
col = self.layout.column(heading="Options") # new syntax in >= 2.90
|
col = self.layout.column(heading="Options") # new syntax in >= 2.90
|
||||||
|
|||||||
@@ -31,11 +31,11 @@ def calculate_quantities(usecase_path, ifc_file: ifcopenshell.file, settings):
|
|||||||
return
|
return
|
||||||
task = next(e for e in ifc_file.get_inverse(element) if e.is_a("IfcTask"))
|
task = next(e for e in ifc_file.get_inverse(element) if e.is_a("IfcTask"))
|
||||||
qto = ifcopenshell.api.pset.add_qto(
|
qto = ifcopenshell.api.pset.add_qto(
|
||||||
ifc_file, should_run_listeners=False, product=task, name="Qto_TaskBaseQuantities"
|
ifc_file, should_run_listeners=False, product=task, name="Qto_TaskBaseQuantities" # ty:ignore[unknown-argument]
|
||||||
)
|
)
|
||||||
ifcopenshell.api.pset.edit_qto(
|
ifcopenshell.api.pset.edit_qto(
|
||||||
ifc_file,
|
ifc_file,
|
||||||
should_run_listeners=False,
|
should_run_listeners=False, # ty:ignore[unknown-argument]
|
||||||
qto=qto,
|
qto=qto,
|
||||||
properties={
|
properties={
|
||||||
"StandardWork": ifcopenshell.util.date.ifc2datetime(element.ScheduleDuration).days,
|
"StandardWork": ifcopenshell.util.date.ifc2datetime(element.ScheduleDuration).days,
|
||||||
|
|||||||
@@ -360,7 +360,7 @@ class BIM_PT_sverchok(bpy.types.Panel):
|
|||||||
self.layout.label(text="Requires Sverchok Add-on", icon="ERROR")
|
self.layout.label(text="Requires Sverchok Add-on", icon="ERROR")
|
||||||
return
|
return
|
||||||
|
|
||||||
props = context.active_object.BIMSverchokProperties
|
props = tool.Model.get_sverchok_props(context.active_object)
|
||||||
self.layout.prop_search(props, "node_group", bpy.data, "node_groups")
|
self.layout.prop_search(props, "node_group", bpy.data, "node_groups")
|
||||||
self.layout.operator("bim.create_new_sverchok_graph", icon="ADD")
|
self.layout.operator("bim.create_new_sverchok_graph", icon="ADD")
|
||||||
|
|
||||||
|
|||||||
@@ -1268,27 +1268,6 @@ class DumbWallJoiner:
|
|||||||
bonsai.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=wall2)
|
bonsai.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=wall2)
|
||||||
return wall2
|
return wall2
|
||||||
|
|
||||||
def join_Z(self, wall1, slab2):
|
|
||||||
element1 = tool.Ifc.get_entity(wall1)
|
|
||||||
element2 = tool.Ifc.get_entity(slab2)
|
|
||||||
|
|
||||||
for rel in element1.ConnectedFrom:
|
|
||||||
if rel.is_a() == "IfcRelConnectsElements" and rel.Description == "TOP":
|
|
||||||
ifcopenshell.api.geometry.disconnect_element(
|
|
||||||
tool.Ifc.get(),
|
|
||||||
relating_element=rel.RelatingElement,
|
|
||||||
related_element=element1,
|
|
||||||
)
|
|
||||||
|
|
||||||
ifcopenshell.api.geometry.connect_element(
|
|
||||||
tool.Ifc.get(),
|
|
||||||
relating_element=element2,
|
|
||||||
related_element=element1,
|
|
||||||
description="TOP",
|
|
||||||
)
|
|
||||||
|
|
||||||
tool.Model.recreate_wall(element1, wall1)
|
|
||||||
|
|
||||||
def set_axis(self, wall, p1, p2):
|
def set_axis(self, wall, p1, p2):
|
||||||
axis = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Plan", "Axis", "GRAPH_VIEW")
|
axis = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Plan", "Axis", "GRAPH_VIEW")
|
||||||
builder = ifcopenshell.util.shape_builder.ShapeBuilder(tool.Ifc.get())
|
builder = ifcopenshell.util.shape_builder.ShapeBuilder(tool.Ifc.get())
|
||||||
@@ -1333,29 +1312,6 @@ class DumbWallJoiner:
|
|||||||
self.set_axis(element1, p1, p2)
|
self.set_axis(element1, p1, p2)
|
||||||
tool.Model.recreate_wall(element1, wall1)
|
tool.Model.recreate_wall(element1, wall1)
|
||||||
|
|
||||||
def join_T(self, wall1: bpy.types.Object, wall2: bpy.types.Object) -> None:
|
|
||||||
element1 = tool.Ifc.get_entity(wall1)
|
|
||||||
element2 = tool.Ifc.get_entity(wall2)
|
|
||||||
axis1 = tool.Model.get_wall_axis(wall1)
|
|
||||||
axis2 = tool.Model.get_wall_axis(wall2)
|
|
||||||
intersect = tool.Cad.intersect_edges(axis1["reference"], axis2["reference"])
|
|
||||||
if intersect:
|
|
||||||
intersect, _ = intersect
|
|
||||||
else:
|
|
||||||
return
|
|
||||||
connection = "ATEND" if tool.Cad.edge_percent(intersect, axis1["reference"]) > 0.5 else "ATSTART"
|
|
||||||
|
|
||||||
ifcopenshell.api.geometry.connect_path(
|
|
||||||
tool.Ifc.get(),
|
|
||||||
related_element=element1,
|
|
||||||
relating_element=element2,
|
|
||||||
relating_connection="ATPATH",
|
|
||||||
related_connection=connection,
|
|
||||||
description="BUTT",
|
|
||||||
)
|
|
||||||
|
|
||||||
tool.Model.recreate_wall(element1, wall1, axis1["reference"], axis1["reference"])
|
|
||||||
|
|
||||||
def connect(self, obj1: bpy.types.Object, obj2: bpy.types.Object) -> None:
|
def connect(self, obj1: bpy.types.Object, obj2: bpy.types.Object) -> None:
|
||||||
wall1 = tool.Ifc.get_entity(obj1)
|
wall1 = tool.Ifc.get_entity(obj1)
|
||||||
wall2 = tool.Ifc.get_entity(obj2)
|
wall2 = tool.Ifc.get_entity(obj2)
|
||||||
|
|||||||
@@ -943,7 +943,7 @@ class EditObjectUI:
|
|||||||
if "LAYER2" in AuthoringData.data["selected_material_usages"]:
|
if "LAYER2" in AuthoringData.data["selected_material_usages"]:
|
||||||
row = cls.layout.row(align=True) if ui_context != "TOOL_HEADER" else row
|
row = cls.layout.row(align=True) if ui_context != "TOOL_HEADER" else row
|
||||||
add_layout_hotkey_operator(
|
add_layout_hotkey_operator(
|
||||||
cls.layout, "Extend To Underside", "S_E", bpy.ops.bim.extend_to_underside.__doc__, ui_context
|
cls.layout, "Extend To Underside", "S_E", bpy.ops.bim.extend_walls_to_underside.__doc__, ui_context
|
||||||
)
|
)
|
||||||
|
|
||||||
if AuthoringData.data["is_flippable_element"]:
|
if AuthoringData.data["is_flippable_element"]:
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ class NestDecorator:
|
|||||||
cls.is_installed = False
|
cls.is_installed = False
|
||||||
|
|
||||||
def dotted_line_shader(self):
|
def dotted_line_shader(self):
|
||||||
vert_out = gpu.types.GPUStageInterfaceInfo("my_interface")
|
vert_out = gpu.types.GPUStageInterfaceInfo("my_interface") # ty:ignore[too-many-positional-arguments]
|
||||||
vert_out.smooth("FLOAT", "v_ArcLength")
|
vert_out.smooth("FLOAT", "v_ArcLength")
|
||||||
|
|
||||||
shader_info = gpu.types.GPUShaderCreateInfo()
|
shader_info = gpu.types.GPUShaderCreateInfo()
|
||||||
@@ -215,8 +215,6 @@ class NestDecorator:
|
|||||||
self.draw_batch("LINES", line_z, color, [(0, 1)])
|
self.draw_batch("LINES", line_z, color, [(0, 1)])
|
||||||
else:
|
else:
|
||||||
self.draw_batch("POINTS", [location], color)
|
self.draw_batch("POINTS", [location], color)
|
||||||
# if context.scene.BIMNestProperties.in_aggregate_mode:
|
|
||||||
# return
|
|
||||||
components = ifcopenshell.util.element.get_components(tool.Ifc.get_entity(nest))
|
components = ifcopenshell.util.element.get_components(tool.Ifc.get_entity(nest))
|
||||||
components_objs = [tool.Ifc.get_object(p) for p in components]
|
components_objs = [tool.Ifc.get_object(p) for p in components]
|
||||||
components_objs.append(nest)
|
components_objs.append(nest)
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ classes = (
|
|||||||
operator.UnlinkIfc,
|
operator.UnlinkIfc,
|
||||||
operator.UnloadLink,
|
operator.UnloadLink,
|
||||||
workspace.ExploreHotkey,
|
workspace.ExploreHotkey,
|
||||||
|
operator.GenerateUVMap,
|
||||||
prop.LibraryBreadcrumb,
|
prop.LibraryBreadcrumb,
|
||||||
prop.LibraryElement,
|
prop.LibraryElement,
|
||||||
prop.FilterCategory,
|
prop.FilterCategory,
|
||||||
|
|||||||
@@ -178,9 +178,18 @@ class SelectLibraryFile(bpy.types.Operator, IFCFileSelector, ImportHelper):
|
|||||||
bl_description = (
|
bl_description = (
|
||||||
"Select an IFC file that can be used as a library.\n\nALT+click to reload the current loaded library file."
|
"Select an IFC file that can be used as a library.\n\nALT+click to reload the current loaded library file."
|
||||||
)
|
)
|
||||||
filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"})
|
filter_glob: bpy.props.StringProperty(
|
||||||
append_all: bpy.props.BoolProperty(default=False)
|
default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"}
|
||||||
use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=False)
|
) # pyright: ignore[reportRedeclaration]
|
||||||
|
append_all: bpy.props.BoolProperty(default=False) # pyright: ignore[reportRedeclaration]
|
||||||
|
use_relative_path: bpy.props.BoolProperty(
|
||||||
|
name="Use Relative Path", default=False
|
||||||
|
) # pyright: ignore[reportRedeclaration]
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
filter_glob: str
|
||||||
|
append_all: bool
|
||||||
|
use_relative_path: bool
|
||||||
|
|
||||||
reload_previous_file = False
|
reload_previous_file = False
|
||||||
|
|
||||||
@@ -558,7 +567,11 @@ class AppendEntireLibrary(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
class AppendLibraryElementByQuery(bpy.types.Operator, tool.Ifc.Operator):
|
class AppendLibraryElementByQuery(bpy.types.Operator, tool.Ifc.Operator):
|
||||||
bl_idname = "bim.append_library_element_by_query"
|
bl_idname = "bim.append_library_element_by_query"
|
||||||
bl_label = "Append Library Element By Query"
|
bl_label = "Append Library Element By Query"
|
||||||
query: bpy.props.StringProperty(name="Query")
|
|
||||||
|
query: bpy.props.StringProperty(name="Query") # pyright: ignore[reportRedeclaration]
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
query: str
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
@@ -587,9 +600,16 @@ class AppendLibraryElement(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
"Append element to the current project.\n\n"
|
"Append element to the current project.\n\n"
|
||||||
"ALT+CLICK to skip reusing materials, profiles, styles based on their name (may result in duplicates)"
|
"ALT+CLICK to skip reusing materials, profiles, styles based on their name (may result in duplicates)"
|
||||||
)
|
)
|
||||||
definition: bpy.props.IntProperty()
|
definition: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
|
||||||
prop_index: bpy.props.IntProperty()
|
prop_index: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
|
||||||
assume_unique_by_name: bpy.props.BoolProperty(name="Assume Unique By Name", default=True, options={"SKIP_SAVE"})
|
assume_unique_by_name: bpy.props.BoolProperty(
|
||||||
|
name="Assume Unique By Name", default=True, options={"SKIP_SAVE"}
|
||||||
|
) # pyright: ignore[reportRedeclaration]
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
definition: int
|
||||||
|
prop_index: int
|
||||||
|
assume_unique_by_name: bool
|
||||||
|
|
||||||
file: ifcopenshell.file
|
file: ifcopenshell.file
|
||||||
|
|
||||||
@@ -618,8 +638,6 @@ class AppendLibraryElement(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
if not element:
|
if not element:
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
if element.is_a("IfcTypeProduct"):
|
if element.is_a("IfcTypeProduct"):
|
||||||
# Store opening template from library if it exists
|
|
||||||
self.store_opening_template_from_library(element, library_file)
|
|
||||||
self.import_type_from_ifc(element, context)
|
self.import_type_from_ifc(element, context)
|
||||||
elif element.is_a("IfcProduct"):
|
elif element.is_a("IfcProduct"):
|
||||||
# NOTE: Non-types are not exposed in UI directly
|
# NOTE: Non-types are not exposed in UI directly
|
||||||
@@ -720,53 +738,6 @@ class AppendLibraryElement(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
if element.is_a("IfcSurfaceStyle") and not tool.Ifc.get_object_by_identifier(element.id()):
|
if element.is_a("IfcSurfaceStyle") and not tool.Ifc.get_object_by_identifier(element.id()):
|
||||||
ifc_importer.create_style(element)
|
ifc_importer.create_style(element)
|
||||||
|
|
||||||
def store_opening_template_from_library(
|
|
||||||
self, element: ifcopenshell.entity_instance, library_file: ifcopenshell.file
|
|
||||||
) -> None:
|
|
||||||
"""
|
|
||||||
Find an opening representation in the library and copy it to the current file
|
|
||||||
as a template. Store the template ID on the type for later retrieval.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
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")
|
|
||||||
):
|
|
||||||
|
|
||||||
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"]
|
|
||||||
)
|
|
||||||
|
|
||||||
# Store reference in type's Description
|
|
||||||
current_desc = element.Description or ""
|
|
||||||
element.Description = f"{current_desc}||BonsaiOpeningTemplate:{template_rep.id()}"
|
|
||||||
return
|
|
||||||
break
|
|
||||||
|
|
||||||
|
|
||||||
class EditProjectLibrary(bpy.types.Operator):
|
class EditProjectLibrary(bpy.types.Operator):
|
||||||
bl_idname = "bim.edit_project_library"
|
bl_idname = "bim.edit_project_library"
|
||||||
@@ -988,24 +959,28 @@ class LoadProject(bpy.types.Operator, IFCFileSelector, ImportHelper):
|
|||||||
bl_label = "Load Project"
|
bl_label = "Load Project"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
bl_description = "Load an existing IFC project"
|
bl_description = "Load an existing IFC project"
|
||||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH", options={"SKIP_SAVE"})
|
filepath: bpy.props.StringProperty(
|
||||||
filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml;*.ifcsqlite", options={"HIDDEN"})
|
subtype="FILE_PATH", options={"SKIP_SAVE"}
|
||||||
is_advanced: bpy.props.BoolProperty(
|
) # pyright: ignore[reportRedeclaration]
|
||||||
|
filter_glob: bpy.props.StringProperty(
|
||||||
|
default="*.ifc;*.ifczip;*.ifcxml;*.ifcsqlite", options={"HIDDEN"}
|
||||||
|
) # pyright: ignore[reportRedeclaration]
|
||||||
|
is_advanced: bpy.props.BoolProperty( # pyright: ignore[reportRedeclaration]
|
||||||
name="Enable Advanced Mode",
|
name="Enable Advanced Mode",
|
||||||
description="Load IFC file with advanced settings. Checking this option will skip loading IFC file and will open advanced load settings",
|
description="Load IFC file with advanced settings. Checking this option will skip loading IFC file and will open advanced load settings",
|
||||||
default=False,
|
default=False,
|
||||||
)
|
)
|
||||||
use_relative_path: bpy.props.BoolProperty(
|
use_relative_path: bpy.props.BoolProperty( # pyright: ignore[reportRedeclaration]
|
||||||
name="Use Relative Path",
|
name="Use Relative Path",
|
||||||
description="Store the IFC project path relative to the .blend file. Requires .blend file to be saved",
|
description="Store the IFC project path relative to the .blend file. Requires .blend file to be saved",
|
||||||
default=False,
|
default=False,
|
||||||
)
|
)
|
||||||
should_start_fresh_session: bpy.props.BoolProperty(
|
should_start_fresh_session: bpy.props.BoolProperty( # pyright: ignore[reportRedeclaration]
|
||||||
name="Should Start Fresh Session",
|
name="Should Start Fresh Session",
|
||||||
description="Clear current Blender session before loading IFC. Not supported with 'Use Relative Path' option",
|
description="Clear current Blender session before loading IFC. Not supported with 'Use Relative Path' option",
|
||||||
default=True,
|
default=True,
|
||||||
)
|
)
|
||||||
import_without_ifc_data: bpy.props.BoolProperty(
|
import_without_ifc_data: bpy.props.BoolProperty( # pyright: ignore[reportRedeclaration]
|
||||||
name="Import Without IFC Data",
|
name="Import Without IFC Data",
|
||||||
description=(
|
description=(
|
||||||
"Import IFC objects as Blender objects without any IFC metadata and authoring capabilities."
|
"Import IFC objects as Blender objects without any IFC metadata and authoring capabilities."
|
||||||
@@ -1013,9 +988,20 @@ class LoadProject(bpy.types.Operator, IFCFileSelector, ImportHelper):
|
|||||||
),
|
),
|
||||||
default=False,
|
default=False,
|
||||||
)
|
)
|
||||||
use_detailed_tooltip: bpy.props.BoolProperty(default=False, options={"HIDDEN"})
|
use_detailed_tooltip: bpy.props.BoolProperty(
|
||||||
|
default=False, options={"HIDDEN"}
|
||||||
|
) # pyright: ignore[reportRedeclaration]
|
||||||
filename_ext = ".ifc"
|
filename_ext = ".ifc"
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
filepath: str
|
||||||
|
filter_glob: str
|
||||||
|
is_advanced: bool
|
||||||
|
use_relative_path: bool
|
||||||
|
should_start_fresh_session: bool
|
||||||
|
import_without_ifc_data: bool
|
||||||
|
use_detailed_tooltip: bool
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def description(cls, context, properties):
|
def description(cls, context, properties):
|
||||||
tooltip = cls.bl_description
|
tooltip = cls.bl_description
|
||||||
@@ -1116,7 +1102,7 @@ class LoadProject(bpy.types.Operator, IFCFileSelector, ImportHelper):
|
|||||||
else:
|
else:
|
||||||
return self.finish_loading_project(context)
|
return self.finish_loading_project(context)
|
||||||
|
|
||||||
def finish_loading_project(self, context):
|
def finish_loading_project(self, context: bpy.types.Context) -> set["rna_enums.OperatorReturnItems"]:
|
||||||
try:
|
try:
|
||||||
filepath = self.get_filepath()
|
filepath = self.get_filepath()
|
||||||
if not self.is_existing_ifc_file():
|
if not self.is_existing_ifc_file():
|
||||||
@@ -1314,7 +1300,10 @@ class ToggleFilterCategories(bpy.types.Operator):
|
|||||||
bl_idname = "bim.toggle_filter_categories"
|
bl_idname = "bim.toggle_filter_categories"
|
||||||
bl_label = "Toggle Filter Categories"
|
bl_label = "Toggle Filter Categories"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
should_select: bpy.props.BoolProperty(name="Should Select", default=True)
|
should_select: bpy.props.BoolProperty(name="Should Select", default=True) # pyright: ignore[reportRedeclaration]
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
should_select: bool
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
props = tool.Project.get_project_props()
|
props = tool.Project.get_project_props()
|
||||||
@@ -1338,6 +1327,14 @@ class LinkIfc(bpy.types.Operator, ImportHelper, tool.Ifc.Operator):
|
|||||||
default=False,
|
default=False,
|
||||||
)
|
)
|
||||||
use_cache: bpy.props.BoolProperty(name="Use Cache", default=True)
|
use_cache: bpy.props.BoolProperty(name="Use Cache", default=True)
|
||||||
|
query: bpy.props.StringProperty( # pyright: ignore[reportRedeclaration]
|
||||||
|
name="Query",
|
||||||
|
description=(
|
||||||
|
"Custom selector query to use to load element from a linked model. E.g. 'IfcElement'.\n\n"
|
||||||
|
"Default query - IfcElement, but excluding IfcProxy, IfcSpatialStructureElement, IfcSpatialElement, IfcFeatureElement."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
filename_ext = ".ifc"
|
filename_ext = ".ifc"
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@@ -1347,20 +1344,25 @@ class LinkIfc(bpy.types.Operator, ImportHelper, tool.Ifc.Operator):
|
|||||||
filter_glob: str
|
filter_glob: str
|
||||||
use_relative_path: bool
|
use_relative_path: bool
|
||||||
use_cache: bool
|
use_cache: bool
|
||||||
|
query: str
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
|
assert self.layout
|
||||||
pprops = tool.Project.get_project_props()
|
pprops = tool.Project.get_project_props()
|
||||||
row = self.layout.row()
|
row = self.layout.row()
|
||||||
row.prop(self, "use_relative_path")
|
row.prop(self, "use_relative_path")
|
||||||
row = self.layout.row()
|
row = self.layout.row()
|
||||||
row.prop(self, "use_cache")
|
row.prop(self, "use_cache")
|
||||||
row = self.layout.row()
|
row = self.layout.row()
|
||||||
row.prop(pprops, "false_origin_mode")
|
row.label(text="False Origin Mode:")
|
||||||
|
row = self.layout.row()
|
||||||
|
row.prop(pprops, "false_origin_mode", text="")
|
||||||
if pprops.false_origin_mode == "MANUAL":
|
if pprops.false_origin_mode == "MANUAL":
|
||||||
row = self.layout.row()
|
row = self.layout.row()
|
||||||
row.prop(pprops, "false_origin")
|
row.prop(pprops, "false_origin")
|
||||||
row = self.layout.row()
|
row = self.layout.row()
|
||||||
row.prop(pprops, "project_north")
|
row.prop(pprops, "project_north")
|
||||||
|
self.layout.prop(self, "query", placeholder="IfcElement")
|
||||||
|
|
||||||
def _execute(self, context):
|
def _execute(self, context):
|
||||||
start = time.time()
|
start = time.time()
|
||||||
@@ -1393,7 +1395,7 @@ class LinkIfc(bpy.types.Operator, ImportHelper, tool.Ifc.Operator):
|
|||||||
new.ifc_definition_id = reference.id()
|
new.ifc_definition_id = reference.id()
|
||||||
new.name = filepath
|
new.name = filepath
|
||||||
new.filepath = filepath
|
new.filepath = filepath
|
||||||
bpy.ops.bim.load_link(link_index=-1, use_cache=self.use_cache)
|
bpy.ops.bim.load_link(link_index=-1, use_cache=self.use_cache, query=self.query)
|
||||||
|
|
||||||
|
|
||||||
class UnlinkIfc(bpy.types.Operator, tool.Ifc.Operator):
|
class UnlinkIfc(bpy.types.Operator, tool.Ifc.Operator):
|
||||||
@@ -1401,7 +1403,11 @@ class UnlinkIfc(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
bl_label = "Unlink IFC"
|
bl_label = "Unlink IFC"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
bl_description = "Remove the selected file from the link list"
|
bl_description = "Remove the selected file from the link list"
|
||||||
link_index: bpy.props.IntProperty(name="Link Index")
|
|
||||||
|
link_index: bpy.props.IntProperty(name="Link Index") # pyright: ignore[reportRedeclaration]
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
link_index: int
|
||||||
|
|
||||||
def _execute(self, context):
|
def _execute(self, context):
|
||||||
props = tool.Project.get_project_props()
|
props = tool.Project.get_project_props()
|
||||||
@@ -1421,7 +1427,11 @@ class UnloadLink(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
bl_label = "Unload Link"
|
bl_label = "Unload Link"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
bl_description = "Unload the selected linked file"
|
bl_description = "Unload the selected linked file"
|
||||||
link_index: bpy.props.IntProperty(name="Link Index")
|
|
||||||
|
link_index: bpy.props.IntProperty(name="Link Index") # pyright: ignore[reportRedeclaration]
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
link_index: int
|
||||||
|
|
||||||
def _execute(self, context):
|
def _execute(self, context):
|
||||||
link = tool.Project.get_project_props().links[self.link_index]
|
link = tool.Project.get_project_props().links[self.link_index]
|
||||||
@@ -1446,10 +1456,12 @@ class LoadLink(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
|
|
||||||
link_index: bpy.props.IntProperty(name="Link Index") # pyright: ignore[reportRedeclaration]
|
link_index: bpy.props.IntProperty(name="Link Index") # pyright: ignore[reportRedeclaration]
|
||||||
use_cache: bpy.props.BoolProperty(name="Use Cache", default=True) # pyright: ignore[reportRedeclaration]
|
use_cache: bpy.props.BoolProperty(name="Use Cache", default=True) # pyright: ignore[reportRedeclaration]
|
||||||
|
query: bpy.props.StringProperty() # pyright: ignore[reportRedeclaration]
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
link_index: int
|
link_index: int
|
||||||
use_cache: bool
|
use_cache: bool
|
||||||
|
query: str
|
||||||
|
|
||||||
def _execute(self, context):
|
def _execute(self, context):
|
||||||
self.link = tool.Project.get_project_props().links[self.link_index]
|
self.link = tool.Project.get_project_props().links[self.link_index]
|
||||||
@@ -1491,8 +1503,20 @@ class LoadLink(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
def link_ifc(self) -> Union[set[str], None]:
|
def link_ifc(self) -> Union[set[str], None]:
|
||||||
blend_filepath = self.filepath_.with_suffix(".ifc.cache.blend")
|
blend_filepath = self.filepath_.with_suffix(".ifc.cache.blend")
|
||||||
h5_filepath = self.filepath_.with_suffix(".ifc.cache.h5")
|
h5_filepath = self.filepath_.with_suffix(".ifc.cache.h5")
|
||||||
|
json_filepath = self.filepath_.with_suffix(".ifc.cache.json")
|
||||||
|
|
||||||
if not self.use_cache and blend_filepath.exists():
|
def should_clear_cache() -> bool:
|
||||||
|
if not self.use_cache:
|
||||||
|
return True
|
||||||
|
if not blend_filepath.exists():
|
||||||
|
return False
|
||||||
|
data = json.loads(json_filepath.read_text())
|
||||||
|
# Empty 'query' - model loaded without custom query.
|
||||||
|
# Missing 'query' - model was loaded before custom queries were introduced in Bonsai.
|
||||||
|
query = data.get("query", "")
|
||||||
|
return query != self.query
|
||||||
|
|
||||||
|
if should_clear_cache():
|
||||||
os.remove(blend_filepath)
|
os.remove(blend_filepath)
|
||||||
|
|
||||||
if not blend_filepath.exists():
|
if not blend_filepath.exists():
|
||||||
@@ -1520,7 +1544,7 @@ def run():
|
|||||||
pprops.project_north = "{pprops.project_north}"
|
pprops.project_north = "{pprops.project_north}"
|
||||||
# Use absolute path to be safe from cwd changes.
|
# Use absolute path to be safe from cwd changes.
|
||||||
try:
|
try:
|
||||||
bpy.ops.bim.load_linked_project(filepath=r"{str(self.filepath_)}")
|
bpy.ops.bim.load_linked_project(filepath=r"{str(self.filepath_)}", query={repr(self.query)})
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
# Operator failed (returned CANCELLED with error report)
|
# Operator failed (returned CANCELLED with error report)
|
||||||
print(f"Failed to load linked project: {{e}}")
|
print(f"Failed to load linked project: {{e}}")
|
||||||
@@ -1606,7 +1630,11 @@ class ReloadLink(bpy.types.Operator):
|
|||||||
bl_label = "Reload Link"
|
bl_label = "Reload Link"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
bl_description = "Reload the selected file"
|
bl_description = "Reload the selected file"
|
||||||
link_index: bpy.props.IntProperty(name="Link Index")
|
|
||||||
|
link_index: bpy.props.IntProperty(name="Link Index") # pyright: ignore[reportRedeclaration]
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
link_index: int
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
bpy.ops.bim.unload_link(link_index=self.link_index)
|
bpy.ops.bim.unload_link(link_index=self.link_index)
|
||||||
@@ -1618,7 +1646,11 @@ class ToggleLinkSelectability(bpy.types.Operator):
|
|||||||
bl_label = "Toggle Link Selectability"
|
bl_label = "Toggle Link Selectability"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
bl_description = "Toggle selectability"
|
bl_description = "Toggle selectability"
|
||||||
link_index: bpy.props.IntProperty(name="Link Index")
|
|
||||||
|
link_index: bpy.props.IntProperty(name="Link Index") # pyright: ignore[reportRedeclaration]
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
link_index: int
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
props = tool.Project.get_project_props()
|
props = tool.Project.get_project_props()
|
||||||
@@ -1788,7 +1820,11 @@ class SelectLinkHandle(bpy.types.Operator):
|
|||||||
bl_label = "Select Link Handle"
|
bl_label = "Select Link Handle"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
bl_description = "Select link empty object handle"
|
bl_description = "Select link empty object handle"
|
||||||
link_index: bpy.props.IntProperty(name="Link Index")
|
|
||||||
|
link_index: bpy.props.IntProperty(name="Link Index") # pyright: ignore[reportRedeclaration]
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
link_index: int
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
props = tool.Project.get_project_props()
|
props = tool.Project.get_project_props()
|
||||||
@@ -1846,11 +1882,28 @@ class ExportIFC(bpy.types.Operator, ExportHelper):
|
|||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
filename_ext = ".ifc"
|
filename_ext = ".ifc"
|
||||||
supported_filexts = (".ifc", ".ifczip", ".ifcjson")
|
supported_filexts = (".ifc", ".ifczip", ".ifcjson")
|
||||||
filter_glob: bpy.props.StringProperty(default=";".join(f"*{ext}" for ext in supported_filexts), options={"HIDDEN"})
|
filter_glob: bpy.props.StringProperty(
|
||||||
json_version: bpy.props.EnumProperty(items=[("4", "4", ""), ("5a", "5a", "")], name="IFC JSON Version")
|
default=";".join(f"*{ext}" for ext in supported_filexts), options={"HIDDEN"}
|
||||||
json_compact: bpy.props.BoolProperty(name="Export Compact IFCJSON", default=False)
|
) # pyright: ignore[reportRedeclaration]
|
||||||
should_save_as: bpy.props.BoolProperty(name="Should Save As", default=False, options={"HIDDEN"})
|
json_version: bpy.props.EnumProperty(
|
||||||
use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=False)
|
items=[("4", "4", ""), ("5a", "5a", "")], name="IFC JSON Version"
|
||||||
|
) # pyright: ignore[reportRedeclaration]
|
||||||
|
json_compact: bpy.props.BoolProperty(
|
||||||
|
name="Export Compact IFCJSON", default=False
|
||||||
|
) # pyright: ignore[reportRedeclaration]
|
||||||
|
should_save_as: bpy.props.BoolProperty(
|
||||||
|
name="Should Save As", default=False, options={"HIDDEN"}
|
||||||
|
) # pyright: ignore[reportRedeclaration]
|
||||||
|
use_relative_path: bpy.props.BoolProperty(
|
||||||
|
name="Use Relative Path", default=False
|
||||||
|
) # pyright: ignore[reportRedeclaration]
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
filter_glob: str
|
||||||
|
json_version: str
|
||||||
|
json_compact: bool
|
||||||
|
should_save_as: bool
|
||||||
|
use_relative_path: bool
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
@@ -2000,6 +2053,12 @@ class LoadLinkedProject(bpy.types.Operator, ImportHelper):
|
|||||||
bl_description = "Operator is used to load a project .cache.blend to then link it to the IFC file."
|
bl_description = "Operator is used to load a project .cache.blend to then link it to the IFC file."
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
|
query: bpy.props.StringProperty() # pyright: ignore[reportRedeclaration]
|
||||||
|
"""See ``bim.link_ifc``."""
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
query: str
|
||||||
|
|
||||||
file: ifcopenshell.file
|
file: ifcopenshell.file
|
||||||
meshes: dict[str, bpy.types.Mesh]
|
meshes: dict[str, bpy.types.Mesh]
|
||||||
# Material names is derived from diffuse as in 'r-g-b-a'.
|
# Material names is derived from diffuse as in 'r-g-b-a'.
|
||||||
@@ -2049,14 +2108,17 @@ class LoadLinkedProject(bpy.types.Operator, ImportHelper):
|
|||||||
tool.Loader.settings.context_settings = tool.Loader.create_settings()
|
tool.Loader.settings.context_settings = tool.Loader.create_settings()
|
||||||
tool.Loader.settings.gross_context_settings = tool.Loader.create_settings(is_gross=True)
|
tool.Loader.settings.gross_context_settings = tool.Loader.create_settings(is_gross=True)
|
||||||
|
|
||||||
self.elements = set(self.file.by_type("IfcElement"))
|
if self.query:
|
||||||
if self.file.schema in ("IFC2X3", "IFC4"):
|
self.elements = ifcopenshell.util.selector.filter_elements(self.file, self.query)
|
||||||
self.elements |= set(self.file.by_type("IfcProxy"))
|
|
||||||
if self.file.schema == "IFC2X3":
|
|
||||||
self.elements |= set(self.file.by_type("IfcSpatialStructureElement"))
|
|
||||||
else:
|
else:
|
||||||
self.elements |= set(self.file.by_type("IfcSpatialElement"))
|
self.elements = set(self.file.by_type("IfcElement"))
|
||||||
self.elements -= set(self.file.by_type("IfcFeatureElement"))
|
if self.file.schema in ("IFC2X3", "IFC4"):
|
||||||
|
self.elements |= set(self.file.by_type("IfcProxy"))
|
||||||
|
if self.file.schema == "IFC2X3":
|
||||||
|
self.elements |= set(self.file.by_type("IfcSpatialStructureElement"))
|
||||||
|
else:
|
||||||
|
self.elements |= set(self.file.by_type("IfcSpatialElement"))
|
||||||
|
self.elements -= set(self.file.by_type("IfcFeatureElement"))
|
||||||
|
|
||||||
if tool.Loader.settings.false_origin_mode == "MANUAL" and tool.Loader.settings.false_origin:
|
if tool.Loader.settings.false_origin_mode == "MANUAL" and tool.Loader.settings.false_origin:
|
||||||
tool.Loader.set_manual_blender_offset(self.file)
|
tool.Loader.set_manual_blender_offset(self.file)
|
||||||
@@ -2081,6 +2143,7 @@ class LoadLinkedProject(bpy.types.Operator, ImportHelper):
|
|||||||
"false_origin_mode": pprops.false_origin_mode,
|
"false_origin_mode": pprops.false_origin_mode,
|
||||||
"false_origin": pprops.false_origin,
|
"false_origin": pprops.false_origin,
|
||||||
"project_north": pprops.project_north,
|
"project_north": pprops.project_north,
|
||||||
|
"query": self.query,
|
||||||
}
|
}
|
||||||
with open(self.json_filepath, "w") as f:
|
with open(self.json_filepath, "w") as f:
|
||||||
json.dump(data, f)
|
json.dump(data, f)
|
||||||
@@ -2142,6 +2205,7 @@ class LoadLinkedProject(bpy.types.Operator, ImportHelper):
|
|||||||
if iterator.initialize():
|
if iterator.initialize():
|
||||||
while True: # Main loop.
|
while True: # Main loop.
|
||||||
shape = iterator.get()
|
shape = iterator.get()
|
||||||
|
assert isinstance(shape, W.TriangulationElement)
|
||||||
results.add(self.file.by_id(shape.id))
|
results.add(self.file.by_id(shape.id))
|
||||||
geometry = shape.geometry
|
geometry = shape.geometry
|
||||||
|
|
||||||
@@ -2372,18 +2436,23 @@ class HideQueriedLinkedElement(bpy.types.Operator):
|
|||||||
bl_label = "Hide Queried Linked Element"
|
bl_label = "Hide Queried Linked Element"
|
||||||
bl_description = (
|
bl_description = (
|
||||||
"Hide geometry for currently queried linked element.\n\n"
|
"Hide geometry for currently queried linked element.\n\n"
|
||||||
"ALT+Click (or ALT+H in Explore Tool) to unhide all geometry for currently selected linked model.\n"
|
"SHIFT+Click (or SHIFT+H in Explore Tool) to hide everything "
|
||||||
"(Not Yet Implemented) SHIFT+Click to hide everything but currently queried element."
|
"in the currently selected model, but the queried element.\n"
|
||||||
|
"ALT+Click (or ALT+H in Explore Tool) to unhide all geometry for currently selected linked model.\n\n"
|
||||||
|
"Known limitation: doesn't work with UNDO."
|
||||||
)
|
)
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
unhide_all: bpy.props.BoolProperty(options={"SKIP_SAVE"}) # pyright: ignore[reportRedeclaration]
|
unhide_all: bpy.props.BoolProperty(options={"SKIP_SAVE"}) # pyright: ignore[reportRedeclaration]
|
||||||
|
hide_all_except: bpy.props.BoolProperty(options={"SKIP_SAVE"}) # pyright: ignore[reportRedeclaration]
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
unhide_all: bool
|
unhide_all: bool
|
||||||
|
hide_all_except: bool
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
self.unhide_all = event.alt
|
self.unhide_all = event.alt
|
||||||
|
self.hide_all_except = event.shift
|
||||||
return self.execute(context)
|
return self.execute(context)
|
||||||
|
|
||||||
def execute(self, context) -> set["rna_enums.OperatorReturnItems"]:
|
def execute(self, context) -> set["rna_enums.OperatorReturnItems"]:
|
||||||
@@ -2392,6 +2461,9 @@ class HideQueriedLinkedElement(bpy.types.Operator):
|
|||||||
if self.unhide_all:
|
if self.unhide_all:
|
||||||
return self.run_unhide_all()
|
return self.run_unhide_all()
|
||||||
|
|
||||||
|
if self.hide_all_except:
|
||||||
|
return self.run_hide_all_except()
|
||||||
|
|
||||||
obj = props.queried_obj
|
obj = props.queried_obj
|
||||||
if not obj:
|
if not obj:
|
||||||
self.report({"INFO"}, "No object is queried to hide.")
|
self.report({"INFO"}, "No object is queried to hide.")
|
||||||
@@ -2413,6 +2485,21 @@ class HideQueriedLinkedElement(bpy.types.Operator):
|
|||||||
self.report({"INFO"}, "All linked model geometry is unhidden.")
|
self.report({"INFO"}, "All linked model geometry is unhidden.")
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
def run_hide_all_except(self) -> set["rna_enums.OperatorReturnItems"]:
|
||||||
|
props = tool.Project.get_project_props()
|
||||||
|
obj = props.queried_obj
|
||||||
|
if not obj:
|
||||||
|
self.report({"INFO"}, "No object is queried.")
|
||||||
|
return {"FINISHED"}
|
||||||
|
link = props.active_link
|
||||||
|
if not link:
|
||||||
|
self.report({"INFO"}, "No linked model is currently selected.")
|
||||||
|
return {"FINISHED"}
|
||||||
|
guid = props.queried_guid
|
||||||
|
tool.Project.Link.hide_all_elements_except(link, obj, guid)
|
||||||
|
self.report({"INFO"}, "All other linked model geometry is now hidden.")
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
class AppendInspectedLinkedElement(AppendLibraryElement):
|
class AppendInspectedLinkedElement(AppendLibraryElement):
|
||||||
bl_idname = "bim.append_inspected_linked_element"
|
bl_idname = "bim.append_inspected_linked_element"
|
||||||
@@ -2471,7 +2558,7 @@ class EnableCulling(bpy.types.Operator):
|
|||||||
self.total_mousemoves = 0
|
self.total_mousemoves = 0
|
||||||
self.cullable_objects = []
|
self.cullable_objects = []
|
||||||
|
|
||||||
def modal(self, context, event):
|
def modal(self, context, event) -> set["rna_enums.OperatorReturnItems"]:
|
||||||
if not LinksData.enable_culling:
|
if not LinksData.enable_culling:
|
||||||
for obj in bpy.context.visible_objects:
|
for obj in bpy.context.visible_objects:
|
||||||
if obj.type == "MESH" and obj.name.startswith("Ifc"):
|
if obj.type == "MESH" and obj.name.startswith("Ifc"):
|
||||||
@@ -2502,7 +2589,7 @@ class EnableCulling(bpy.types.Operator):
|
|||||||
|
|
||||||
return {"PASS_THROUGH"}
|
return {"PASS_THROUGH"}
|
||||||
|
|
||||||
def is_view_changed(self, context):
|
def is_view_changed(self, context: bpy.types.Context) -> bool:
|
||||||
view_matrix = context.region_data.view_matrix
|
view_matrix = context.region_data.view_matrix
|
||||||
projection_matrix = context.region_data.window_matrix
|
projection_matrix = context.region_data.window_matrix
|
||||||
vp_matrix = projection_matrix @ view_matrix
|
vp_matrix = projection_matrix @ view_matrix
|
||||||
@@ -2517,7 +2604,7 @@ class EnableCulling(bpy.types.Operator):
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def is_object_in_view(self, obj, context, camera_position):
|
def is_object_in_view(self, obj: bpy.types.Object, context: bpy.types.Context, camera_position: Vector) -> bool:
|
||||||
# Get the view matrix and the projection matrix from the active viewport
|
# Get the view matrix and the projection matrix from the active viewport
|
||||||
view_matrix = context.region_data.view_matrix
|
view_matrix = context.region_data.view_matrix
|
||||||
projection_matrix = context.region_data.window_matrix
|
projection_matrix = context.region_data.window_matrix
|
||||||
@@ -2544,7 +2631,7 @@ class EnableCulling(bpy.types.Operator):
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context: bpy.types.Context, event: bpy.types.Event) -> set["rna_enums.OperatorReturnItems"]:
|
||||||
LinksData.enable_culling = True
|
LinksData.enable_culling = True
|
||||||
self.cullable_objects = []
|
self.cullable_objects = []
|
||||||
for obj in bpy.context.visible_objects:
|
for obj in bpy.context.visible_objects:
|
||||||
@@ -2698,10 +2785,7 @@ class CreateClippingPlane(bpy.types.Operator):
|
|||||||
self.report({"INFO"}, "Maximum of six clipping planes allowed.")
|
self.report({"INFO"}, "Maximum of six clipping planes allowed.")
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
assert context.screen
|
tool.Blender.update_all_viewports(context)
|
||||||
for area in context.screen.areas:
|
|
||||||
if area.type == "VIEW_3D":
|
|
||||||
area.tag_redraw()
|
|
||||||
|
|
||||||
assert context.region and context.region_data
|
assert context.region and context.region_data
|
||||||
region = context.region
|
region = context.region
|
||||||
@@ -2834,8 +2918,16 @@ class IFCFileHandlerOperator(bpy.types.Operator):
|
|||||||
bl_label = "Import .ifc file"
|
bl_label = "Import .ifc file"
|
||||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||||
|
|
||||||
directory: bpy.props.StringProperty(subtype="FILE_PATH", options={"SKIP_SAVE", "HIDDEN"})
|
directory: bpy.props.StringProperty(
|
||||||
files: bpy.props.CollectionProperty(type=bpy.types.OperatorFileListElement, options={"SKIP_SAVE", "HIDDEN"})
|
subtype="FILE_PATH", options={"SKIP_SAVE", "HIDDEN"}
|
||||||
|
) # pyright: ignore[reportRedeclaration]
|
||||||
|
files: bpy.props.CollectionProperty(
|
||||||
|
type=bpy.types.OperatorFileListElement, options={"SKIP_SAVE", "HIDDEN"}
|
||||||
|
) # pyright: ignore[reportRedeclaration]
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
directory: str
|
||||||
|
files: list[bpy.types.OperatorFileListElement]
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
# Keeping code in .invoke() as we'll probably add some
|
# Keeping code in .invoke() as we'll probably add some
|
||||||
@@ -2886,7 +2978,10 @@ class MeasureTool(bpy.types.Operator, PolylineOperator):
|
|||||||
bl_label = "Measure Tool"
|
bl_label = "Measure Tool"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
measure_type: bpy.props.StringProperty()
|
measure_type: bpy.props.StringProperty() # pyright: ignore[reportRedeclaration]
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
measure_type: str
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
@@ -2982,7 +3077,10 @@ class MeasureFaceAreaTool(bpy.types.Operator, PolylineOperator):
|
|||||||
bl_label = "Measure Face Area Tool"
|
bl_label = "Measure Face Area Tool"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
measure_type: bpy.props.StringProperty()
|
measure_type: bpy.props.StringProperty() # pyright: ignore[reportRedeclaration]
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
measure_type: str
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
@@ -3086,7 +3184,10 @@ class ClearMeasurement(bpy.types.Operator):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
polyline_props = tool.Model.get_polyline_props()
|
polyline_props = tool.Model.get_polyline_props()
|
||||||
return len(polyline_props.measurement_polyline) > 0
|
if len(polyline_props.measurement_polyline) > 0:
|
||||||
|
return True
|
||||||
|
cls.poll_message_set("No measurement to clear.")
|
||||||
|
return False
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
polyline_props = tool.Model.get_polyline_props()
|
polyline_props = tool.Model.get_polyline_props()
|
||||||
@@ -3190,7 +3291,7 @@ class ImageScalingTool(bpy.types.Operator, PolylineOperator):
|
|||||||
super().invoke(context, event)
|
super().invoke(context, event)
|
||||||
return {"RUNNING_MODAL"}
|
return {"RUNNING_MODAL"}
|
||||||
|
|
||||||
def cancel_tool(self, context):
|
def cancel_tool(self, context: bpy.types.Context) -> set["rna_enums.OperatorReturnItems"]:
|
||||||
context.workspace.status_text_set(text=None)
|
context.workspace.status_text_set(text=None)
|
||||||
if hasattr(self, "tool_state"):
|
if hasattr(self, "tool_state"):
|
||||||
self.tool_state.plane_method = None
|
self.tool_state.plane_method = None
|
||||||
@@ -3198,7 +3299,7 @@ class ImageScalingTool(bpy.types.Operator, PolylineOperator):
|
|||||||
tool.Blender.update_viewport()
|
tool.Blender.update_viewport()
|
||||||
return {"CANCELLED"}
|
return {"CANCELLED"}
|
||||||
|
|
||||||
def handle_custom_instructions(self, context):
|
def handle_custom_instructions(self, context: bpy.types.Context) -> None:
|
||||||
if len(self.selected_points) == 0:
|
if len(self.selected_points) == 0:
|
||||||
instruction_text = "Click First Point on Image"
|
instruction_text = "Click First Point on Image"
|
||||||
elif len(self.selected_points) == 1:
|
elif len(self.selected_points) == 1:
|
||||||
@@ -3213,14 +3314,14 @@ class ImageScalingTool(bpy.types.Operator, PolylineOperator):
|
|||||||
|
|
||||||
context.workspace.status_text_set(text=instruction_text)
|
context.workspace.status_text_set(text=instruction_text)
|
||||||
|
|
||||||
def calculate_distance(self):
|
def calculate_distance(self) -> None:
|
||||||
if len(self.selected_points) == 2:
|
if len(self.selected_points) == 2:
|
||||||
point1 = self.selected_points[0]
|
point1 = self.selected_points[0]
|
||||||
point2 = self.selected_points[1]
|
point2 = self.selected_points[1]
|
||||||
distance_3d = (point2 - point1).length
|
distance_3d = (point2 - point1).length
|
||||||
self.calculated_distance = distance_3d / self.unit_scale
|
self.calculated_distance = distance_3d / self.unit_scale
|
||||||
|
|
||||||
def apply_scaling(self, context):
|
def apply_scaling(self, context: bpy.types.Context) -> set["rna_enums.OperatorReturnItems"]:
|
||||||
if len(self.selected_points) != 2:
|
if len(self.selected_points) != 2:
|
||||||
self.report({"ERROR"}, "Two points must be selected")
|
self.report({"ERROR"}, "Two points must be selected")
|
||||||
return {"CANCELLED"}
|
return {"CANCELLED"}
|
||||||
@@ -3278,7 +3379,10 @@ class LoadBlendMetadataAndIFC(bpy.types.Operator):
|
|||||||
bl_idname = "bim.load_blend_metadata_and_ifc"
|
bl_idname = "bim.load_blend_metadata_and_ifc"
|
||||||
bl_label = "Load Blend Metadata and IFC"
|
bl_label = "Load Blend Metadata and IFC"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
filepath: bpy.props.StringProperty(name="IFC File Path", default="")
|
filepath: bpy.props.StringProperty(name="IFC File Path", default="") # pyright: ignore[reportRedeclaration]
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
filepath: str
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
ifc_file = self.filepath
|
ifc_file = self.filepath
|
||||||
@@ -3312,3 +3416,19 @@ class LoadBlendMetadataAndIFC(bpy.types.Operator):
|
|||||||
bpy.app.handlers.load_post.append(load_handler)
|
bpy.app.handlers.load_post.append(load_handler)
|
||||||
bpy.ops.wm.open_mainfile(filepath=metadata_path)
|
bpy.ops.wm.open_mainfile(filepath=metadata_path)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
class GenerateUVMap(bpy.types.Operator):
|
||||||
|
bl_idname = "bim.generate_uv_map"
|
||||||
|
bl_label = "Generate UV Map"
|
||||||
|
bl_description = "Generate UV map for selected mesh."
|
||||||
|
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
obj = context.active_object
|
||||||
|
if not obj or not isinstance(obj.data, bpy.types.Mesh):
|
||||||
|
self.report({"ERROR"}, "No valid mesh selected.")
|
||||||
|
return {"CANCELLED"}
|
||||||
|
tool.Loader.load_generated_uv_map(obj.data)
|
||||||
|
self.report({"INFO"}, "Generated UV map for selected mesh.")
|
||||||
|
return {"FINISHED"}
|
||||||
|
|||||||
@@ -230,11 +230,11 @@ class Link(PropertyGroup):
|
|||||||
)
|
)
|
||||||
georeferenced: EnumProperty(
|
georeferenced: EnumProperty(
|
||||||
name="Georeferenced",
|
name="Georeferenced",
|
||||||
description="Georeferencing status: compatibility between host and linked model",
|
description="Georeferencing status, compatibility between host and linked model",
|
||||||
items=[
|
items=[
|
||||||
("NONE", "No Georef", "Linked model has no georeferencing"),
|
("NONE", "No Georef", "Linked model has no georeferencing", "QUESTION", 0),
|
||||||
("NOT_COMPATIBLE", "Not Compatible", "Has geo data but CRS differ from host"),
|
("NOT_COMPATIBLE", "Not Compatible", "Has geo data but CRS differ from host", "ERROR", 1),
|
||||||
("FULL_COMPATIBLE", "Full Compatible", "Both CRS name and vertical datum match host"),
|
("FULL_COMPATIBLE", "Full Compatible", "Both CRS name and vertical datum match host", "WORLD", 2),
|
||||||
],
|
],
|
||||||
default="NONE",
|
default="NONE",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -255,7 +255,7 @@ class BIM_PT_project(Panel):
|
|||||||
row = self.layout.row(align=True)
|
row = self.layout.row(align=True)
|
||||||
row.operator("bim.load_project_elements")
|
row.operator("bim.load_project_elements")
|
||||||
|
|
||||||
def draw_editing_buttons(self, context, row):
|
def draw_editing_buttons(self, context: object, row: bpy.types.UILayout) -> None:
|
||||||
pprops = self.props
|
pprops = self.props
|
||||||
if tool.Ifc.get():
|
if tool.Ifc.get():
|
||||||
if pprops.is_editing:
|
if pprops.is_editing:
|
||||||
@@ -496,7 +496,7 @@ class BIM_PT_links(Panel):
|
|||||||
row.operator("bim.reload_link", text="", icon="FILE_REFRESH").link_index = index
|
row.operator("bim.reload_link", text="", icon="FILE_REFRESH").link_index = index
|
||||||
else:
|
else:
|
||||||
row.operator("bim.load_link", text="", icon="LINKED").link_index = index
|
row.operator("bim.load_link", text="", icon="LINKED").link_index = index
|
||||||
row.operator("bim.unlink_ifc", text="", icon="X").link_index = index
|
row.operator("bim.unlink_ifc", text="", icon="X").link_index = index
|
||||||
self.layout.template_list("BIM_UL_links", "", self.props, "links", self.props, "active_link_index")
|
self.layout.template_list("BIM_UL_links", "", self.props, "links", self.props, "active_link_index")
|
||||||
|
|
||||||
if LinksData.enable_culling:
|
if LinksData.enable_culling:
|
||||||
@@ -619,12 +619,14 @@ class BIM_UL_links(UIList):
|
|||||||
):
|
):
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
if item.is_loaded:
|
if item.is_loaded:
|
||||||
if item.georeferenced == "NONE":
|
from bonsai.bim.module.project.prop import Link
|
||||||
row.label(text="", icon="QUESTION")
|
|
||||||
elif item.georeferenced == "NOT_COMPATIBLE":
|
s = Link.bl_rna
|
||||||
row.label(text="", icon="ERROR")
|
geo_prop = s.properties["georeferenced"]
|
||||||
elif item.georeferenced == "FULL_COMPATIBLE":
|
assert isinstance(geo_prop, bpy.types.EnumProperty)
|
||||||
row.label(text="", icon="WORLD")
|
enum_item = geo_prop.enum_items[item.georeferenced]
|
||||||
|
op = row.operator("bim.show_description", text="", icon=enum_item.icon, emboss=False)
|
||||||
|
op.description = f"{geo_prop.description}\n{enum_item.name}: {enum_item.description}"
|
||||||
if item.has_transformation:
|
if item.has_transformation:
|
||||||
row.label(text="", icon="OBJECT_ORIGIN")
|
row.label(text="", icon="OBJECT_ORIGIN")
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ class ExploreTool(bpy.types.WorkSpaceTool):
|
|||||||
("bim.explore_hotkey", {"type": "M", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_M")]}),
|
("bim.explore_hotkey", {"type": "M", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_M")]}),
|
||||||
("bim.explore_hotkey", {"type": "S", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_S")]}),
|
("bim.explore_hotkey", {"type": "S", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_S")]}),
|
||||||
("bim.explore_hotkey", {"type": "H", "value": "PRESS"}, {"properties": [("hotkey", "H")]}),
|
("bim.explore_hotkey", {"type": "H", "value": "PRESS"}, {"properties": [("hotkey", "H")]}),
|
||||||
|
("bim.explore_hotkey", {"type": "H", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_H")]}),
|
||||||
("bim.explore_hotkey", {"type": "H", "value": "PRESS", "alt": True}, {"properties": [("hotkey", "A_H")]}),
|
("bim.explore_hotkey", {"type": "H", "value": "PRESS", "alt": True}, {"properties": [("hotkey", "A_H")]}),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -70,21 +71,26 @@ class ExploreTool(bpy.types.WorkSpaceTool):
|
|||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
row.label(text="", icon="EVENT_SHIFT")
|
row.label(text="", icon="EVENT_SHIFT")
|
||||||
row.label(text="", icon="EVENT_M")
|
row.label(text="", icon="EVENT_M")
|
||||||
row = layout.row(align=True)
|
|
||||||
op = row.operator("bim.explore_hotkey", text="Measure Tool", icon="CON_DISTLIMIT")
|
op = row.operator("bim.explore_hotkey", text="Measure Tool", icon="CON_DISTLIMIT")
|
||||||
op.hotkey = "S_M"
|
op.hotkey = "S_M"
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
row.prop(prop, "measurement_type", text="Measure Type", expand=True, icon_only=True, emboss=True)
|
row.prop(prop, "measurement_type", text="Measure Type", expand=True, icon_only=True, emboss=True)
|
||||||
row = layout.row(align=True)
|
|
||||||
op = row.operator("bim.clear_measurement", text="", icon="X")
|
op = row.operator("bim.clear_measurement", text="", icon="X")
|
||||||
|
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
row.label(text="", icon="EVENT_SHIFT")
|
row.label(text="", icon="EVENT_SHIFT")
|
||||||
row.label(text="", icon="EVENT_S")
|
row.label(text="", icon="EVENT_S")
|
||||||
row = layout.row(align=True)
|
|
||||||
op = row.operator("bim.explore_hotkey", text="Image Scaling Tool", icon="IMAGE_PLANE")
|
op = row.operator("bim.explore_hotkey", text="Image Scaling Tool", icon="IMAGE_PLANE")
|
||||||
op.hotkey = "S_S"
|
op.hotkey = "S_S"
|
||||||
op.description = "Scale Image Annotation. Allows to scale an IfcReferenceImage. Select image, select tool. Check lower left corner instructions to select two points and provide real distance between them"
|
op.description = (
|
||||||
|
"Scale Image Annotation.\n\n"
|
||||||
|
"Allows to scale an IfcReferenceImage.\n\n"
|
||||||
|
"Select image, select tool. "
|
||||||
|
"Check lower left corner instructions to select two points and provide real distance between them"
|
||||||
|
)
|
||||||
|
|
||||||
|
row = layout.row(align=True)
|
||||||
|
row.operator("bim.generate_uv_map", icon="UV")
|
||||||
|
|
||||||
|
|
||||||
class ExploreHotkey(bpy.types.Operator):
|
class ExploreHotkey(bpy.types.Operator):
|
||||||
@@ -147,5 +153,8 @@ class ExploreHotkey(bpy.types.Operator):
|
|||||||
def hotkey_H(self) -> None:
|
def hotkey_H(self) -> None:
|
||||||
bpy.ops.bim.hide_queried_linked_element()
|
bpy.ops.bim.hide_queried_linked_element()
|
||||||
|
|
||||||
|
def hotkey_S_H(self) -> None:
|
||||||
|
bpy.ops.bim.hide_queried_linked_element(hide_all_except=True)
|
||||||
|
|
||||||
def hotkey_A_H(self) -> None:
|
def hotkey_A_H(self) -> None:
|
||||||
bpy.ops.bim.hide_queried_linked_element(unhide_all=True)
|
bpy.ops.bim.hide_queried_linked_element(unhide_all=True)
|
||||||
|
|||||||
@@ -262,7 +262,7 @@ class BIM_PT_object_psets(Panel):
|
|||||||
|
|
||||||
row = self.layout.row(align=True)
|
row = self.layout.row(align=True)
|
||||||
prop_with_search(row, props, "pset_name", text="")
|
prop_with_search(row, props, "pset_name", text="")
|
||||||
if props.pset_name != "BBIM_BSDD" and not props.pset_name.startswith(tool.Bsdd.identifier_url):
|
if props.pset_name != "BBIM_BSDD" and not props.pset_name.startswith(tool.Bsdd.identifier_url()):
|
||||||
op = row.operator("bim.add_pset", icon="ADD", text="")
|
op = row.operator("bim.add_pset", icon="ADD", text="")
|
||||||
op.obj = obj.name
|
op.obj = obj.name
|
||||||
op.obj_type = "Object"
|
op.obj_type = "Object"
|
||||||
|
|||||||
@@ -321,7 +321,7 @@ def get_gross_perimeter(o: bpy.types.Object) -> float:
|
|||||||
return gross_perimeter
|
return gross_perimeter
|
||||||
|
|
||||||
|
|
||||||
def get_space_net_perimeter(obj: bpy.types.Object) -> float:
|
def get_space_net_perimeter(obj: bpy.types.Object) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -188,7 +188,7 @@ class BIMResourceProperties(PropertyGroup):
|
|||||||
@property
|
@property
|
||||||
def productivity(self) -> "BIMResourceProductivity":
|
def productivity(self) -> "BIMResourceProductivity":
|
||||||
assert bpy.context.scene
|
assert bpy.context.scene
|
||||||
productivity = bpy.context.scene.BIMResourceProductivity
|
productivity = bpy.context.scene.BIMResourceProductivity # pyright: ignore[reportAttributeAccessIssue]
|
||||||
assert isinstance(productivity, BIMResourceProductivity)
|
assert isinstance(productivity, BIMResourceProductivity)
|
||||||
return productivity
|
return productivity
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ classes = (
|
|||||||
operator.SelectByProperty,
|
operator.SelectByProperty,
|
||||||
operator.SelectFilterElements,
|
operator.SelectFilterElements,
|
||||||
operator.SelectGlobalId,
|
operator.SelectGlobalId,
|
||||||
|
operator.SelectQueryElements,
|
||||||
operator.SelectIfcClass,
|
operator.SelectIfcClass,
|
||||||
operator.SelectSimilar,
|
operator.SelectSimilar,
|
||||||
operator.ShowAllElements,
|
operator.ShowAllElements,
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ from bonsai.bim.ifc import IfcStore
|
|||||||
from bonsai.bim.prop import StrProperty
|
from bonsai.bim.prop import StrProperty
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
from bpy.stub_internal import rna_enums
|
||||||
|
|
||||||
from bonsai.bim.prop import BIMFacet
|
from bonsai.bim.prop import BIMFacet
|
||||||
|
|
||||||
|
|
||||||
@@ -617,7 +619,7 @@ class SelectFilterElements(bpy.types.Operator):
|
|||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
class ApplyFilterFromText(Operator, tool.Ifc.Operator):
|
class ApplyFilterFromText(Operator):
|
||||||
bl_idname = "bim.apply_filter_from_text"
|
bl_idname = "bim.apply_filter_from_text"
|
||||||
bl_label = "Apply Filter Configuration"
|
bl_label = "Apply Filter Configuration"
|
||||||
bl_description = "Apply the JSON filter configuration from the current text block"
|
bl_description = "Apply the JSON filter configuration from the current text block"
|
||||||
@@ -791,6 +793,27 @@ class Search(Operator):
|
|||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
class SelectQueryElements(Operator):
|
||||||
|
bl_idname = "bim.select_query_elements"
|
||||||
|
bl_label = "Select Query Elements"
|
||||||
|
bl_description = "Select elements matching an provided selector query"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
|
query: StringProperty(name="Query") # pyright: ignore[reportRedeclaration]
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
query: str
|
||||||
|
|
||||||
|
def execute(self, context) -> set["rna_enums.OperatorReturnItems"]:
|
||||||
|
results = ifcopenshell.util.selector.filter_elements(tool.Ifc.get(), self.query)
|
||||||
|
objs = [obj for e in results if isinstance(obj := tool.Ifc.get_object(e), bpy.types.Object)]
|
||||||
|
active_object = context.active_object or next(iter(objs), None)
|
||||||
|
selection = tool.Blender.validate_object_selection(context, active_object, objs)
|
||||||
|
tool.Blender.set_objects_selection(*selection, clear_previous_selection=False)
|
||||||
|
self.report({"INFO"}, f"{len(results)} Results, {len(selection.selected_objects)} Objects Selected")
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
class SaveSearch(Operator, tool.Ifc.Operator):
|
class SaveSearch(Operator, tool.Ifc.Operator):
|
||||||
bl_idname = "bim.save_search"
|
bl_idname = "bim.save_search"
|
||||||
bl_label = "Save Search"
|
bl_label = "Save Search"
|
||||||
@@ -1053,8 +1076,8 @@ class ColourByProperty(Operator):
|
|||||||
colourscheme[str(values[index])]["total"] += 1
|
colourscheme[str(values[index])]["total"] += 1
|
||||||
obj.color = (*tool.Search.get_quantitative_palette(palette, value, min_value, max_value), 1)
|
obj.color = (*tool.Search.get_quantitative_palette(palette, value, min_value, max_value), 1)
|
||||||
|
|
||||||
if areas := [a for a in context.screen.areas if a.type == "VIEW_3D"]:
|
assert (space := tool.Blender.get_view3d_space())
|
||||||
areas[0].spaces[0].shading.color_type = "OBJECT"
|
space.shading.color_type = "OBJECT"
|
||||||
|
|
||||||
props.colourscheme.clear()
|
props.colourscheme.clear()
|
||||||
|
|
||||||
@@ -1078,16 +1101,18 @@ class ColourByProperty(Operator):
|
|||||||
return (1, value)
|
return (1, value)
|
||||||
|
|
||||||
def store_state(self, context):
|
def store_state(self, context):
|
||||||
if areas := [a for a in context.screen.areas if a.type == "VIEW_3D"]:
|
if space := tool.Blender.get_view3d_space():
|
||||||
self.transaction_data = {"area": areas[0], "color_type": areas[0].spaces[0].shading.color_type}
|
self.transaction_data = {"color_type": space.shading.color_type}
|
||||||
|
|
||||||
def rollback(self, data):
|
def rollback(self, data):
|
||||||
if data:
|
if data:
|
||||||
data["area"].spaces[0].shading.color_type = data["color_type"]
|
assert (space := tool.Blender.get_view3d_space())
|
||||||
|
space.shading.color_type = data["color_type"]
|
||||||
|
|
||||||
def commit(self, data):
|
def commit(self, data):
|
||||||
if data:
|
if data:
|
||||||
data["area"].spaces[0].shading.color_type = "OBJECT"
|
assert (space := tool.Blender.get_view3d_space())
|
||||||
|
space.shading.color_type = "OBJECT"
|
||||||
|
|
||||||
|
|
||||||
class SelectByProperty(Operator):
|
class SelectByProperty(Operator):
|
||||||
@@ -1415,7 +1440,7 @@ class ShowAllElements(Operator):
|
|||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
class SelectSimilar(Operator, tool.Ifc.Operator):
|
class SelectSimilar(Operator):
|
||||||
bl_idname = "bim.select_similar"
|
bl_idname = "bim.select_similar"
|
||||||
bl_label = "Select Similar"
|
bl_label = "Select Similar"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import TYPE_CHECKING, Any, Optional
|
from typing import TYPE_CHECKING, Any, Optional
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
@@ -37,8 +39,11 @@ from bonsai.bim.module.sequence.data import (
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from bonsai.bim.module.sequence.prop import (
|
from bonsai.bim.module.sequence.prop import (
|
||||||
BIMTaskTreeProperties,
|
BIMTaskTreeProperties,
|
||||||
|
BIMTaskTypeColor,
|
||||||
BIMWorkScheduleProperties,
|
BIMWorkScheduleProperties,
|
||||||
Task,
|
Task,
|
||||||
|
TaskProduct,
|
||||||
|
TaskResource,
|
||||||
)
|
)
|
||||||
from bonsai.bim.prop import Attribute
|
from bonsai.bim.prop import Attribute
|
||||||
|
|
||||||
@@ -799,23 +804,24 @@ class BIM_UL_task_columns(UIList):
|
|||||||
self,
|
self,
|
||||||
context,
|
context,
|
||||||
layout: bpy.types.UILayout,
|
layout: bpy.types.UILayout,
|
||||||
data: "BIMWorkScheduleProperties",
|
data: BIMWorkScheduleProperties,
|
||||||
item: "Attribute",
|
item: Attribute,
|
||||||
icon,
|
icon,
|
||||||
active_data,
|
active_data,
|
||||||
active_propname,
|
active_propname,
|
||||||
):
|
):
|
||||||
props = tool.Sequence.get_work_schedule_props()
|
|
||||||
if item:
|
if item:
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
row.prop(item, "name", emboss=False, text="")
|
row.prop(item, "name", emboss=False, text="")
|
||||||
if props.sort_column == item.name:
|
if data.sort_column == item.name:
|
||||||
row.label(text="", icon="SORTALPHA")
|
row.label(text="", icon="SORTALPHA")
|
||||||
row.operator("bim.remove_task_column", text="", icon="X").name = item.name
|
row.operator("bim.remove_task_column", text="", icon="X").name = item.name
|
||||||
|
|
||||||
|
|
||||||
class BIM_UL_task_inputs(UIList):
|
class BIM_UL_task_inputs(UIList):
|
||||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
def draw_item(
|
||||||
|
self, context, layout: bpy.types.UILayout, data, item: TaskProduct, icon, active_data, active_propname
|
||||||
|
) -> None:
|
||||||
if item:
|
if item:
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
op = row.operator("bim.select_product", text="", icon="RESTRICT_SELECT_OFF")
|
op = row.operator("bim.select_product", text="", icon="RESTRICT_SELECT_OFF")
|
||||||
@@ -825,7 +831,9 @@ class BIM_UL_task_inputs(UIList):
|
|||||||
|
|
||||||
|
|
||||||
class BIM_UL_task_resources(UIList):
|
class BIM_UL_task_resources(UIList):
|
||||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
def draw_item(
|
||||||
|
self, context, layout: bpy.types.UILayout, data, item: TaskResource, icon, active_data, active_propname
|
||||||
|
) -> None:
|
||||||
if item:
|
if item:
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
row.operator("bim.go_to_resource", text="", icon="STYLUS_PRESSURE").resource = item.ifc_definition_id
|
row.operator("bim.go_to_resource", text="", icon="STYLUS_PRESSURE").resource = item.ifc_definition_id
|
||||||
@@ -834,7 +842,9 @@ class BIM_UL_task_resources(UIList):
|
|||||||
|
|
||||||
|
|
||||||
class BIM_UL_animation_colors(UIList):
|
class BIM_UL_animation_colors(UIList):
|
||||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
def draw_item(
|
||||||
|
self, context, layout: bpy.types.UILayout, data, item: BIMTaskTypeColor, icon, active_data, active_propname
|
||||||
|
) -> None:
|
||||||
if item:
|
if item:
|
||||||
row = layout.row()
|
row = layout.row()
|
||||||
row.prop(item, "color", text="")
|
row.prop(item, "color", text="")
|
||||||
@@ -842,7 +852,9 @@ class BIM_UL_animation_colors(UIList):
|
|||||||
|
|
||||||
|
|
||||||
class BIM_UL_task_outputs(UIList):
|
class BIM_UL_task_outputs(UIList):
|
||||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
def draw_item(
|
||||||
|
self, context, layout: bpy.types.UILayout, data, item: TaskProduct, icon, active_data, active_propname
|
||||||
|
) -> None:
|
||||||
if item:
|
if item:
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
op = row.operator("bim.select_product", text="", icon="RESTRICT_SELECT_OFF")
|
op = row.operator("bim.select_product", text="", icon="RESTRICT_SELECT_OFF")
|
||||||
@@ -851,7 +863,9 @@ class BIM_UL_task_outputs(UIList):
|
|||||||
|
|
||||||
|
|
||||||
class BIM_UL_product_input_tasks(UIList):
|
class BIM_UL_product_input_tasks(UIList):
|
||||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
def draw_item(
|
||||||
|
self, context, layout: bpy.types.UILayout, data, item: TaskProduct, icon, active_data, active_propname
|
||||||
|
) -> None:
|
||||||
if item:
|
if item:
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
op = row.operator("bim.go_to_task", text="", icon="STYLUS_PRESSURE")
|
op = row.operator("bim.go_to_task", text="", icon="STYLUS_PRESSURE")
|
||||||
@@ -861,7 +875,9 @@ class BIM_UL_product_input_tasks(UIList):
|
|||||||
|
|
||||||
|
|
||||||
class BIM_UL_product_output_tasks(UIList):
|
class BIM_UL_product_output_tasks(UIList):
|
||||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
def draw_item(
|
||||||
|
self, context, layout: bpy.types.UILayout, data, item: TaskProduct, icon, active_data, active_propname
|
||||||
|
) -> None:
|
||||||
if item:
|
if item:
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
op = row.operator("bim.go_to_task", text="", icon="STYLUS_PRESSURE")
|
op = row.operator("bim.go_to_task", text="", icon="STYLUS_PRESSURE")
|
||||||
@@ -886,8 +902,8 @@ class BIM_UL_tasks(UIList):
|
|||||||
self,
|
self,
|
||||||
context,
|
context,
|
||||||
layout: bpy.types.UILayout,
|
layout: bpy.types.UILayout,
|
||||||
data: "BIMTaskTreeProperties",
|
data: BIMTaskTreeProperties,
|
||||||
item: "Task",
|
item: Task,
|
||||||
icon,
|
icon,
|
||||||
active_data,
|
active_data,
|
||||||
active_propname,
|
active_propname,
|
||||||
|
|||||||
@@ -512,7 +512,7 @@ class SetContainerVisibility(bpy.types.Operator):
|
|||||||
containers -= set(tool.Ifc.get().by_type("IfcSpatialZone"))
|
containers -= set(tool.Ifc.get().by_type("IfcSpatialZone"))
|
||||||
for container in containers:
|
for container in containers:
|
||||||
if obj := tool.Ifc.get_object(container):
|
if obj := tool.Ifc.get_object(container):
|
||||||
if collection := obj.BIMObjectProperties.collection:
|
if collection := tool.Blender.get_object_bim_props(obj).collection:
|
||||||
collection.hide_viewport = True
|
collection.hide_viewport = True
|
||||||
should_hide = False
|
should_hide = False
|
||||||
else:
|
else:
|
||||||
@@ -523,7 +523,7 @@ class SetContainerVisibility(bpy.types.Operator):
|
|||||||
while queue:
|
while queue:
|
||||||
container = queue.pop()
|
container = queue.pop()
|
||||||
if obj := tool.Ifc.get_object(container):
|
if obj := tool.Ifc.get_object(container):
|
||||||
if collection := obj.BIMObjectProperties.collection:
|
if collection := tool.Blender.get_object_bim_props(obj).collection:
|
||||||
collection.hide_viewport = should_hide
|
collection.hide_viewport = should_hide
|
||||||
if self.should_include_children:
|
if self.should_include_children:
|
||||||
queue.extend(ifcopenshell.util.element.get_parts(container))
|
queue.extend(ifcopenshell.util.element.get_parts(container))
|
||||||
|
|||||||
@@ -251,7 +251,7 @@ class BIM_PT_grids(Panel):
|
|||||||
bl_options = {"HEADER_LAYOUT_EXPAND"}
|
bl_options = {"HEADER_LAYOUT_EXPAND"}
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
self.layout.row().operator("mesh.add_grid", icon="ADD", text="Add Grids")
|
self.layout.row().operator("bim.add_grid", icon="ADD", text="Add Grids")
|
||||||
|
|
||||||
def draw_header(self, context):
|
def draw_header(self, context):
|
||||||
props = tool.Spatial.get_grid_props()
|
props = tool.Spatial.get_grid_props()
|
||||||
@@ -357,7 +357,7 @@ class BIM_UL_elements(UIList):
|
|||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.use_filter_show = True
|
self.use_filter_show = True
|
||||||
|
|
||||||
def draw_toggle(self, row: bpy.types.UILayout, is_expanded: bool, index: int):
|
def draw_toggle(self, row: bpy.types.UILayout, is_expanded: bool, index: int) -> None:
|
||||||
icon_id = "DISCLOSURE_TRI_DOWN" if is_expanded else "DISCLOSURE_TRI_RIGHT"
|
icon_id = "DISCLOSURE_TRI_DOWN" if is_expanded else "DISCLOSURE_TRI_RIGHT"
|
||||||
row.operator("bim.toggle_container_element", text="", emboss=False, icon=icon_id).element_index = index
|
row.operator("bim.toggle_container_element", text="", emboss=False, icon=icon_id).element_index = index
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user