mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-21 04:32:23 +00:00
Compare commits
287 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9a871f1459 | |||
| 0d401c9272 | |||
| 2129350ab7 | |||
| 43bc979114 | |||
| c7232a780a | |||
| e96b4c11a2 | |||
| fecca6a8b2 | |||
| 1fc56585dd | |||
| d1be0e8c1c | |||
| dcc568f5a3 | |||
| 2e4a2712e9 | |||
| d4b055aeb2 | |||
| 0e2cc463c8 | |||
| c3930a6573 | |||
| 3c094b7bd2 | |||
| f8dffa1a9b | |||
| 03b6c08c50 | |||
| 42b96d6290 | |||
| c240f0582a | |||
| 76b7748be1 | |||
| a2334f4ce0 | |||
| db50c87bf4 | |||
| dc8766cf0a | |||
| 158f3d2962 | |||
| 008fa70345 | |||
| e59cd80868 | |||
| f0d7965d49 | |||
| b616cafa49 | |||
| 70bb0e5481 | |||
| 07060fc769 | |||
| b5d613989e | |||
| f604aa8b6b | |||
| dc8912c128 | |||
| 07a0b4fe2d | |||
| eb67ffcbec | |||
| 4ec8345716 | |||
| 56d3b2ca36 | |||
| dd06b1a6c4 | |||
| 3469a9caad | |||
| 599ca56496 | |||
| 14dbe121f6 | |||
| dee936b2c3 | |||
| a98cddf676 | |||
| 48d058a32a | |||
| 98afb72ba3 | |||
| 2d5be6cf89 | |||
| 1299a8f512 | |||
| eccbdfa76e | |||
| 24e16d97f3 | |||
| 13d61f0135 | |||
| c56a4a6c9d | |||
| 832dce462e | |||
| 6f45373edd | |||
| d5613e0e26 | |||
| d0ec4fe345 | |||
| 665bf00f8b | |||
| 2d2ca64a72 | |||
| 60cb41a428 | |||
| a69f3e8da9 | |||
| 2f81cab5f1 | |||
| 78e81542d9 | |||
| 7bbc81b7c3 | |||
| 6e6b256c22 | |||
| d23eba9343 | |||
| e899104d56 | |||
| de00429df6 | |||
| 3aba82f608 | |||
| cb85b690e7 | |||
| 527742ca93 | |||
| b85dcdb437 | |||
| f740ca3cf1 | |||
| b9269b5a11 | |||
| 16e98e1400 | |||
| 4976074b18 | |||
| e670a7ca71 | |||
| d892ad956c | |||
| ac42ed7bab | |||
| bf677b839d | |||
| 464815b045 | |||
| ae91c2b9f0 | |||
| 39cd980008 | |||
| f364643d7e | |||
| ef9bbd8de6 | |||
| 8172096a5b | |||
| 46d3d3afe1 | |||
| 39cd4187ea | |||
| d6a3bc3556 | |||
| ce9e4f2854 | |||
| 7700e8016b | |||
| 6e1ebb02ac | |||
| d2e7e3daaf | |||
| 5d6215d889 | |||
| 17eb45fb4e | |||
| 0a06d58c42 | |||
| a65c6b6d3f | |||
| c5262274eb | |||
| 65f94bcc0c | |||
| 804bbc47c3 | |||
| a967665cbd | |||
| cbd017aceb | |||
| 8d9d142bc2 | |||
| f6c30f2ddf | |||
| d2a27999ba | |||
| 2738b280a1 | |||
| c8946a19f2 | |||
| 6b1932c8d9 | |||
| a133b51dcc | |||
| 6155edef0d | |||
| 439595f04c | |||
| c1fa9fe28e | |||
| 54a0a5b454 | |||
| bace73f854 | |||
| 8da68f599b | |||
| 278524ee3e | |||
| a9912f351f | |||
| 1e5c51e5b2 | |||
| 44100f3c46 | |||
| 713e9c8e14 | |||
| e6238a1f75 | |||
| cfd440dae9 | |||
| a0e5d350bd | |||
| c37240cf96 | |||
| 115844279f | |||
| cbb7c5620d | |||
| 6d5d15793b | |||
| 8ac6f59394 | |||
| 5d8addf1f8 | |||
| 9b9301cbb0 | |||
| 4d9b461776 | |||
| 36f7cdacbd | |||
| feb0e46257 | |||
| 0b901bff5f | |||
| b45b0f2c81 | |||
| 7a36c05c30 | |||
| ac58e6b8b2 | |||
| 68d3771a83 | |||
| 80349d70d8 | |||
| e295d596fe | |||
| 013b138e5c | |||
| 95d2d0af83 | |||
| 03ee826e67 | |||
| 9f31815d9e | |||
| 1e9bfec655 | |||
| fd8cb7e8ed | |||
| 4ef237e0f7 | |||
| 3c48cfbc19 | |||
| 177d43c30e | |||
| a6c3ae8446 | |||
| d0373da0ad | |||
| 7bb3de8371 | |||
| 45e0045820 | |||
| 347b2b783d | |||
| 2fc7cc7de2 | |||
| 4d39804045 | |||
| 746051815d | |||
| 4385bf0bcc | |||
| 1e092c140f | |||
| c4ae9578e9 | |||
| a834e5b4cd | |||
| e329b18e33 | |||
| 5731c4036a | |||
| a594a6cfe2 | |||
| 8b6235f42f | |||
| d443c5e2a6 | |||
| 68d3539a86 | |||
| 301cdf6504 | |||
| 882e0a4ae9 | |||
| f921b65953 | |||
| 1e26587bd3 | |||
| bce6485b91 | |||
| a0cb980a8f | |||
| fc0e9e900f | |||
| d700262e7f | |||
| d92679223e | |||
| 7ac117cd8d | |||
| b87a6bc15c | |||
| fbefe8c555 | |||
| 030119fd0e | |||
| fcf2c37432 | |||
| fab3ee70dd | |||
| 4738528e69 | |||
| 88c8bcf119 | |||
| 3c3e4dd047 | |||
| b44b1cb8b0 | |||
| 6ad2bef05c | |||
| 8e0c92d5eb | |||
| 6bafbd92f3 | |||
| 03663eec3f | |||
| a3d4642ea9 | |||
| 0e42130e9d | |||
| 691fe549d7 | |||
| f5979be4d5 | |||
| 9fe40c3137 | |||
| 82496e8126 | |||
| 9c22e6ecc8 | |||
| 17a433a97b | |||
| b90c1ee7c9 | |||
| bd30f73b3a | |||
| 0aad3d99b5 | |||
| f7d55a4a4a | |||
| df2655e7ae | |||
| 9ec51e040e | |||
| 467602aec3 | |||
| 88610e23b2 | |||
| 89fb59307c | |||
| 65a1fbea78 | |||
| 78bc8fe977 | |||
| 6ab08a2f49 | |||
| de7d315eb3 | |||
| 24662d75e4 | |||
| 76e6697bbc | |||
| 3c2fcffd0d | |||
| 0188757197 | |||
| 444554a12d | |||
| ba42f01db2 | |||
| 5d91766f07 | |||
| c4cbc577d8 | |||
| bfd9cc0cf0 | |||
| ac0b5abfc5 | |||
| ad61cc3c92 | |||
| 6fa586a1b6 | |||
| 4c09773211 | |||
| 2300411ce4 | |||
| 9c61a9eed7 | |||
| 743a197542 | |||
| 99f329c13d | |||
| 623d487df7 | |||
| 348ef4023f | |||
| b253b74e88 | |||
| a3b6a6ea39 | |||
| 8246d2c593 | |||
| 3a6cf0e5ae | |||
| 1db26aab15 | |||
| a23e6dcd66 | |||
| 9331292dd1 | |||
| cad7db0162 | |||
| 9f356542bc | |||
| 1f7d80e6f1 | |||
| f3f1e8721c | |||
| 114fca8be5 | |||
| 52167a47b9 | |||
| d4ff2c8dda | |||
| 75f8e75a8e | |||
| d0f612afc2 | |||
| b6b681c244 | |||
| 5911a602b9 | |||
| 453ef437d3 | |||
| 88494d3379 | |||
| 1b41440aba | |||
| 105434fe68 | |||
| a0a505d64b | |||
| 3a4bdd623d | |||
| 99338187de | |||
| 9d7ef6ff47 | |||
| ffd944f4f9 | |||
| ecef745c3b | |||
| 44507785ac | |||
| 79c0246e6a | |||
| 3c659a342f | |||
| d192217e78 | |||
| 6474552649 | |||
| 4b454dd646 | |||
| 85e1786ce5 | |||
| 45b81ab814 | |||
| 8241b557fe | |||
| 1312a8e704 | |||
| 2a5e42e359 | |||
| dae7d86dad | |||
| 9906149b0d | |||
| 2e2c116180 | |||
| 13c6b9d673 | |||
| 2fbac57faf | |||
| f803447651 | |||
| bec834c651 | |||
| 3df75e9af3 | |||
| 6630db21ee | |||
| f54f45a051 | |||
| acd4274136 | |||
| d400a48a9d | |||
| 4331f4f785 | |||
| 95e36979d5 | |||
| 07827940cc | |||
| c2efcda7bb | |||
| aa0fbd434e | |||
| 8cff046a46 | |||
| ff4154c2bd | |||
| d7e497727c |
@@ -1,24 +1,8 @@
|
||||
name: ci-bcf-pypi
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# ┌───────────── minute (0 - 59)
|
||||
# │ ┌───────────── hour (0 - 23)
|
||||
# │ │ ┌───────────── day of the month (1 - 31)
|
||||
# │ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
|
||||
# │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
|
||||
# * * * * *
|
||||
- cron: "0 0 18 * *"
|
||||
push:
|
||||
paths:
|
||||
- '.github/workflows/ci-bcf-pypi.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
major: 0
|
||||
minor: 0
|
||||
name: ifcopenshell
|
||||
|
||||
jobs:
|
||||
activate:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -54,4 +38,4 @@ jobs:
|
||||
with:
|
||||
user: __token__
|
||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||
packages_dir: src/bcf/dist
|
||||
packages_dir: src/bcf/dist
|
||||
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
pyver: [py310]
|
||||
pyver: [py311]
|
||||
config:
|
||||
- {
|
||||
name: "Windows Build",
|
||||
@@ -43,33 +43,37 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
|
||||
with:
|
||||
python-version: '3.10.9' # Version range or exact version of a Python version to use, using SemVer's version range syntax
|
||||
python-version: '3.11.9' # Version range or exact version of a Python version to use, using SemVer's version range syntax
|
||||
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
|
||||
- run: echo ${{ env.DATE }}
|
||||
|
||||
- name: Check in published releases if we should do a choco release
|
||||
- name: Check in release tags if we should do a choco release
|
||||
id: do_choco
|
||||
run: |
|
||||
echo "::set-output name=choco_release::$(python3 /home/runner/work/IfcOpenShell/IfcOpenShell/choco/blenderbim/check_repo_infos.py --do_choco_release?)"
|
||||
cd /home/runner/work/IfcOpenShell/IfcOpenShell/choco/blenderbim/ &&
|
||||
echo "::set-output name=choco_release::$(python3 check_release_needed.py)"
|
||||
|
||||
- name: Fill chocolatey scripts on win with latest blender python
|
||||
if: ${{steps.do_choco.outputs.choco_release}} == 'do_choco_release'
|
||||
run: |
|
||||
cd /home/runner/work/IfcOpenShell/IfcOpenShell/choco/blenderbim/ &&
|
||||
yesterday_non_iso="$(date --date='yesterday' +'%y%m%d' | tr -d '\n')" &&
|
||||
target_os=${{ matrix.config.short_name }} &&
|
||||
latest_blender_python_version_maj_min=$(python3 /home/runner/work/IfcOpenShell/IfcOpenShell/choco/blenderbim/check_repo_infos.py --latest_blender_python_version_maj_min?) &&
|
||||
latest_blender_python_version_maj_min=$(python3 check_repo_infos.py --latest_blender_python_version_maj_min?) &&
|
||||
echo "latest_blender_python_version_maj_min?: $latest_blender_python_version_maj_min" &&
|
||||
pyver=$(python3 /home/runner/work/IfcOpenShell/IfcOpenShell/choco/blenderbim/check_repo_infos.py --pyver?) &&
|
||||
export latest_blender_version_maj_min=$(python3 /home/runner/work/IfcOpenShell/IfcOpenShell/choco/blenderbim/check_repo_infos.py --latest_blender_release_maj_min?) &&
|
||||
export latest_blender_version_maj_min_pat=$(python3 /home/runner/work/IfcOpenShell/IfcOpenShell/choco/blenderbim/check_repo_infos.py --latest_blender_release_maj_min_pat?) &&
|
||||
export latest_blender_version_maj_min=$(python3 check_repo_infos.py --latest_blender_release_maj_min?) &&
|
||||
export latest_blender_version_maj_min_pat=$(python3 check_repo_infos.py --latest_blender_release_maj_min_pat?) &&
|
||||
echo latest_blender_version_maj_min_pat?: $latest_blender_version_maj_min_pat &&
|
||||
export blenderbim_build_version="${{ env.major }}.${{ env.minor }}.$yesterday_non_iso" &&
|
||||
export url_blenderbim_py310_win_zip="https://github.com/IfcOpenShell/IfcOpenShell/releases/download/blenderbim-$yesterday_non_iso/blenderbim-$yesterday_non_iso-$pyver-$target_os.zip" &&
|
||||
wget $url_blenderbim_py310_win_zip --no-verbose &&
|
||||
export sha256sum_blenderbim_py310_win_zip=$( sha256sum blenderbim-$yesterday_non_iso-$pyver-$target_os.zip --tag | cut -d ' ' -f 4 | tr -d '\n') &&
|
||||
echo sha256sum_blenderbim_py310_win_zip: $sha256sum_blenderbim_py310_win_zip &&
|
||||
python3 choco/blenderbim/fill_dynamic_parameters.py &&
|
||||
export url_blenderbim_py3x_win_zip="https://github.com/IfcOpenShell/IfcOpenShell/releases/download/$target_release_tag/$target_release_tag-$pyver-$target_os.zip" &&
|
||||
wget $url_blenderbim_py3x_win_zip --no-verbose &&
|
||||
export sha256sum_blenderbim_py3x_win_zip=$( sha256sum $target_release_tag-$pyver-$target_os.zip --tag | cut -d ' ' -f 4 | tr -d '\n') &&
|
||||
echo sha256sum_blenderbim_py3x_win_zip: $sha256sum_blenderbim_py3x_win_zip &&
|
||||
python3 fill_dynamic_parameters.py &&
|
||||
echo __build choco with mono &&
|
||||
mkdir /tmp/choco/ &&
|
||||
cd /tmp/choco/ &&
|
||||
wget "https://github.com/chocolatey/choco/archive/refs/tags/$choco_version.tar.gz" --quiet &&
|
||||
tar -xzf "$choco_version.tar.gz" &&
|
||||
cd choco-$choco_version &&
|
||||
@@ -78,7 +82,6 @@ jobs:
|
||||
cp -r build_output/chocolatey /opt/chocolatey &&
|
||||
cd /home/runner/work/IfcOpenShell/IfcOpenShell/choco/blenderbim/ &&
|
||||
echo __choco pack &&
|
||||
mono /opt/chocolatey/choco.exe --version &&
|
||||
mono /opt/chocolatey/choco.exe pack --allow-unofficial &&
|
||||
echo __choco set apiKey &&
|
||||
mono /opt/chocolatey/choco.exe setapikey --key="$CHOCO_TOKEN" --source="https://push.chocolatey.org/" --allow-unofficial # &&
|
||||
@@ -89,4 +92,3 @@ jobs:
|
||||
if: ${{steps.do_choco.outputs.choco_release}} != 'do_choco_release'
|
||||
run: |
|
||||
echo "no releases found today ${{ env.DATE }}, therefore choco packaging is skipped."
|
||||
|
||||
|
||||
@@ -1,21 +1,8 @@
|
||||
name: ci-bsdd-pypi
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# ┌───────────── minute (0 - 59)
|
||||
# │ ┌───────────── hour (0 - 23)
|
||||
# │ │ ┌───────────── day of the month (1 - 31)
|
||||
# │ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
|
||||
# │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
|
||||
# * * * * *
|
||||
- cron: "0 0 18 * *"
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
major: 0
|
||||
minor: 0
|
||||
name: ifcopenshell
|
||||
|
||||
jobs:
|
||||
activate:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
name: ci-ifc4d-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@v2
|
||||
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
|
||||
with:
|
||||
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
|
||||
- name: Compile
|
||||
run: |
|
||||
pip install build
|
||||
cd src/ifc4d &&
|
||||
make dist
|
||||
- name: Publish a Python distribution to PyPI
|
||||
uses: ortega2247/pypi-upload-action@master
|
||||
with:
|
||||
user: __token__
|
||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||
packages_dir: src/ifc4d/dist
|
||||
@@ -0,0 +1,35 @@
|
||||
name: ci-ifc5d-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@v2
|
||||
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
|
||||
with:
|
||||
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
|
||||
- name: Compile
|
||||
run: |
|
||||
pip install build
|
||||
cd src/ifc5d &&
|
||||
make dist
|
||||
- name: Publish a Python distribution to PyPI
|
||||
uses: ortega2247/pypi-upload-action@master
|
||||
with:
|
||||
user: __token__
|
||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||
packages_dir: src/ifc5d/dist
|
||||
@@ -0,0 +1,35 @@
|
||||
name: ci-ifccityjson-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@v2
|
||||
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
|
||||
with:
|
||||
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
|
||||
- name: Compile
|
||||
run: |
|
||||
pip install build
|
||||
cd src/ifccityjson &&
|
||||
make dist
|
||||
- name: Publish a Python distribution to PyPI
|
||||
uses: ortega2247/pypi-upload-action@master
|
||||
with:
|
||||
user: __token__
|
||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||
packages_dir: src/ifccityjson/dist
|
||||
@@ -1,21 +1,8 @@
|
||||
name: ci-ifcclash-pypi
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# ┌───────────── minute (0 - 59)
|
||||
# │ ┌───────────── hour (0 - 23)
|
||||
# │ │ ┌───────────── day of the month (1 - 31)
|
||||
# │ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
|
||||
# │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
|
||||
# * * * * *
|
||||
- cron: "0 0 18 * *"
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
major: 0
|
||||
minor: 0
|
||||
name: ifcopenshell
|
||||
|
||||
jobs:
|
||||
activate:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -1,21 +1,8 @@
|
||||
name: ci-ifccsv-pypi
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# ┌───────────── minute (0 - 59)
|
||||
# │ ┌───────────── hour (0 - 23)
|
||||
# │ │ ┌───────────── day of the month (1 - 31)
|
||||
# │ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
|
||||
# │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
|
||||
# * * * * *
|
||||
- cron: "0 0 18 * *"
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
major: 0
|
||||
minor: 0
|
||||
name: ifcopenshell
|
||||
|
||||
jobs:
|
||||
activate:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
name: ci-ifcdiff-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@v2
|
||||
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
|
||||
with:
|
||||
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
|
||||
- name: Compile
|
||||
run: |
|
||||
pip install build
|
||||
cd src/ifcdiff &&
|
||||
make dist
|
||||
- name: Publish a Python distribution to PyPI
|
||||
uses: ortega2247/pypi-upload-action@master
|
||||
with:
|
||||
user: __token__
|
||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||
packages_dir: src/ifcdiff/dist
|
||||
@@ -0,0 +1,35 @@
|
||||
name: ci-ifcfm-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@v2
|
||||
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
|
||||
with:
|
||||
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
|
||||
- name: Compile
|
||||
run: |
|
||||
pip install build
|
||||
cd src/ifcfm &&
|
||||
make dist
|
||||
- name: Publish a Python distribution to PyPI
|
||||
uses: ortega2247/pypi-upload-action@master
|
||||
with:
|
||||
user: __token__
|
||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||
packages_dir: src/ifcfm/dist
|
||||
@@ -0,0 +1,101 @@
|
||||
name: ci-ifcopenshell-conda-daily-cleaner
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
# ┌───────────── minute (0 - 59)
|
||||
# │ ┌───────────── hour (0 - 23)
|
||||
# │ │ ┌───────────── day of the month (1 - 31)
|
||||
# │ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
|
||||
# │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
|
||||
# * * * * *
|
||||
- cron: "00 23 * * *" # 11min before utc midnight every day
|
||||
|
||||
env:
|
||||
ANACONDA_TOKEN: ${{ secrets.ANACONDA_TOKEN }}
|
||||
NUM_SUPPORTED_VERSIONS: 5
|
||||
|
||||
jobs:
|
||||
activate:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
shell: bash -l {0}
|
||||
if: |
|
||||
github.repository == 'IfcOpenShell/IfcOpenShell'
|
||||
steps:
|
||||
- uses: mamba-org/setup-micromamba@v1 # https://github.com/mamba-org/setup-micromamba
|
||||
with:
|
||||
environment-name: test-env
|
||||
create-args: >-
|
||||
python=3.11
|
||||
anaconda-client=1.12.3
|
||||
|
||||
- name: Run conda cleaner
|
||||
run: |
|
||||
python - << EOF
|
||||
|
||||
import os
|
||||
from datetime import datetime, timedelta
|
||||
from binstar_client.utils import get_server_api
|
||||
from binstar_client.errors import BinstarError
|
||||
|
||||
# Configuration
|
||||
api_token = os.environ.get('ANACONDA_TOKEN')
|
||||
pkg_name = 'ifcopenshell'
|
||||
channel_name = 'ifcopenshell'
|
||||
|
||||
# Authenticate with Anaconda
|
||||
aserver_api = get_server_api(token=api_token)
|
||||
|
||||
|
||||
# Get the list of packages in the channel
|
||||
def get_package(filter_package_name: str = None):
|
||||
try:
|
||||
user_packages = aserver_api.user_packages(channel_name)
|
||||
if filter_package_name:
|
||||
user_packages = [pkg for pkg in user_packages if pkg['name'] == filter_package_name]
|
||||
if len(user_packages) == 0:
|
||||
print(f"No packages found for {filter_package_name}.")
|
||||
if len(user_packages) > 1:
|
||||
raise ValueError(f"Found {len(user_packages)} package for {filter_package_name}. Will only support 1 package.")
|
||||
|
||||
return user_packages[0]
|
||||
except BinstarError as err:
|
||||
raise ValueError(f"Failed to fetch packages: {err}")
|
||||
|
||||
|
||||
# Delete a package version
|
||||
def delete_package(package_name, version):
|
||||
try:
|
||||
aserver_api.remove_release(channel_name, package_name, version)
|
||||
print(f"Deleted {package_name} version {version}")
|
||||
except BinstarError as err:
|
||||
print(f"Failed to delete {package_name} version {version}: {err}")
|
||||
|
||||
|
||||
# Main logic
|
||||
def main():
|
||||
package = get_package(pkg_name)
|
||||
if not package:
|
||||
print("No packages found.")
|
||||
return
|
||||
|
||||
number_of_supported_versions = ${{ env.NUM_SUPPORTED_VERSIONS }}
|
||||
|
||||
package_name = package['name']
|
||||
versions = package["versions"]
|
||||
if len(versions) <= number_of_supported_versions:
|
||||
print(f"Number of versions {len(versions)} is less than or equal to {number_of_supported_versions}.")
|
||||
return
|
||||
|
||||
# sort the versions in descending order
|
||||
versions = sorted(versions, reverse=True)
|
||||
releases = versions[number_of_supported_versions:]
|
||||
|
||||
for release in releases:
|
||||
delete_package(package_name, release)
|
||||
|
||||
main()
|
||||
|
||||
EOF
|
||||
@@ -2,21 +2,44 @@ name: ci-ifcopenshell-conda-daily-v0.8.0
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- v0.8.0
|
||||
schedule:
|
||||
# ┌───────────── minute (0 - 59)
|
||||
# │ ┌───────────── hour (0 - 23)
|
||||
# │ │ ┌───────────── day of the month (1 - 31)
|
||||
# │ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
|
||||
# │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
|
||||
# * * * * *
|
||||
- cron: "49 23 * * *" # 11min before utc midnight every day
|
||||
|
||||
jobs:
|
||||
activate:
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
github.repository == 'IfcOpenShell/IfcOpenShell'
|
||||
outputs:
|
||||
version: ${{ steps.version.outputs.version }}
|
||||
date: ${{ steps.date.outputs.date }}
|
||||
verdate: ${{ steps.verdate.outputs.verdate }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set env
|
||||
run: echo ok go
|
||||
|
||||
- name: Get current version
|
||||
id: version
|
||||
run: echo "version=$(cat VERSION)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Get current date
|
||||
id: date
|
||||
run: echo "date=$(date +'%y%m%d')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Version + date str
|
||||
id: verdate
|
||||
run: echo "verdate='${{ steps.version.outputs.version }}.${{ steps.date.outputs.date }}'" >> $GITHUB_OUTPUT
|
||||
|
||||
|
||||
test:
|
||||
name: ${{ matrix.platform.distver }}-${{ matrix.pyver.name }}
|
||||
name: ${{ matrix.platform.distver }}-${{ matrix.pyver.name }}-${{ matrix.variant }}
|
||||
needs: activate
|
||||
runs-on: ${{ matrix.platform.distver }}
|
||||
defaults:
|
||||
@@ -25,8 +48,9 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
variant: [novtk, all]
|
||||
pyver: [
|
||||
{ name: py311, distver: '3.11'},
|
||||
# { name: py311, distver: '3.11'},
|
||||
{ name: py312, distver: '3.12'}
|
||||
]
|
||||
platform: [
|
||||
@@ -35,24 +59,28 @@ jobs:
|
||||
{ name: macOS, distver: macos-latest, pkg_dir: 'osx-64' }
|
||||
]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up swap space
|
||||
if: runner.os == 'Linux'
|
||||
uses: pierotofy/set-swap-space@v1.0
|
||||
with:
|
||||
swap-size-gb: 10
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Download and extract MacOSX SDK
|
||||
if: ${{ matrix.platform.name == 'macOS' }}
|
||||
if: ${{ runner.os == 'macOS' }}
|
||||
run: |
|
||||
curl -o MacOSX10.13.sdk.tar.xz -L https://github.com/phracker/MacOSX-SDKs/releases/download/11.3/MacOSX10.13.sdk.tar.xz && \
|
||||
tar xf MacOSX10.13.sdk.tar.xz && \
|
||||
sudo mv -v MacOSX10.13.sdk /opt/ && \
|
||||
ls /opt/
|
||||
- uses: seanmiddleditch/gha-setup-ninja@master
|
||||
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@v1 # https://github.com/mamba-org/setup-micromamba
|
||||
with:
|
||||
environment-file: conda/environment.build.yml
|
||||
environment-name: test-env
|
||||
create-args: >-
|
||||
python=${{ matrix.pyver.distver }}
|
||||
python=3.11
|
||||
anaconda-client
|
||||
boa
|
||||
|
||||
- name: create conda package dist dir
|
||||
run: |
|
||||
@@ -60,8 +88,10 @@ jobs:
|
||||
|
||||
- name: build & test ifcopenshell
|
||||
run: |
|
||||
boa build . --python ${{ matrix.pyver.distver }} --no-remove-work-dir --output-folder '${{ github.workspace }}/dist'
|
||||
conda mambabuild . --python ${{ matrix.pyver.distver }} --no-remove-work-dir --output-folder '${{ github.workspace }}/dist' --variants='{variant: ${{ matrix.variant }}}'
|
||||
working-directory: ./conda
|
||||
env:
|
||||
VERSION_OVERRIDE: ${{ needs.activate.outputs.verdate }}
|
||||
|
||||
- name: upload to anaconda
|
||||
if: ${{ matrix.platform.name == 'win' }}
|
||||
@@ -71,4 +101,4 @@ jobs:
|
||||
- name: upload to anaconda
|
||||
if: ${{ matrix.platform.name != 'win' }}
|
||||
run: |
|
||||
anaconda -t ${{ secrets.ANACONDA_TOKEN }} upload --force --user ifcopenshell ${{ github.workspace }}/dist/${{ matrix.platform.pkg_dir }}/*.tar.bz2
|
||||
anaconda -t ${{ secrets.ANACONDA_TOKEN }} upload --force --user ifcopenshell ${{ github.workspace }}/dist/${{ matrix.platform.pkg_dir }}/*.tar.bz2
|
||||
|
||||
@@ -6,7 +6,6 @@ on:
|
||||
tags:
|
||||
- v0.**
|
||||
|
||||
|
||||
jobs:
|
||||
|
||||
activate:
|
||||
|
||||
@@ -27,19 +27,19 @@ jobs:
|
||||
pyver: [py39, py310, py311, py312]
|
||||
config:
|
||||
- {
|
||||
name: "Windows Build",
|
||||
name: "Windows 64bit",
|
||||
short_name: win64,
|
||||
}
|
||||
- {
|
||||
name: "Linux Build",
|
||||
name: "Linux 64bit",
|
||||
short_name: linux64
|
||||
}
|
||||
- {
|
||||
name: "MacOS Build",
|
||||
name: "MacOS Intel 64bit",
|
||||
short_name: macos64
|
||||
}
|
||||
- {
|
||||
name: "MacOS ARM Build",
|
||||
name: "MacOS Silicon 64bit",
|
||||
short_name: macosm164
|
||||
}
|
||||
steps:
|
||||
|
||||
@@ -22,19 +22,23 @@ jobs:
|
||||
pyver: [py39, py310, py311, py312]
|
||||
config:
|
||||
- {
|
||||
name: "Windows Build",
|
||||
name: "Windows 64bit",
|
||||
short_name: win64,
|
||||
}
|
||||
- {
|
||||
name: "Linux Build",
|
||||
name: "Windows 32bit",
|
||||
short_name: win32,
|
||||
}
|
||||
- {
|
||||
name: "Linux 64bit",
|
||||
short_name: linux64
|
||||
}
|
||||
- {
|
||||
name: "MacOS Build",
|
||||
name: "MacOS Intel 64bit",
|
||||
short_name: macos64
|
||||
}
|
||||
- {
|
||||
name: "MacOS ARM Build",
|
||||
name: "MacOS Silicon 64bit",
|
||||
short_name: macosm164
|
||||
}
|
||||
steps:
|
||||
|
||||
@@ -1,21 +1,8 @@
|
||||
name: ci-ifcpatch-pypi
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# ┌───────────── minute (0 - 59)
|
||||
# │ ┌───────────── hour (0 - 23)
|
||||
# │ │ ┌───────────── day of the month (1 - 31)
|
||||
# │ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
|
||||
# │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
|
||||
# * * * * *
|
||||
- cron: "0 0 18 * *"
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
major: 0
|
||||
minor: 0
|
||||
name: ifcopenshell
|
||||
|
||||
jobs:
|
||||
activate:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -46,6 +46,6 @@ jobs:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
file: src/ifcsverchok/dist/ifcsverchok-${{steps.version.outputs.version}}.${{steps.date.outputs.date}}.zip
|
||||
asset_name: ifcsverchok-${{steps.version.outputs.version}}.${{steps.date.outputs.date}}.zip
|
||||
release_name: "ifcsverchok-${{steps.version.outputs.version}}.${{steps.date.outputs.date}}"
|
||||
release_name: "ifcsverchok-${{steps.version.outputs.version}}.${{steps.date.outputs.date}} (unstable)"
|
||||
tag: "ifcsverchok-${{steps.version.outputs.version}}.${{steps.date.outputs.date}}"
|
||||
overwrite: true
|
||||
|
||||
@@ -1,21 +1,8 @@
|
||||
name: ci-ifctester-pypi
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# ┌───────────── minute (0 - 59)
|
||||
# │ ┌───────────── hour (0 - 23)
|
||||
# │ │ ┌───────────── day of the month (1 - 31)
|
||||
# │ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
|
||||
# │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
|
||||
# * * * * *
|
||||
- cron: "0 0 18 * *"
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
major: 0
|
||||
minor: 0
|
||||
name: ifcopenshell
|
||||
|
||||
jobs:
|
||||
activate:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
name: Restart failed daily conda builds
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: [ ci-ifcopenshell-conda-daily ]
|
||||
types:
|
||||
- completed
|
||||
|
||||
env:
|
||||
REPO_OWNER: IfcOpenShell/IfcOpenShell
|
||||
MY_WORKFLOW: ci-ifcopenshell-conda-daily
|
||||
|
||||
jobs:
|
||||
restart-failed-runs:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.workflow_run.conclusion == 'failure' }}
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Use python 3.11
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.11
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install requests
|
||||
- name: Check for failed runs
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
WORKFLOW_RUN_ID: ${{ github.event.workflow_run.id }}
|
||||
run: |
|
||||
python check_repo_ci_jobs.py
|
||||
working-directory: .github/workflows
|
||||
@@ -30,7 +30,7 @@ For more information, see:
|
||||
| Service | Status |
|
||||
| ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Anaconda Daily Build | [](https://anaconda.org/ifcopenshell/ifcopenshell) |
|
||||
| Anaconda v0.7.0 Stable | [](https://anaconda.org/conda-forge/ifcopenshell) |
|
||||
| Anaconda v0.8.0 Stable | [](https://anaconda.org/conda-forge/ifcopenshell) |
|
||||
| PyPi Daily Build | [](https://pypi.org/project/ifcopenshell/) |
|
||||
| ArchLinux AUR Package Stable | [](https://aur.archlinux.org/packages/ifcopenshell) |
|
||||
| ArchLinux AUR Package git | [](https://aur.archlinux.org/packages/ifcopenshell-git) |
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<projectUrl>https://github.com/IfcOpenShell/IfcOpenShell</projectUrl>
|
||||
<iconUrl>https://rawcdn.githack.com/IfcOpenShell/IfcOpenShell/c6ee1d1679d1298de0d7447ff108c22709c68e54/choco/blenderbim/blenderbim.png</iconUrl>
|
||||
<!-- <copyright>Year Software Vendor</copyright> -->
|
||||
<licenseUrl>https://github.com/IfcOpenShell/IfcOpenShell/blob/v0.7.0/COPYING</licenseUrl>
|
||||
<licenseUrl>https://github.com/IfcOpenShell/IfcOpenShell/blob/v0.8.0/COPYING</licenseUrl>
|
||||
<requireLicenseAcceptance>true</requireLicenseAcceptance>
|
||||
<projectSourceUrl>https://github.com/IfcOpenShell/IfcOpenShell</projectSourceUrl>
|
||||
<docsUrl>https://docs.blenderbim.org/</docsUrl>
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import datetime
|
||||
import os
|
||||
|
||||
|
||||
def get_repo_tag_names():
|
||||
git_return = os.popen("git tag -l").read()
|
||||
return [tag_name for tag_name in git_return.split("\n") if tag_name]
|
||||
|
||||
|
||||
def set_github_env_var(key: str, value: str):
|
||||
env_file_path = os.getenv('GITHUB_ENV')
|
||||
with open(env_file_path, "a") as env_file:
|
||||
_ = env_file.write(f"{key}={value}" + "\n")
|
||||
|
||||
|
||||
blenderbim_date_yesterday = (datetime.datetime.now() - datetime.timedelta(days=1)).strftime("%y%m%d")
|
||||
|
||||
for tag_name in get_repo_tag_names():
|
||||
if blenderbim_date_yesterday in tag_name:
|
||||
set_github_env_var(key="choco_release", value="do_choco_release")
|
||||
set_github_env_var(key="target_release_tag", value=tag_name)
|
||||
print("do_choco_release", end="")
|
||||
break
|
||||
@@ -1,4 +1,4 @@
|
||||
import datetime
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from urllib import request
|
||||
@@ -24,24 +24,14 @@ def get_latest_blender_version() -> list:
|
||||
return re.findall(RE_BLENDER_VERSION_MIN_MAJ_PAT, html_txt)
|
||||
|
||||
|
||||
URL_IFCOS_RELEASES = "https://github.com/IfcOpenShell/IfcOpenShell/releases"
|
||||
URL_CHOCO_PACKAGE = "https://community.chocolatey.org/packages/blender"
|
||||
URL_BLENDER_CMAKE = "https://raw.githubusercontent.com/blender/blender/{}/build_files/cmake/Modules/FindPythonLibsUnix.cmake"
|
||||
RE_BLENDER_VERSION_MIN_MAJ = r"Latest Version.+<span>Blender (\d+\.\d+)\..+</span>"
|
||||
RE_BLENDER_VERSION_MIN_MAJ_PAT = r"Latest Version.+<span>Blender (\d+\.\d+\.\d+)</span>"
|
||||
RE_BLENDER_PYTHON_VERSION_MAJ_MIN = r"(?:set|SET)\(_PYTHON_VERSION_SUPPORTED (\d+\.\d+)\)"
|
||||
RE_BLENDER_PYTHON_VERSION_MAJ_MIN = r"\(_PYTHON_VERSION_SUPPORTED (\d+\.\d+)\)"
|
||||
|
||||
|
||||
if sys.argv[1] == "--do_choco_release?":
|
||||
now = datetime.datetime.now()
|
||||
blenderbim_date = (now - datetime.timedelta(days=1)).strftime("%y%m%d")
|
||||
resp = request_repo_info(URL_IFCOS_RELEASES)
|
||||
text = str(resp.read())
|
||||
if blenderbim_date in text:
|
||||
print("do_choco_release", end="")
|
||||
|
||||
|
||||
elif sys.argv[1] == "--latest_blender_release_maj_min_pat?":
|
||||
if sys.argv[1] == "--latest_blender_release_maj_min_pat?":
|
||||
latest_blender_version = get_latest_blender_version()
|
||||
if latest_blender_version:
|
||||
print(latest_blender_version[0], end="")
|
||||
@@ -86,4 +76,3 @@ elif sys.argv[1] == "--pyver?":
|
||||
else:
|
||||
print("[ERROR] could not determine pyver")
|
||||
quit(1)
|
||||
|
||||
|
||||
@@ -17,8 +17,8 @@ topics = {
|
||||
"install": {
|
||||
"path": HERE_DIR / "tools" / "chocolateyinstall.ps1",
|
||||
"env_vars": {
|
||||
"url_blenderbim_py310_win_zip",
|
||||
"sha256sum_blenderbim_py310_win_zip",
|
||||
"url_blenderbim_py3x_win_zip",
|
||||
"sha256sum_blenderbim_py3x_win_zip",
|
||||
"latest_blender_version_maj_min",
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$url64 = 'url_blenderbim_py310_win_zip'
|
||||
$checksum64 = 'sha256sum_blenderbim_py310_win_zip'
|
||||
$url64 = 'url_blenderbim_py3x_win_zip'
|
||||
$checksum64 = 'sha256sum_blenderbim_py3x_win_zip'
|
||||
$checksumType64 = 'sha256'
|
||||
|
||||
$appDataUserDir = [System.Environment]::GetEnvironmentVariable('appdata')
|
||||
|
||||
+46
-30
@@ -27,8 +27,6 @@ if(CCACHE_FOUND)
|
||||
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache)
|
||||
endif()
|
||||
|
||||
project(IfcOpenShell VERSION 0.8.0)
|
||||
|
||||
cmake_policy(SET CMP0048 NEW)
|
||||
cmake_policy(SET CMP0074 NEW)
|
||||
cmake_policy(SET CMP0078 OLD)
|
||||
@@ -82,6 +80,17 @@ option(WITH_RELATIONSHIP_VALIDATION "Build IfcConvert with option to validate ge
|
||||
|
||||
option(USERSPACE_PYTHON_PREFIX "Installs IfcPython for the current user only instead of system-wide." OFF)
|
||||
option(ADD_COMMIT_SHA "Add commit sha and branch in version number, warning results in many rebuilds, requires git" OFF)
|
||||
option(VERSION_OVERRIDE "Override the version defined in IfcParse.h with the file VERSION in the repository root" OFF)
|
||||
|
||||
if (VERSION_OVERRIDE)
|
||||
file(READ "../VERSION" "RELEASE_VERSION_")
|
||||
string(STRIP "${RELEASE_VERSION_}" RELEASE_VERSION)
|
||||
message(STATUS "Detected version '${RELEASE_VERSION}'")
|
||||
else()
|
||||
set(RELEASE_VERSION "0.8.0")
|
||||
endif()
|
||||
|
||||
project(IfcOpenShell VERSION ${RELEASE_VERSION})
|
||||
|
||||
if(MINIMAL_BUILD)
|
||||
message(STATUS "Setting options for minimal build")
|
||||
@@ -590,25 +599,27 @@ if(HDF5_SUPPORT)
|
||||
else()
|
||||
message(STATUS "Packaging hdf5 and zlib for conda distribution")
|
||||
|
||||
if(WIN32)
|
||||
# Windows
|
||||
set(zlib_post zlib)
|
||||
set(lib_ext lib)
|
||||
elseif(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
|
||||
if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
|
||||
# macOS
|
||||
set(zlib_post libz)
|
||||
set(lib_ext dylib)
|
||||
else()
|
||||
# linux
|
||||
set(zlib_post libz)
|
||||
set(lib_ext so)
|
||||
endif()
|
||||
|
||||
set(HDF5_LIBRARIES
|
||||
set(HDF5_LIBRARIES
|
||||
"${HDF5_LIBRARY_DIR}/libhdf5_cpp.${lib_ext}"
|
||||
"${HDF5_LIBRARY_DIR}/libhdf5.${lib_ext}"
|
||||
"${HDF5_LIBRARY_DIR}/${zlib_post}.${lib_ext}"
|
||||
)
|
||||
else()
|
||||
# linux and windows
|
||||
# Find HDF5 package
|
||||
find_package(HDF5 REQUIRED COMPONENTS C CXX)
|
||||
# Find ZLIB package
|
||||
find_package(ZLIB REQUIRED)
|
||||
# Include directories
|
||||
include_directories(${HDF5_INCLUDE_DIRS} ${ZLIB_INCLUDE_DIRS})
|
||||
# Link libraries
|
||||
set(HDF5_LIBRARIES ${HDF5_LIBRARIES} ${ZLIB_LIBRARIES})
|
||||
message(STATUS "HDF5 libraries: ${HDF5_LIBRARIES}")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@@ -741,6 +752,7 @@ endif(MSVC)
|
||||
include_directories(${INCLUDE_DIRECTORIES} ${OCC_INCLUDE_DIR} ${OPENCOLLADA_INCLUDE_DIRS}
|
||||
${Boost_INCLUDE_DIRS} ${LIBXML2_INCLUDE_DIR} ${JSON_INCLUDE_DIR} ${HDF5_INCLUDE_DIR}
|
||||
${EIGEN_DIR} ${CGAL_INCLUDE_DIR} ${GMP_INCLUDE_DIR} ${MPFR_INCLUDE_DIR} ${USD_INCLUDE_DIR}
|
||||
${TBB_INCLUDE_DIR}
|
||||
)
|
||||
|
||||
if(NOT SCHEMA_VERSIONS)
|
||||
@@ -1095,22 +1107,26 @@ if(ADD_COMMIT_SHA)
|
||||
find_package(Git)
|
||||
|
||||
if(GIT_FOUND)
|
||||
message("git found: ${GIT_EXECUTABLE} with version ${GIT_VERSION_STRING}")
|
||||
execute_process(
|
||||
COMMAND ${GIT_EXECUTABLE} branch -a --contains HEAD
|
||||
OUTPUT_VARIABLE git_branches
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
string(REPLACE "\n" ";" git_branch_list "${git_branches}")
|
||||
if (VERSION_OVERRIDE)
|
||||
set (git_branch ${RELEASE_VERSION})
|
||||
else()
|
||||
message("git found: ${GIT_EXECUTABLE} with version ${GIT_VERSION_STRING}")
|
||||
execute_process(
|
||||
COMMAND ${GIT_EXECUTABLE} branch -a --contains HEAD
|
||||
OUTPUT_VARIABLE git_branches
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
string(REPLACE "\n" ";" git_branch_list "${git_branches}")
|
||||
|
||||
foreach(git_branch_candidate IN ITEMS ${git_branch_list})
|
||||
string(REPLACE "*" "" git_branch_candidate_temp "${git_branch_candidate}")
|
||||
string(STRIP "${git_branch_candidate_temp}" git_branch_candidate_2)
|
||||
if(NOT git_branch_candidate_2 MATCHES "^HEAD$")
|
||||
string(REPLACE "/" ";" git_branch_candidate_2_list "${git_branch_candidate_2}")
|
||||
list(GET git_branch_candidate_2_list -1 git_branch)
|
||||
endif()
|
||||
endforeach()
|
||||
foreach(git_branch_candidate IN ITEMS ${git_branch_list})
|
||||
string(REPLACE "*" "" git_branch_candidate_temp "${git_branch_candidate}")
|
||||
string(STRIP "${git_branch_candidate_temp}" git_branch_candidate_2)
|
||||
if(NOT git_branch_candidate_2 MATCHES "^HEAD$")
|
||||
string(REPLACE "/" ";" git_branch_candidate_2_list "${git_branch_candidate_2}")
|
||||
list(GET git_branch_candidate_2_list -1 git_branch)
|
||||
endif()
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
execute_process(
|
||||
COMMAND ${GIT_EXECUTABLE} rev-parse --short HEAD
|
||||
@@ -1177,7 +1193,7 @@ if(BUILD_IFCGEOM)
|
||||
DESTINATION ${INCLUDEDIR}/ifcgeom_schema_agnostic
|
||||
)
|
||||
|
||||
install(TARGETS ${IFCGEOM_SCHEMA_LIBRARIES} IfcGeom
|
||||
install(TARGETS ${IFCGEOM_SCHEMA_LIBRARIES} ${kernel_libraries} IfcGeom
|
||||
ARCHIVE DESTINATION ${LIBDIR}
|
||||
LIBRARY DESTINATION ${LIBDIR}
|
||||
RUNTIME DESTINATION ${BINDIR}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
.env
|
||||
+8
-6
@@ -1,10 +1,13 @@
|
||||
mkdir build && cd build
|
||||
|
||||
REM Remove dot from PY_VER for use in library name
|
||||
REM From https://github.com/tpaviot/pythonocc-core/blob/master/ci/conda/bld.bat
|
||||
set MY_PY_VER=%PY_VER:.=%
|
||||
rem set LIBXML2="%LIBRARY_PREFIX%/lib/libxml2.lib"
|
||||
|
||||
set LIBXML2="%LIBRARY_PREFIX%/lib/libxml2.lib"
|
||||
|
||||
cmake -G "Ninja" ^
|
||||
-D SCHEMA_VERSIONS="2x3;4;4x1;4x3;4x3_add1" ^
|
||||
-D SCHEMA_VERSIONS="2x3;4;4x1;4x3_add2" ^
|
||||
-D CMAKE_BUILD_TYPE:STRING=Release ^
|
||||
-D CMAKE_INSTALL_PREFIX:FILEPATH="%LIBRARY_PREFIX%" ^
|
||||
-D CMAKE_PREFIX_PATH:FILEPATH="%LIBRARY_PREFIX%" ^
|
||||
@@ -39,11 +42,10 @@ cmake -G "Ninja" ^
|
||||
-D Boost_INCLUDE_DIR:FILEPATH="%LIBRARY_PREFIX%\include" ^
|
||||
-D Boost_USE_STATIC_LIBS:BOOL=OFF ^
|
||||
-D CITYJSON_SUPPORT:BOOL=OFF ^
|
||||
%SRC_DIR%/cmake
|
||||
|
||||
../cmake
|
||||
|
||||
if errorlevel 1 exit 1
|
||||
|
||||
:: Build and install
|
||||
cmake --build . -- install
|
||||
ninja install -j 1
|
||||
|
||||
if errorlevel 1 exit 1
|
||||
|
||||
+3
-5
@@ -6,16 +6,14 @@
|
||||
|
||||
if [ "$(uname)" == "Darwin" ]; then
|
||||
export FSUFFIX=dylib
|
||||
export CFLAGS="$CFLAGS -Wl,-flat_namespace,-undefined,suppress"
|
||||
export CXXFLAGS="$CXXFLAGS -Wl,-flat_namespace,-undefined,suppress"
|
||||
export LDFLAGS="$LDFLAGS -Wl,-flat_namespace,-undefined,suppress"
|
||||
elif [ "$(expr substr $(uname -s) 1 5)" == "Linux" ]; then
|
||||
export FSUFFIX=so
|
||||
fi
|
||||
|
||||
|
||||
cmake -G Ninja \
|
||||
-DSCHEMA_VERSIONS="2x3;4;4x1;4x3;4x3_add1" \
|
||||
cmake ${CMAKE_ARGS} -G Ninja \
|
||||
-DSCHEMA_VERSIONS="2x3;4;4x1;4x3_add2" \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX=$PREFIX \
|
||||
${CMAKE_PLATFORM_FLAGS[@]} \
|
||||
@@ -48,4 +46,4 @@ cmake -G Ninja \
|
||||
|
||||
ninja
|
||||
|
||||
ninja install -j 1
|
||||
ninja install -j 1
|
||||
@@ -1,11 +1,58 @@
|
||||
CONDA_BUILD_SYSROOT: # used for github actions conda daily build
|
||||
- /Users/runner/work/MacOSX10.13.sdk # [osx]
|
||||
variant:
|
||||
- novtk
|
||||
- all
|
||||
|
||||
occt:
|
||||
- 7.7.1
|
||||
- 7.8.1
|
||||
|
||||
c_compiler:
|
||||
- vs2022 # [win]
|
||||
|
||||
- vs2022 # [win]
|
||||
- gcc # [linux]
|
||||
- clang # [osx]
|
||||
c_stdlib:
|
||||
- vs # [win]
|
||||
- sysroot # [linux]
|
||||
- macosx_deployment_target # [osx]
|
||||
cxx_compiler:
|
||||
- vs2022 # [win]
|
||||
- vs2022 # [win]
|
||||
- gcc # [linux]
|
||||
- clangxx # [osx]
|
||||
c_compiler_version:
|
||||
- '12' # [linux]
|
||||
- '16' # [osx]
|
||||
cxx_compiler_version:
|
||||
- '12' # [linux]
|
||||
- '16' # [osx]
|
||||
c_stdlib_version:
|
||||
- '2.12' # [linux]
|
||||
- '10.13' # [osx]
|
||||
hdf5:
|
||||
- 1.14.3
|
||||
libboost_devel:
|
||||
- '1.84'
|
||||
libxml2:
|
||||
- '2'
|
||||
mpfr:
|
||||
- '4'
|
||||
gmp:
|
||||
- '6' # [not win]
|
||||
pin_run_as_build:
|
||||
python:
|
||||
min_pin: x.x
|
||||
max_pin: x.x
|
||||
|
||||
zlib:
|
||||
- '1'
|
||||
target_platform:
|
||||
- win-64 # [win]
|
||||
- linux-64 # [linux]
|
||||
- osx-64 # [osx]
|
||||
macos_machine:
|
||||
- x86_64-apple-darwin13.4.0
|
||||
MACOSX_DEPLOYMENT_TARGET:
|
||||
- '10.13'
|
||||
MACOSX_SDK_VERSION:
|
||||
- '10.13'
|
||||
|
||||
CONDA_BUILD_SYSROOT:
|
||||
- "/Users/runner/work/MacOSX10.13.sdk" # [osx]
|
||||
@@ -1,58 +0,0 @@
|
||||
@echo off
|
||||
|
||||
:: This is a batch file to set the environment variables for the project
|
||||
:: It is not necessary for conda compilation, but it provides you with type hints when working with the c++ libraries
|
||||
:: OpenCascade, CGAL, Eigen, etc distributed using conda-forge in your IDE.
|
||||
::
|
||||
:: mamba env update -f environment.build.yml --prune
|
||||
:: mamba activate ifcopenshell-build
|
||||
::
|
||||
:: Note!
|
||||
:: You have to add a .env file next to this env.bat file where you set PREFIX=<path to your conda env>
|
||||
set CONDA_BUILD=1
|
||||
set MY_PY_VER=311
|
||||
|
||||
:: set this file's parent directory as a variable
|
||||
set THIS_DIR=%~dp0
|
||||
|
||||
:: read the .env file located in THIS_DIR and set the environment variables.
|
||||
:: the .env file should contain a line like this:
|
||||
:: PREFIX=C:\Users\your_user_name\mambaforge3\envs\ifcopenshell-build
|
||||
for /f "tokens=*" %%i in (%THIS_DIR%.env) do set %%i
|
||||
|
||||
set LIBRARY_PREFIX=%PREFIX%/Library
|
||||
set CMAKE_PREFIX_PATH=%PREFIX%;%LIBRARY_PREFIX%/include;%LIBRARY_PREFIX%/lib;%LIBRARY_PREFIX%/bin
|
||||
|
||||
set OCC_LIBRARY_DIR=%LIBRARY_PREFIX%/lib
|
||||
set OCC_INCLUDE_DIR=%LIBRARY_PREFIX%/include/opencascade
|
||||
|
||||
set CGAL_DIR=%LIBRARY_PREFIX%/lib/cmake/CGAL
|
||||
set CGAL_INCLUDE_DIR=%LIBRARY_PREFIX%/include
|
||||
set EIGEN_DIR=%LIBRARY_PREFIX%/include/eigen3
|
||||
set LIBXML2_INCLUDE_DIR=%LIBRARY_PREFIX%/include/libxml2
|
||||
set LIBXML2_LIBRARIES=%LIBRARY_PREFIX%/lib/libxml2.lib
|
||||
set Boost_LIBRARY_DIR=%LIBRARY_PREFIX%/lib
|
||||
set Boost_INCLUDE_DIR=%LIBRARY_PREFIX%/include
|
||||
set Boost_USE_STATIC_LIBS=OFF
|
||||
|
||||
set GMP_INCLUDE_DIR=%LIBRARY_PREFIX%/include
|
||||
set GMP_LIBRARY_DIR=%LIBRARY_PREFIX%/lib
|
||||
set MPFR_LIBRARY_DIR=%LIBRARY_PREFIX%/lib
|
||||
|
||||
set HDF5_INCLUDE_DIR=%LIBRARY_PREFIX%/include
|
||||
set HDF5_LIBRARY_DIR=%LIBRARY_PREFIX%/lib
|
||||
set JSON_INCLUDE_DIR=%LIBRARY_PREFIX%/include
|
||||
|
||||
set HDF5_SUPPORT=ON
|
||||
set BUILD_IFCPYTHON=ON
|
||||
set BUILD_IFCGEOM=ON
|
||||
set COLLADA_SUPPORT=OFF
|
||||
set BUILD_EXAMPLES=OFF
|
||||
set BUILD_GEOMSERVER=OFF
|
||||
set GLTF_SUPPORT=ON
|
||||
set BUILD_CONVERT=ON
|
||||
set BUILD_IFCMAX=OFF
|
||||
set IFCXML_SUPPORT=ON
|
||||
|
||||
set PYTHON_EXECUTABLE=%PREFIX%/python.exe
|
||||
set PYTHON_LIBRARY=%PREFIX%/libs/python%MY_PY_VER%.lib
|
||||
@@ -1,17 +0,0 @@
|
||||
name: ifcopenshell-build
|
||||
channels:
|
||||
- conda-forge
|
||||
dependencies:
|
||||
- boa
|
||||
- boost-cpp
|
||||
- occt
|
||||
- libxml2
|
||||
- cgal-cpp
|
||||
- hdf5
|
||||
- mpfr
|
||||
- nlohmann_json
|
||||
- swig
|
||||
- zlib
|
||||
# These needs to be installed per platform
|
||||
#- sel(unix): gmp
|
||||
#- sel(win): mpir
|
||||
+303
@@ -0,0 +1,303 @@
|
||||
{% set version = environ.get('VERSION_OVERRIDE', '0.8.0') %}
|
||||
{% set build = 0 %}
|
||||
|
||||
# Higher number -> Always prioritize "novtk" variant over "all" variant
|
||||
{% set build = build + 200 %} # [variant == "novtk"]
|
||||
{% set build = build + 100 %} # [variant == "all"]
|
||||
|
||||
package:
|
||||
name: ifcopenshell
|
||||
version: {{ version }}
|
||||
|
||||
source:
|
||||
path: ../
|
||||
|
||||
|
||||
build:
|
||||
number: {{ build }}
|
||||
skip: true # [py<39]
|
||||
binary_relocation: false # [osx]
|
||||
string: py{{ CONDA_PY }}_{{ variant }}_h{{ PKG_HASH }}_{{ build }}
|
||||
run_exports:
|
||||
- {{ pin_subpackage('ifcopenshell', max_pin='x.x.x') }} *{{ variant }}*
|
||||
|
||||
|
||||
requirements:
|
||||
build:
|
||||
- cmake
|
||||
- ninja
|
||||
- swig >=4.1.1
|
||||
- {{ stdlib("c") }}
|
||||
- {{ compiler('c') }}
|
||||
- {{ compiler('cxx') }}
|
||||
host:
|
||||
- python
|
||||
- libboost-devel
|
||||
- occt *=*{{ variant }}*
|
||||
- libxml2
|
||||
- cgal-cpp
|
||||
- hdf5
|
||||
- eigen
|
||||
- mpfr
|
||||
- nlohmann_json
|
||||
- gmp # [unix]
|
||||
- mpir # [win]
|
||||
- zlib
|
||||
run:
|
||||
- python
|
||||
- {{ pin_compatible('occt', max_pin='x.x.x') }} *{{ variant }}*
|
||||
- {{ pin_compatible('cgal-cpp', max_pin='x.x') }}
|
||||
|
||||
|
||||
test:
|
||||
imports:
|
||||
- ifcopenshell
|
||||
|
||||
|
||||
about:
|
||||
home: https://ifcopenshell.org
|
||||
license: LGPL-3.0-or-later
|
||||
license_file: COPYING
|
||||
summary: 'IfcOpenShell is a library to support the IFC file format'
|
||||
description: |
|
||||
IfcOpenShell
|
||||
============
|
||||
|
||||
IfcOpenShell is an open source ([LGPL]) software library for working with Industry Foundation Classes ([IFC]). Complete
|
||||
parsing support is provided for [IFC2x3 TC1], [IFC4 Add2 TC1], IFC4x1, IFC4x3, and IFC4x3. Extensive geometric support
|
||||
is implemented for the IFC releases [IFC2x3 TC1] and [IFC4 Add2 TC1]. Extending with support for arbitrary IFC schemas
|
||||
is possible at compile-time when using C++ and at run-time when using Python.
|
||||
|
||||
In addition to a C++ and Python API, IfcOpenShell comes with an ecosystem of tools, notably including IfcConvert (an
|
||||
application to convert IFC models to
|
||||
other formats), the BlenderBIM Add-on (an add-on to Blender providing a graphical IFC authoring platform), and many
|
||||
other libraries, CLI apps, and more. Support is also provided for auxiliary standards such as BCF and IDS.
|
||||
|
||||
For more information, see:
|
||||
|
||||
* [IfcOpenShell Website](http://ifcopenshell.org)
|
||||
* [IfcOpenShell Documentation](http://blenderbim.org/docs-python)
|
||||
* [IfcOpenShell C++ Installation](https://blenderbim.org/docs-python/ifcopenshell/installation.html)
|
||||
* [IfcOpenShell Python Installation](https://blenderbim.org/docs-python/ifcopenshell-python/installation.html)
|
||||
* [IfcOpenShell Python Hello World Tutorial](https://blenderbim.org/docs-python/ifcopenshell-python/hello_world.html)
|
||||
* [BlenderBIM Add-on Website](https://blenderbim.org)
|
||||
* [BlenderBIM Add-on Documentation](http://blenderbim.org/docs)
|
||||
* [Add-on Installation](https://blenderbim.org/docs/users/installation.html)
|
||||
* [Exploring an IFC model](https://blenderbim.org/docs/users/exploring_an_ifc_model.html)
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Service</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Anaconda Daily Build</td>
|
||||
<td><a href="https://anaconda.org/ifcopenshell/ifcopenshell"><img src="https://img.shields.io/conda/vn/ifcopenshell/ifcopenshell" alt="Anaconda-Server Badge"></a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Anaconda v0.8.0 Stable</td>
|
||||
<td><a href="https://anaconda.org/conda-forge/ifcopenshell"><img src="https://img.shields.io/conda/vn/conda-forge/ifcopenshell" alt="Anaconda-Server Badge"></a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>PyPi Daily Build</td>
|
||||
<td><a href="https://pypi.org/project/ifcopenshell/"><img src="https://img.shields.io/pypi/v/ifcopenshell" alt="PyPi Badge"></a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ArchLinux AUR Package Stable</td>
|
||||
<td><a href="https://aur.archlinux.org/packages/ifcopenshell"><img src="https://img.shields.io/aur/version/ifcopenshell" alt="AUR Badge"></a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ArchLinux AUR Package git</td>
|
||||
<td><a href="https://aur.archlinux.org/packages/ifcopenshell-git"><img src="https://img.shields.io/aur/version/ifcopenshell-git" alt="AUR Badge"></a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>BlenderBIM Add-on Chocolatey (under moderation)</td>
|
||||
<td><a href="https://community.chocolatey.org/packages/blenderbim-nightly/"><img src="https://img.shields.io/chocolatey/v/blenderbim-nightly" alt="Chocolatey Badge"></a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Sponsor development on OpenCollective</td>
|
||||
<td><a href="https://opencollective.com/opensourcebim/"><img src="https://opencollective.com/opensourcebim/tiers/badge.svg" alt="Financial Contributors"></a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Docker hub</td>
|
||||
<td><a href="https://hub.docker.com/r/aecgeeks/ifcopenshell"><img src="https://img.shields.io/docker/pulls/aecgeeks/ifcopenshell" alt="Docker Pulls"></a></td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
Contents
|
||||
--------
|
||||
|
||||
Those marked with an asterisk are part of IfcOpenShell.
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Description</th>
|
||||
<th>License</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>bcf</td>
|
||||
<td>Library to read and write BCF-XML and query OpenCDE BCF-API modules</td>
|
||||
<td>LGPL-3.0-or-later</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>blenderbim</td>
|
||||
<td>Add-on to Blender providing a graphical native IFC authoring platform</td>
|
||||
<td>GPL-3.0-or-later</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>bsdd</td>
|
||||
<td>Library to query the bSDD API</td>
|
||||
<td>LGPL-3.0-or-later</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ifc2ca</td>
|
||||
<td>Utility to convert IFC structural analysis models to Code_Aster</td>
|
||||
<td>LGPL-3.0-or-later</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ifc4d</td>
|
||||
<td>Convert to and from IFC and project management software</td>
|
||||
<td>LGPL-3.0-or-later</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ifc5d</td>
|
||||
<td>Report and optimise cost information from IFC</td>
|
||||
<td>LGPL-3.0-or-later</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ifcbimtester</td>
|
||||
<td>Wrapper for Gherkin based unit testing for IFC models</td>
|
||||
<td>LGPL-3.0-or-later</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ifcblender</td>
|
||||
<td>Historic Blender IFC import add-on</td>
|
||||
<td>LGPL-3.0-or-later*</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ifccityjson</td>
|
||||
<td>Convert CityJSON to IFC</td>
|
||||
<td>LGPL-3.0-or-later</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ifcclash</td>
|
||||
<td>Clash detection library and CLI app</td>
|
||||
<td>LGPL-3.0-or-later</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ifccobie</td>
|
||||
<td>Extract IFC data for COBie handover requirements</td>
|
||||
<td>LGPL-3.0-or-later</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ifcconvert</td>
|
||||
<td>CLI app to convert IFC to many other formats</td>
|
||||
<td>LGPL-3.0-or-later*</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ifccsv</td>
|
||||
<td>Library and CLI app to export and import schedules from IFC</td>
|
||||
<td>LGPL-3.0-or-later</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ifcdiff</td>
|
||||
<td>Compare changes between IFC models</td>
|
||||
<td>LGPL-3.0-or-later</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ifcfm</td>
|
||||
<td>Extract IFC data for FM handover requirements</td>
|
||||
<td>LGPL-3.0-or-later</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ifcgeom</td>
|
||||
<td>Internal library for IfcOpenShell</td>
|
||||
<td>LGPL-3.0-or-later*</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ifcgeom_schema_agnostic</td>
|
||||
<td>Internal library for IfcOpenShell</td>
|
||||
<td>LGPL-3.0-or-later*</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ifcgeomserver</td>
|
||||
<td>Internal library for IfcOpenShell</td>
|
||||
<td>LGPL-3.0-or-later*</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ifcjni</td>
|
||||
<td>Internal library for IfcOpenShell</td>
|
||||
<td>LGPL-3.0-or-later*</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ifcmax</td>
|
||||
<td>Historic extension for IFC support in 3DS Max</td>
|
||||
<td>LGPL-3.0-or-later*</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ifcopenshell-python</td>
|
||||
<td>Python library for IFC manipulation</td>
|
||||
<td>LGPL-3.0-or-later*</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ifcparse</td>
|
||||
<td>Internal library for IfcOpenShell</td>
|
||||
<td>LGPL-3.0-or-later*</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ifcpatch</td>
|
||||
<td>Utility to run pre-packaged scripts to manipulate IFCs</td>
|
||||
<td>LGPL-3.0-or-later</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ifcsverchok</td>
|
||||
<td>Blender Add-on for visual node programming with IFC</td>
|
||||
<td>GPL-3.0-or-later</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ifctester</td>
|
||||
<td>Library, CLI and webapp for IDS model auditing</td>
|
||||
<td>LGPL-3.0-or-later</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ifcwrap</td>
|
||||
<td>Internal library for IfcOpenShell</td>
|
||||
<td>LGPL-3.0-or-later*</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>qtviewer</td>
|
||||
<td>Internal library for IfcOpenShell</td>
|
||||
<td>LGPL-3.0-or-later*</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>serializers</td>
|
||||
<td>Internal library for IfcOpenShell</td>
|
||||
<td>LGPL-3.0-or-later*</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
[LGPL]: https://github.com/IfcOpenShell/IfcOpenShell/tree/master/COPYING.LESSER "LGPL-3.0-or-later"
|
||||
[IFC]: https://technical.buildingsmart.org/standards/ifc/ "IFC"
|
||||
[IFC2x3 TC1]: https://standards.buildingsmart.org/IFC/RELEASE/IFC2x3/TC1/HTML/ "IFC2x3 TC1"
|
||||
[IFC4 Add2 TC1]: https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/ "IFC4 Add2 TC1"
|
||||
[Visual Studio]: https://www.visualstudio.com/ "Visual Studio"
|
||||
[Visual C++ Build Tools]: http://landinghub.visualstudio.com/visual-cpp-build-tools "Visual C++ Build Tools"
|
||||
[MSYS2]: https://msys2.github.io/ "MSYS2"
|
||||
[win/readme.md]: https://github.com/IfcOpenShell/IfcOpenShell/tree/master/win/readme.md "win/readme.md"
|
||||
[nix/build-all.py]: https://github.com/IfcOpenShell/IfcOpenShell/tree/master/nix/build-all.py "nix/build-all.py"
|
||||
doc_url: https://ifcopenshell.org/
|
||||
dev_url: https://github.com/IfcOpenShell/IfcOpenShell
|
||||
|
||||
extra:
|
||||
recipe-maintainers:
|
||||
- adrianinsaval
|
||||
- looooo
|
||||
- Krande
|
||||
@@ -1,64 +0,0 @@
|
||||
context:
|
||||
name: ifcopenshell
|
||||
version: 0.8.0alpha1
|
||||
|
||||
package:
|
||||
name: '{{ name|lower }}'
|
||||
version: '{{ version }}'
|
||||
|
||||
source:
|
||||
path: ..
|
||||
|
||||
build:
|
||||
number: 1
|
||||
|
||||
requirements:
|
||||
build:
|
||||
- "{{ compiler('c') }}"
|
||||
- "{{ compiler('cxx') }}"
|
||||
- ninja >=1.10.2
|
||||
- cmake
|
||||
- swig 4.1.1
|
||||
|
||||
host:
|
||||
- python
|
||||
- boost-cpp
|
||||
- occt ==7.7.0
|
||||
- libxml2
|
||||
- cgal-cpp
|
||||
- hdf5
|
||||
- eigen
|
||||
- mpfr
|
||||
- sel(unix): gmp
|
||||
- sel(win): mpir
|
||||
- nlohmann_json
|
||||
- zlib
|
||||
|
||||
run:
|
||||
- python
|
||||
- {{ pin_compatible('occt', max_pin='x.x.x') }}
|
||||
- {{ pin_compatible('cgal-cpp', max_pin='x.x.x') }}
|
||||
- {{ pin_compatible('boost-cpp', max_pin='x.x.x') }}
|
||||
- libxml2
|
||||
- hdf5
|
||||
- eigen
|
||||
- mpfr
|
||||
- sel(unix): gmp
|
||||
- sel(win): mpir
|
||||
- nlohmann_json
|
||||
- zlib
|
||||
|
||||
test:
|
||||
imports:
|
||||
- ifcopenshell
|
||||
|
||||
about:
|
||||
home: https://ifcopenshell.org
|
||||
license: LGPL-3.0-or-later
|
||||
license_file: COPYING
|
||||
summary: 'IfcOpenShell is a library to support the IFC file format'
|
||||
description: |
|
||||
IfcOpenShell is an open source (LGPL) software library for
|
||||
working with the Industry Foundation Classes (IFC) file format.
|
||||
doc_url: https://blenderbim.org/docs-python/
|
||||
dev_url: https://github.com/IfcOpenShell/IfcOpenShell
|
||||
+3
-1
@@ -576,6 +576,7 @@ if "freetype" in targets:
|
||||
download_url = "https://github.com/freetype/freetype",
|
||||
download_name = "freetype2",
|
||||
download_tool=download_tool_git,
|
||||
revision="VER-2-11-1"
|
||||
)
|
||||
|
||||
if USE_OCCT and "occ" in targets:
|
||||
@@ -830,7 +831,8 @@ cmake_args = [
|
||||
"-DJSON_INCLUDE_DIR=" f"{DEPS_DIR}/install/json",
|
||||
"-DEIGEN_DIR=" f"{DEPS_DIR}/install/eigen-3.3.9",
|
||||
"-DBoost_NO_BOOST_CMAKE=" "On",
|
||||
"-DADD_COMMIT_SHA=" +("On" if ADD_COMMIT_SHA else "Off")
|
||||
"-DADD_COMMIT_SHA=" +("On" if ADD_COMMIT_SHA else "Off"),
|
||||
"-DVERSION_OVERRIDE=" +("On" if ADD_COMMIT_SHA else "Off")
|
||||
]
|
||||
|
||||
if "wasm" in flags:
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
package:
|
||||
name: ifcopenshell
|
||||
version: 0.7.0
|
||||
version: 0.8.0
|
||||
|
||||
source:
|
||||
path: IfcOpenShell
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
setup(name='IfcOpenShell',
|
||||
version='0.7.0',
|
||||
version='0.8.0',
|
||||
description='IfcOpenShell is an open source (LGPL) software library for working with the Industry Foundation Classes (IFC) file format.',
|
||||
author='Thomas Krijnen',
|
||||
author_email='thomas@aecgeeks.com',
|
||||
|
||||
+4
-1
@@ -1,2 +1,5 @@
|
||||
[tool.black]
|
||||
line-length = 120
|
||||
line-length = 120
|
||||
|
||||
[tool.pyright]
|
||||
reportInvalidTypeForm = false
|
||||
|
||||
@@ -10,7 +10,7 @@ type: software
|
||||
authors:
|
||||
- name: "IfcOpenShell contributors"
|
||||
repository-code: >-
|
||||
https://github.com/IfcOpenShell/IfcOpenShell/tree/v0.7.0/src/bcf
|
||||
https://github.com/IfcOpenShell/IfcOpenShell/tree/v0.8.0/src/bcf
|
||||
abstract: >-
|
||||
Library to read and write BCF-XML and query OpenCDE
|
||||
BCF-API modules
|
||||
|
||||
+26
-4
@@ -1,4 +1,22 @@
|
||||
VERSION:=`date '+%y%m%d'`
|
||||
# BCF - BCF Python library
|
||||
# Copyright (C) 2021 Prabhat Singh <singh01prabhat@gmail.com>
|
||||
#
|
||||
# This file is part of BCF.
|
||||
#
|
||||
# BCF is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BCF 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with BCF. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
VERSION:=$(shell cat ../../VERSION)
|
||||
SED:=sed -i
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
SED:=sed -i '' -e
|
||||
@@ -21,10 +39,14 @@ models:
|
||||
.PHONY: dist
|
||||
dist:
|
||||
rm -rf dist
|
||||
$(SED) "s/999999/$(VERSION)/" pyproject.toml
|
||||
cp pyproject.toml pyproject.toml.bak
|
||||
cp src/bcf/__init__.py __init__.py.bak
|
||||
$(SED) 's/version = "0.0.0"/version = "$(VERSION)"/' pyproject.toml
|
||||
$(SED) 's/version = "0.0.0"/version = "$(VERSION)"/' src/bcf/__init__.py
|
||||
python -m build
|
||||
$(SED) "s/$(VERSION)/999999/" pyproject.toml
|
||||
mv pyproject.toml.bak pyproject.toml
|
||||
mv __init__.py.bak src/bcf/__init__.py
|
||||
|
||||
# .PHONY
|
||||
# api:
|
||||
# openapi-python-client generate --url https://api.swaggerhub.com/apis/buildingSMART/BCF/3.0
|
||||
# openapi-python-client generate --url https://api.swaggerhub.com/apis/buildingSMART/BCF/3.0
|
||||
|
||||
@@ -17,7 +17,7 @@ dependencies = [
|
||||
"numpy",
|
||||
"ifcopenshell",
|
||||
]
|
||||
version = "0.0.999999"
|
||||
version = "0.0.0"
|
||||
classifiers = [
|
||||
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
|
||||
"Operating System :: OS Independent",
|
||||
@@ -138,4 +138,4 @@ max-attributes = 10
|
||||
|
||||
[tool.pylint.format]
|
||||
expected-line-ending-format = "LF"
|
||||
max-line-length = 120
|
||||
max-line-length = 120
|
||||
|
||||
@@ -15,3 +15,5 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with BCF. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
__version__ = version = "0.0.0"
|
||||
|
||||
@@ -10,7 +10,7 @@ type: software
|
||||
authors:
|
||||
- name: "IfcOpenShell contributors"
|
||||
repository-code: >-
|
||||
https://github.com/IfcOpenShell/IfcOpenShell/tree/v0.7.0/src/blenderbim
|
||||
https://github.com/IfcOpenShell/IfcOpenShell/tree/v0.8.0/src/blenderbim
|
||||
url: 'https://blenderbim.org/'
|
||||
repository-artifact: 'https://blenderbim.org/download.html'
|
||||
abstract: >-
|
||||
|
||||
+10
-9
@@ -87,7 +87,6 @@ bump:
|
||||
cd . && $(SED) "s/$(OLD)/$(NEW)/" Makefile
|
||||
cd ../ifcopenshell-python/ && $(SED) "s/$(OLD)/$(NEW)/" Makefile
|
||||
cd ../ifcopenshell-python/docs/ifcconvert/ && $(SED) "s/$(OLD)/$(NEW)/" installation.rst
|
||||
cd ../ifcopenshell-python/docs/ifcopenshell-python/ && $(SED) "s/$(OLD)/$(NEW)/" installation.rst
|
||||
|
||||
.PHONY: dist
|
||||
dist:
|
||||
@@ -100,7 +99,7 @@ endif
|
||||
cp -r blenderbim/* dist/blenderbim/
|
||||
|
||||
# Provides IfcOpenShell Python functionality
|
||||
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-$(PYNUMBER)-v0.8.0-90ae709-$(PLATFORM)64.zip
|
||||
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-$(PYNUMBER)-v$(VERSION)-39cd980-$(PLATFORM)64.zip
|
||||
cd dist/working && unzip ifcopenshell-python*
|
||||
cp -r dist/working/ifcopenshell dist/blenderbim/libs/site/packages/
|
||||
|
||||
@@ -113,13 +112,11 @@ endif
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://github.com/IfcOpenShell/IfcOpenShell/archive/refs/heads/v0.8.0.zip
|
||||
cd dist/working && unzip v0.8.0.zip
|
||||
# IfcOpenBot sometimes lags behind, so we hotfix the Python utilities
|
||||
rm -rf dist/blenderbim/libs/site/packages/ifcopenshell/util/
|
||||
rm -rf dist/blenderbim/libs/site/packages/ifcopenshell/api/
|
||||
cp -r dist/working/IfcOpenShell-0.8.0/src/ifcopenshell-python/ifcopenshell/util dist/blenderbim/libs/site/packages/ifcopenshell/
|
||||
cp -r dist/working/IfcOpenShell-0.8.0/src/ifcopenshell-python/ifcopenshell/api dist/blenderbim/libs/site/packages/ifcopenshell/
|
||||
cp dist/working/IfcOpenShell-0.8.0/src/ifcopenshell-python/ifcopenshell/*.py dist/blenderbim/libs/site/packages/ifcopenshell/
|
||||
cp dist/working/IfcOpenShell-0.8.0/src/ifcopenshell-python/ifcopenshell/express/rule_executor.py dist/blenderbim/libs/site/packages/ifcopenshell/express/
|
||||
# IfcOpenBot sometimes lags behind, so we only use its binaries
|
||||
mv dist/blenderbim/libs/site/packages/ifcopenshell/*ifcopenshell_wrapper* dist/working/
|
||||
rm -rf dist/blenderbim/libs/site/packages/ifcopenshell
|
||||
cp -r dist/working/IfcOpenShell-0.8.0/src/ifcopenshell-python/ifcopenshell dist/blenderbim/libs/site/packages/
|
||||
mv dist/working/*ifcopenshell_wrapper* dist/blenderbim/libs/site/packages/ifcopenshell/
|
||||
# Provides bcf functionality
|
||||
cp -r dist/working/IfcOpenShell-0.8.0/src/bcf/src/bcf dist/blenderbim/libs/site/packages/
|
||||
# Provides IFCClash functionality
|
||||
@@ -213,6 +210,10 @@ endif
|
||||
cd dist/working && . env/bin/activate && $(PIP) install lark --target=./site-packages
|
||||
# Required by IFC4D
|
||||
cd dist/working && . env/bin/activate && $(PIP) install PyP6Xer --target=./site-packages
|
||||
# Required by Web module
|
||||
cd dist/working && . env/bin/activate && $(PIP) install python-socketio[asyncio_client] --target=./site-packages
|
||||
cd dist/working && . env/bin/activate && $(PIP) install aiohttp --target=./site-packages
|
||||
|
||||
cp -r dist/working/site-packages/* dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
|
||||
@@ -27,12 +27,14 @@ import sys
|
||||
IN_BLENDER = sys.modules.get("bpy", None)
|
||||
if IN_BLENDER:
|
||||
import bpy
|
||||
import addon_utils
|
||||
import platform
|
||||
import traceback
|
||||
import webbrowser
|
||||
import addon_utils
|
||||
from collections import deque
|
||||
|
||||
# NOTE: bl_info is superseded by blender_manifest.toml
|
||||
# if addon is installed as an extension (Blender 4.2+)
|
||||
bl_info = {
|
||||
"name": "BlenderBIM",
|
||||
"description": "Transforms Blender into a native Building Information Model authoring platform using IFC.",
|
||||
@@ -92,7 +94,6 @@ if IN_BLENDER:
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)), "libs", "site", "packages"))
|
||||
|
||||
try:
|
||||
import blenderbim.bim
|
||||
import ifcopenshell.api
|
||||
|
||||
def log_api(usecase_path, ifc_file, settings):
|
||||
@@ -107,9 +108,21 @@ if IN_BLENDER:
|
||||
ifcopenshell.api.add_pre_listener("*", "action_logger", log_api)
|
||||
|
||||
def register():
|
||||
global BLENDER_PACKAGE_NAME, __name__, __package__
|
||||
BLENDER_PACKAGE_NAME = __name__
|
||||
if bpy.app.version >= (4, 2, 0):
|
||||
sys.modules["blenderbim"] = sys.modules[__name__]
|
||||
__name__ = "blenderbim"
|
||||
# Only renaming __name__ is actually required to make imports work.
|
||||
# We renaming __package__ just for consistency.
|
||||
__package__ = "blenderbim"
|
||||
import blenderbim.bim
|
||||
|
||||
blenderbim.bim.register()
|
||||
|
||||
def unregister():
|
||||
import blenderbim.bim
|
||||
|
||||
blenderbim.bim.unregister()
|
||||
|
||||
except:
|
||||
|
||||
@@ -84,6 +84,7 @@ modules = {
|
||||
"debug": None,
|
||||
"ifcgit": None,
|
||||
"covering": None,
|
||||
"web": None,
|
||||
# Uncomment this line to enable loading of the demo module. Happy hacking!
|
||||
# The name "demo" must correlate to a folder name in `bim/module/`.
|
||||
# "demo": None,
|
||||
@@ -130,7 +131,6 @@ classes = [
|
||||
prop.GlobalId,
|
||||
prop.BIMObjectProperties,
|
||||
prop.BIMCollectionProperties,
|
||||
prop.BIMMaterialProperties,
|
||||
prop.BIMMeshProperties,
|
||||
prop.BIMFacet,
|
||||
prop.BIMFilterGroup,
|
||||
@@ -141,6 +141,7 @@ classes = [
|
||||
# Tabs panel
|
||||
ui.BIM_PT_tabs,
|
||||
# Project overview
|
||||
ui.BIM_PT_tab_new_project_wizard,
|
||||
ui.BIM_PT_tab_project_info,
|
||||
ui.BIM_PT_tab_spatial_decomposition,
|
||||
ui.BIM_PT_tab_project_setup,
|
||||
@@ -223,8 +224,6 @@ def register():
|
||||
bpy.types.Screen.BIMTabProperties = bpy.props.PointerProperty(type=prop.BIMTabProperties)
|
||||
bpy.types.Collection.BIMCollectionProperties = bpy.props.PointerProperty(type=prop.BIMCollectionProperties)
|
||||
bpy.types.Object.BIMObjectProperties = bpy.props.PointerProperty(type=prop.BIMObjectProperties)
|
||||
bpy.types.Material.BIMObjectProperties = bpy.props.PointerProperty(type=prop.BIMObjectProperties)
|
||||
bpy.types.Material.BIMMaterialProperties = bpy.props.PointerProperty(type=prop.BIMMaterialProperties)
|
||||
bpy.types.Mesh.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties)
|
||||
bpy.types.Curve.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties)
|
||||
bpy.types.Camera.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties)
|
||||
@@ -283,8 +282,6 @@ def unregister():
|
||||
del bpy.types.Scene.BIMProperties
|
||||
del bpy.types.Collection.BIMCollectionProperties
|
||||
del bpy.types.Object.BIMObjectProperties
|
||||
del bpy.types.Material.BIMObjectProperties
|
||||
del bpy.types.Material.BIMMaterialProperties
|
||||
del bpy.types.Mesh.BIMMeshProperties
|
||||
del bpy.types.Curve.BIMMeshProperties
|
||||
del bpy.types.Camera.BIMMeshProperties
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
import argparse
|
||||
from aiohttp import web
|
||||
import socketio
|
||||
import pystache
|
||||
import json
|
||||
|
||||
sio_port = 8080 # default port
|
||||
|
||||
sio = socketio.AsyncServer(
|
||||
cors_allowed_origins="*",
|
||||
async_mode="aiohttp",
|
||||
)
|
||||
|
||||
# sio.instrument(
|
||||
# auth={
|
||||
# "username": "admin",
|
||||
# "password": "admin",
|
||||
# }
|
||||
# )
|
||||
|
||||
app = web.Application()
|
||||
sio.attach(app)
|
||||
|
||||
blender_messages = {}
|
||||
|
||||
|
||||
# Web namespace
|
||||
class WebNamespace(socketio.AsyncNamespace):
|
||||
def __init__(self, namespace):
|
||||
super().__init__(namespace)
|
||||
|
||||
async def on_connect(self, sid, environ):
|
||||
print(f"Web client connected: {sid}")
|
||||
|
||||
async def on_disconnect(self, sid):
|
||||
print(f"Web client disconnected: {sid}")
|
||||
|
||||
async def on_web_operator(self, sid, data):
|
||||
await sio.emit("web_operator", data["operator"], namespace="/blender", room=data["blenderId"])
|
||||
|
||||
|
||||
# Blender namespace
|
||||
class BlenderNamespace(socketio.AsyncNamespace):
|
||||
def __init__(self, namespace):
|
||||
super().__init__(namespace)
|
||||
|
||||
async def on_connect(self, sid, environ):
|
||||
print(f"Blender client connected: {sid}")
|
||||
# Notify web client about new connection
|
||||
blender_messages[sid] = {}
|
||||
await sio.emit("blender_connect", sid, namespace="/web")
|
||||
|
||||
async def on_disconnect(self, sid):
|
||||
print(f"Blender client disconnected: {sid}")
|
||||
# Remove client message and notify web client about disconnection
|
||||
if sid in blender_messages:
|
||||
del blender_messages[sid]
|
||||
await sio.emit("blender_disconnect", sid, namespace="/web")
|
||||
|
||||
async def on_data(self, sid, data):
|
||||
print(f"Data from Blender client {sid}")
|
||||
# blender_messages[sid]["default_data"] = data
|
||||
# await sio.emit("default_data", {"blenderId": sid, "data": data}, namespace="/web")
|
||||
|
||||
async def on_csv_data(self, sid, data):
|
||||
print(f"CSV data from Blender client {sid}")
|
||||
# Store the message and forward it to the web client
|
||||
blender_messages[sid]["csv_data"] = data
|
||||
await sio.emit("csv_data", {"blenderId": sid, "data": data}, namespace="/web")
|
||||
|
||||
|
||||
# Attach namespaces
|
||||
sio.register_namespace(WebNamespace("/web"))
|
||||
sio.register_namespace(BlenderNamespace("/blender"))
|
||||
|
||||
|
||||
# Define a route to render the index.html template
|
||||
async def index(request):
|
||||
with open("templates/index.html", "r") as f:
|
||||
template = f.read()
|
||||
html_content = pystache.render(template, {"port": sio_port})
|
||||
return web.Response(text=html_content, content_type="text/html")
|
||||
|
||||
|
||||
app.router.add_get("/", index)
|
||||
app.router.add_static("/static/", path="./static", name="static")
|
||||
|
||||
|
||||
def main():
|
||||
global sio_port
|
||||
|
||||
parser = argparse.ArgumentParser(description="SocketIO server")
|
||||
parser.add_argument("--host", type=str, default="127.0.0.1", help="Host to run the server on")
|
||||
parser.add_argument("--port", type=int, default=8080, help="Port to run the server on")
|
||||
|
||||
args = parser.parse_args()
|
||||
sio_port = args.port
|
||||
web.run_app(app, host=args.host, port=sio_port)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
# web.run_app(app, host="127.0.0.1", port=sio_port)
|
||||
@@ -0,0 +1,72 @@
|
||||
body {
|
||||
background-color: #252525;
|
||||
color: #e0e0e0;
|
||||
margin: 0;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
#container {
|
||||
margin-top: 20px;
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
margin-bottom: 20px
|
||||
}
|
||||
|
||||
.table-container {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.csv-table {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
nav {
|
||||
background-color: #121212;
|
||||
padding: 1em 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
nav .logo {
|
||||
margin-left: 40px;
|
||||
height: 40px;
|
||||
/* Adjust height as needed */
|
||||
}
|
||||
|
||||
nav ul {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
margin-left: 1px;
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
justify-content: left;
|
||||
}
|
||||
|
||||
nav ul li {
|
||||
margin-right: 50px;
|
||||
}
|
||||
|
||||
nav ul li a {
|
||||
text-decoration: none;
|
||||
color: #fff;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
nav ul li a:hover,
|
||||
nav ul li a.active {
|
||||
color: #3fb449;
|
||||
}
|
||||
|
||||
nav .bottom-bar {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background-color: #25682a;
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
// keeps track of blenders connected in form of
|
||||
// {shown:bool, filename:string, headers:array}
|
||||
const connectedClients = {};
|
||||
let socket;
|
||||
|
||||
// Document ready function
|
||||
$(document).ready(function () {
|
||||
connectSocket();
|
||||
});
|
||||
|
||||
// Function to connect to Socket.IO server
|
||||
function connectSocket() {
|
||||
const url = "ws://localhost:" + SOCKET_PORT + "/web";
|
||||
socket = io(url);
|
||||
console.log("socket: ", socket);
|
||||
|
||||
// Register socket event handlers
|
||||
socket.on("blender_connect", handleBlenderConnect);
|
||||
socket.on("blender_disconnect", handleBlenderDisconnect);
|
||||
socket.on("csv_data", handleCsvData);
|
||||
}
|
||||
|
||||
// Function to handle 'blender_connect' event
|
||||
function handleBlenderConnect(blenderId) {
|
||||
console.log("blender_connect: ", blenderId);
|
||||
if (!connectedClients.hasOwnProperty(blenderId)) {
|
||||
connectedClients[blenderId] = { shown: false, ifc_file: "" };
|
||||
}
|
||||
}
|
||||
|
||||
// Function to handle 'blender_disconnect' event
|
||||
function handleBlenderDisconnect(blenderId) {
|
||||
console.log("blender_disconnect: ", blenderId);
|
||||
if (connectedClients.hasOwnProperty(blenderId)) {
|
||||
delete connectedClients[blenderId];
|
||||
removeTableElement(blenderId);
|
||||
}
|
||||
}
|
||||
|
||||
// Function to handle 'csv_data' event
|
||||
function handleCsvData(data) {
|
||||
const blenderId = data["blenderId"];
|
||||
const csvData = data["data"]["data"];
|
||||
const filename = data["data"]["IFC_File"];
|
||||
console.log(data);
|
||||
|
||||
if (connectedClients.hasOwnProperty(blenderId)) {
|
||||
if (!connectedClients[blenderId].shown) {
|
||||
connectedClients[blenderId] = {
|
||||
shown: true,
|
||||
ifc_file: filename,
|
||||
headers: [],
|
||||
};
|
||||
addTableElement(blenderId, csvData, filename);
|
||||
} else {
|
||||
updateTableElement(blenderId, csvData, filename);
|
||||
}
|
||||
} else {
|
||||
connectedClients[blenderId] = {
|
||||
shown: true,
|
||||
ifc_file: filename,
|
||||
headers: [],
|
||||
};
|
||||
addTableElement(blenderId, csvData, filename);
|
||||
}
|
||||
}
|
||||
|
||||
// Function to add a new table with data and filename
|
||||
function addTableElement(blenderId, csvData, filename) {
|
||||
// store headers of the csv data
|
||||
const firstLine = csvData.indexOf("\n");
|
||||
const csvHeaders = csvData.substring(0, firstLine).split(",");
|
||||
connectedClients[blenderId].headers = csvHeaders;
|
||||
|
||||
console.log(connectedClients[blenderId]);
|
||||
const tableContainer = $("<div></div>")
|
||||
.addClass("table-container")
|
||||
.attr("id", "container-" + blenderId);
|
||||
|
||||
const tableTitle = $("<h3></h3>")
|
||||
.attr("id", "title-" + blenderId)
|
||||
.text(filename)
|
||||
.css("margin-bottom", "10px");
|
||||
|
||||
const tableDiv = $("<div></div>")
|
||||
.addClass("csv-table")
|
||||
.attr("id", "table-" + blenderId);
|
||||
|
||||
tableContainer.append(tableTitle);
|
||||
tableContainer.append(tableDiv);
|
||||
$("#container").append(tableContainer);
|
||||
|
||||
function createHeaderMenu(definitions) {
|
||||
var menu = [];
|
||||
|
||||
for (let column of definitions) {
|
||||
// Create text element for checkbox
|
||||
if (!column.hasOwnProperty("key")) {
|
||||
column.visible = true;
|
||||
}
|
||||
let checkbox = document.createElement("span");
|
||||
checkbox.textContent = column.visible ? "\u2611" : "\u2610"; // ☑ or ☐
|
||||
|
||||
// Build label
|
||||
let label = document.createElement("span");
|
||||
let title = document.createElement("span");
|
||||
|
||||
title.textContent = " " + column.title;
|
||||
|
||||
label.appendChild(checkbox);
|
||||
label.appendChild(title);
|
||||
|
||||
// Create menu item
|
||||
menu.push({
|
||||
label: label,
|
||||
action: function (e) {
|
||||
// Prevent menu closing
|
||||
e.stopPropagation();
|
||||
|
||||
// Toggle current column visibility
|
||||
column.visible = !column.visible;
|
||||
console.log(column.title, column.visible);
|
||||
// Change menu item checkbox
|
||||
if (column.visible) {
|
||||
table.showColumn(column.title);
|
||||
checkbox.textContent = "\u2611"; // ☑
|
||||
} else {
|
||||
table.hideColumn(column.title);
|
||||
checkbox.textContent = "\u2610"; // ☐
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
var table = new Tabulator("#table-" + blenderId, {
|
||||
height: "400px",
|
||||
resizableColumnGuide: true,
|
||||
index: "GlobalId",
|
||||
data: csvData,
|
||||
importFormat: "csv",
|
||||
autoColumns: true,
|
||||
selectableRows: 1,
|
||||
layout: "fitColumns",
|
||||
autoColumnsDefinitions: function (definitions) {
|
||||
menu = createHeaderMenu(definitions, table);
|
||||
definitions.forEach((column) => {
|
||||
column.visible = true;
|
||||
column.headerMenu = menu;
|
||||
});
|
||||
return definitions;
|
||||
},
|
||||
});
|
||||
|
||||
table.on("rowSelected", function (row) {
|
||||
var index = row.getIndex(); // the guid of the object
|
||||
|
||||
if (!index) {
|
||||
console.log("No Global ID found");
|
||||
return;
|
||||
}
|
||||
|
||||
// table id is usually "table-BlenderId" so blender id is always
|
||||
// after the first 6 characters
|
||||
var tableId = row.getTable().element.id.substr(6);
|
||||
|
||||
const msg = {
|
||||
blenderId: tableId,
|
||||
operator: {
|
||||
type: "selection",
|
||||
GlobalId: index,
|
||||
},
|
||||
};
|
||||
socket.emit("web_operator", msg);
|
||||
});
|
||||
}
|
||||
|
||||
// Function to update table and filename
|
||||
function updateTableElement(blenderId, csvData, filename) {
|
||||
// check if the headers are the same
|
||||
// if yes, just replace the data
|
||||
// else, replace the whole data and column definations
|
||||
const firstLine = csvData.indexOf("\n");
|
||||
const newHeaders = csvData.substring(0, firstLine).split(",");
|
||||
const sameHeaders = compareHeaders(
|
||||
connectedClients[blenderId].headers,
|
||||
newHeaders
|
||||
);
|
||||
|
||||
const table = Tabulator.findTable("#table-" + blenderId)[0];
|
||||
if (table) {
|
||||
if (sameHeaders) {
|
||||
table.replaceData(csvData, { importFormat: "csv" });
|
||||
} else {
|
||||
table.setData(csvData);
|
||||
connectedClients[blenderId].headers = newHeaders;
|
||||
}
|
||||
$("#title-" + blenderId).text(filename);
|
||||
}
|
||||
}
|
||||
|
||||
// Function to remove table element
|
||||
function removeTableElement(blenderId) {
|
||||
$("#container-" + blenderId).remove();
|
||||
}
|
||||
|
||||
// Utility function to compare two csv header
|
||||
function compareHeaders(headers1, headers2) {
|
||||
if (headers1.length !== headers2.length) {
|
||||
return false;
|
||||
}
|
||||
for (let i = 0; i < headers1.length; i++) {
|
||||
if (headers1[i] !== headers2[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Web Client</title>
|
||||
<link rel="stylesheet" href="/static/css/styles.css" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://unpkg.com/tabulator-tables/dist/css/tabulator_site_dark.min.css"
|
||||
/>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="https://code.jquery.com/jquery-3.6.0.min.js"
|
||||
></script>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="https://unpkg.com/tabulator-tables/dist/js/tabulator.min.js"
|
||||
></script>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="https://cdn.socket.io/4.0.0/socket.io.min.js"
|
||||
></script>
|
||||
<script>
|
||||
var SOCKET_PORT = {{port}};
|
||||
</script>
|
||||
<script defer src="./static/js/index.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="/">IFC Data</a></li>
|
||||
</ul>
|
||||
<div class="bottom-bar"></div>
|
||||
</nav>
|
||||
<div id="container"></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -89,7 +89,7 @@ class IfcExporter:
|
||||
self.get_application_name(), tool.Blender.get_blenderbim_version()
|
||||
)
|
||||
|
||||
def sync_all_objects(self, skip_unlinking=False) -> list[ifcopenshell.entity_instance]:
|
||||
def sync_all_objects(self) -> list[ifcopenshell.entity_instance]:
|
||||
results: list[ifcopenshell.entity_instance] = []
|
||||
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file)
|
||||
for ifc_definition_id in list(IfcStore.id_map.keys()):
|
||||
@@ -118,8 +118,8 @@ class IfcExporter:
|
||||
continue
|
||||
try:
|
||||
if isinstance(obj, bpy.types.Material):
|
||||
if tool.Ifc.has_changed_shading(obj):
|
||||
blenderbim.core.style.update_style_colours(tool.Ifc, tool.Style, obj=obj)
|
||||
# TODO: do we add materials to edited_objs?
|
||||
continue
|
||||
else:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element:
|
||||
@@ -137,9 +137,9 @@ class IfcExporter:
|
||||
return
|
||||
bpy.ops.bim.update_representation(obj=obj.name)
|
||||
|
||||
def has_changed_materials(self, obj):
|
||||
def has_changed_materials(self, obj: bpy.types.Object) -> bool:
|
||||
checksum = obj.data.BIMMeshProperties.material_checksum
|
||||
return checksum != str([s.id() for s in tool.Geometry.get_styles(obj) if s])
|
||||
return checksum != tool.Geometry.get_material_checksum(obj)
|
||||
|
||||
def sync_object_placement(self, obj: bpy.types.Object) -> Union[ifcopenshell.entity_instance, None]:
|
||||
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
|
||||
@@ -51,10 +51,8 @@ def name_callback(obj, data):
|
||||
return
|
||||
|
||||
if isinstance(obj, bpy.types.Material):
|
||||
if obj.BIMObjectProperties.ifc_definition_id:
|
||||
IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id).Name = obj.name
|
||||
if obj.BIMMaterialProperties.ifc_style_id:
|
||||
IfcStore.get_file().by_id(obj.BIMMaterialProperties.ifc_style_id).Name = obj.name
|
||||
if ifc_definition_id := obj.BIMStyleProperties.ifc_definition_id:
|
||||
IfcStore.get_file().by_id(ifc_definition_id).Name = obj.name
|
||||
refresh_ui_data()
|
||||
return
|
||||
|
||||
@@ -86,11 +84,6 @@ def name_callback(obj, data):
|
||||
refresh_ui_data()
|
||||
|
||||
|
||||
def color_callback(obj, data):
|
||||
if obj.BIMMaterialProperties.ifc_style_id:
|
||||
tool.Ifc.edit(obj)
|
||||
|
||||
|
||||
def active_object_callback():
|
||||
refresh_ui_data()
|
||||
update_bim_tool_props()
|
||||
@@ -193,6 +186,8 @@ def refresh_ui_data():
|
||||
tool.Ifc.get().clear_cache()
|
||||
|
||||
bpy.context.scene.DocProperties.should_draw_decorations = bpy.context.scene.DocProperties.should_draw_decorations
|
||||
if bpy.context.scene.WebProperties.is_connected:
|
||||
tool.Web.send_webui_data()
|
||||
|
||||
|
||||
@persistent
|
||||
@@ -296,12 +291,13 @@ def load_post(scene):
|
||||
ifcopenshell.api.owner.settings.get_application = get_application
|
||||
AuthoringData.type_thumbnails = {}
|
||||
|
||||
if not bpy.context.preferences.addons["blenderbim"].preferences.should_setup_toolbar:
|
||||
preferences = tool.Blender.get_addon_preferences()
|
||||
if not preferences.should_setup_toolbar:
|
||||
tool.Blender.unregister_toolbar()
|
||||
|
||||
if bpy.context.preferences.addons["blenderbim"].preferences.should_setup_workspace:
|
||||
if preferences.should_setup_workspace:
|
||||
if "BIM" in bpy.data.workspaces:
|
||||
if bpy.context.preferences.addons["blenderbim"].preferences.activate_workspace:
|
||||
if preferences.activate_workspace:
|
||||
bpy.context.window.workspace = bpy.data.workspaces["BIM"]
|
||||
else:
|
||||
bpy.ops.workspace.append_activate(idname="BIM", filepath=os.path.join(cwd, "data", "workspace.blend"))
|
||||
|
||||
@@ -32,12 +32,29 @@ import blenderbim.bim.handler
|
||||
import blenderbim.tool as tool
|
||||
from pathlib import Path
|
||||
from blenderbim.tool.brick import BrickStore
|
||||
from typing import Set, Union, Optional
|
||||
from typing import Set, Union, Optional, TypedDict, Callable
|
||||
|
||||
|
||||
IFC_CONNECTED_TYPE = Union[bpy.types.Material, bpy.types.Object]
|
||||
|
||||
|
||||
class OperationData(TypedDict):
|
||||
id: int
|
||||
guid: Union[str, None]
|
||||
obj: str
|
||||
|
||||
|
||||
class Operation(TypedDict):
|
||||
rollback: Callable
|
||||
commit: Callable
|
||||
data: OperationData
|
||||
|
||||
|
||||
class TransactionStep(TypedDict):
|
||||
key: str
|
||||
operations: list[Operation]
|
||||
|
||||
|
||||
class IfcStore:
|
||||
path: str = ""
|
||||
file: Optional[ifcopenshell.file] = None
|
||||
@@ -55,9 +72,9 @@ class IfcStore:
|
||||
library_file: Optional[ifcopenshell.file] = None
|
||||
current_transaction = ""
|
||||
last_transaction = ""
|
||||
history = []
|
||||
future = []
|
||||
schema_identifiers = ["IFC4", "IFC2X3", "IFC4X3"]
|
||||
history: list[TransactionStep] = []
|
||||
future: list[TransactionStep] = []
|
||||
schema_identifiers = ["IFC4", "IFC2X3", "IFC4X3_ADD2"]
|
||||
session_files: dict[str, ifcopenshell.file] = {}
|
||||
|
||||
@staticmethod
|
||||
@@ -77,7 +94,7 @@ class IfcStore:
|
||||
IfcStore.last_transaction = ""
|
||||
IfcStore.history = []
|
||||
IfcStore.future = []
|
||||
IfcStore.schema_identifiers = ["IFC4", "IFC2X3", "IFC4X3"]
|
||||
IfcStore.schema_identifiers = ["IFC4", "IFC2X3", "IFC4X3_ADD2"]
|
||||
IfcStore.session_files = {}
|
||||
|
||||
@staticmethod
|
||||
@@ -150,7 +167,7 @@ class IfcStore:
|
||||
IfcStore.file = ifcopenshell.open(path)
|
||||
|
||||
@staticmethod
|
||||
def get_schema():
|
||||
def get_schema() -> ifcopenshell.ifcopenshell_wrapper.schema_definition:
|
||||
if IfcStore.file is None:
|
||||
IfcStore.schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(
|
||||
bpy.context.scene.BIMProjectProperties.export_schema
|
||||
@@ -162,14 +179,13 @@ class IfcStore:
|
||||
@staticmethod
|
||||
def get_element(id_or_guid: Union[int, str]) -> IFC_CONNECTED_TYPE:
|
||||
if isinstance(id_or_guid, int):
|
||||
map_object = IfcStore.id_map
|
||||
obj = IfcStore.id_map.get(id_or_guid)
|
||||
else:
|
||||
map_object = IfcStore.guid_map
|
||||
try:
|
||||
obj = map_object[id_or_guid]
|
||||
obj.name # In case the object has been deleted, this triggers an exception
|
||||
except:
|
||||
return
|
||||
obj = IfcStore.guid_map.get(id_or_guid)
|
||||
if obj is None:
|
||||
return None
|
||||
if not tool.Blender.is_valid_data_block(obj):
|
||||
return None
|
||||
return obj
|
||||
|
||||
@staticmethod
|
||||
@@ -189,21 +205,26 @@ class IfcStore:
|
||||
def relink_object(obj: IFC_CONNECTED_TYPE) -> None:
|
||||
if not obj:
|
||||
return
|
||||
if obj.BIMObjectProperties.ifc_definition_id:
|
||||
try:
|
||||
element = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
except:
|
||||
if isinstance(obj, bpy.types.Object):
|
||||
ifc_definition_id = obj.BIMObjectProperties.ifc_definition_id
|
||||
if not ifc_definition_id:
|
||||
return
|
||||
data = {"id": element.id(), "obj": obj.name}
|
||||
try:
|
||||
element = IfcStore.get_file().by_id(ifc_definition_id)
|
||||
except RuntimeError:
|
||||
return
|
||||
data = OperationData(id=element.id(), obj=obj.name)
|
||||
if hasattr(element, "GlobalId"):
|
||||
data["guid"] = element.GlobalId
|
||||
IfcStore.commit_link_element(data)
|
||||
if hasattr(obj, "BIMMaterialProperties") and obj.BIMMaterialProperties.ifc_style_id:
|
||||
return
|
||||
else: # bpy.types.Material
|
||||
ifc_definition_id = obj.BIMStyleProperties.ifc_definition_id
|
||||
try:
|
||||
element = IfcStore.get_file().by_id(obj.BIMMaterialProperties.ifc_style_id)
|
||||
except:
|
||||
element = IfcStore.get_file().by_id(ifc_definition_id)
|
||||
except RuntimeError:
|
||||
return
|
||||
data = {"id": element.id(), "obj": obj.name}
|
||||
data = OperationData(id=element.id(), obj=obj.name)
|
||||
IfcStore.commit_link_element(data)
|
||||
|
||||
@staticmethod
|
||||
@@ -218,44 +239,40 @@ class IfcStore:
|
||||
existing_obj = IfcStore.id_map.get(element.id(), None)
|
||||
if existing_obj == obj:
|
||||
return
|
||||
elif existing_obj:
|
||||
try:
|
||||
existing_obj.name
|
||||
IfcStore.unlink_element(element=element, obj=existing_obj)
|
||||
except:
|
||||
pass
|
||||
# TODO: When does this occur?
|
||||
elif existing_obj and tool.Blender.is_valid_data_block(existing_obj):
|
||||
IfcStore.unlink_element(obj=existing_obj)
|
||||
|
||||
IfcStore.id_map[element.id()] = obj
|
||||
if hasattr(element, "GlobalId"):
|
||||
IfcStore.guid_map[element.GlobalId] = obj
|
||||
|
||||
if element.is_a("IfcSurfaceStyle"):
|
||||
obj.BIMMaterialProperties.ifc_style_id = element.id()
|
||||
obj.BIMStyleProperties.ifc_definition_id = element.id()
|
||||
else:
|
||||
obj.BIMObjectProperties.ifc_definition_id = element.id()
|
||||
|
||||
blenderbim.bim.handler.subscribe_to(obj, "name", blenderbim.bim.handler.name_callback)
|
||||
|
||||
if isinstance(obj, bpy.types.Material):
|
||||
blenderbim.bim.handler.subscribe_to(obj, "diffuse_color", blenderbim.bim.handler.color_callback)
|
||||
elif isinstance(obj, bpy.types.Object):
|
||||
if isinstance(obj, bpy.types.Object):
|
||||
blenderbim.bim.handler.subscribe_to(
|
||||
obj, "active_material_index", blenderbim.bim.handler.active_material_index_callback
|
||||
)
|
||||
|
||||
if IfcStore.history:
|
||||
data = {"id": element.id(), "guid": getattr(element, "GlobalId", None), "obj": obj.name}
|
||||
data = OperationData(id=element.id(), guid=getattr(element, "GlobalId", None), obj=obj.name)
|
||||
IfcStore.history[-1]["operations"].append(
|
||||
{"rollback": IfcStore.rollback_link_element, "commit": IfcStore.commit_link_element, "data": data}
|
||||
Operation(rollback=IfcStore.rollback_link_element, commit=IfcStore.commit_link_element, data=data)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def rollback_link_element(data):
|
||||
def rollback_link_element(data: OperationData) -> None:
|
||||
del IfcStore.id_map[data["id"]]
|
||||
if data["guid"]:
|
||||
del IfcStore.guid_map[data["guid"]]
|
||||
|
||||
@staticmethod
|
||||
def commit_link_element(data):
|
||||
def commit_link_element(data: OperationData) -> None:
|
||||
obj = bpy.data.objects.get(data["obj"])
|
||||
if not obj:
|
||||
obj = bpy.data.materials.get(data["obj"])
|
||||
@@ -263,9 +280,7 @@ class IfcStore:
|
||||
if "guid" in data:
|
||||
IfcStore.guid_map[data["guid"]] = obj
|
||||
blenderbim.bim.handler.subscribe_to(obj, "name", blenderbim.bim.handler.name_callback)
|
||||
if isinstance(obj, bpy.types.Material):
|
||||
blenderbim.bim.handler.subscribe_to(obj, "diffuse_color", blenderbim.bim.handler.color_callback)
|
||||
elif isinstance(obj, bpy.types.Object):
|
||||
if isinstance(obj, bpy.types.Object):
|
||||
blenderbim.bim.handler.subscribe_to(
|
||||
obj, "active_material_index", blenderbim.bim.handler.active_material_index_callback
|
||||
)
|
||||
@@ -273,7 +288,7 @@ class IfcStore:
|
||||
# TODO We're handling id_map and guid_map, but what about edited_objs? This might cause big problems.
|
||||
|
||||
@staticmethod
|
||||
def rollback_unlink_element(data) -> None:
|
||||
def rollback_unlink_element(data: OperationData) -> None:
|
||||
if "id" not in data or "obj" not in data:
|
||||
return
|
||||
obj = bpy.data.objects.get(data["obj"])
|
||||
@@ -282,7 +297,7 @@ class IfcStore:
|
||||
IfcStore.guid_map[data["guid"]] = obj
|
||||
|
||||
@staticmethod
|
||||
def commit_unlink_element(data) -> None:
|
||||
def commit_unlink_element(data: OperationData) -> None:
|
||||
del IfcStore.id_map[data["id"]]
|
||||
if data["guid"]:
|
||||
del IfcStore.guid_map[data["guid"]]
|
||||
@@ -291,50 +306,62 @@ class IfcStore:
|
||||
def unlink_element(
|
||||
element: Optional[ifcopenshell.entity_instance] = None, obj: Optional[IFC_CONNECTED_TYPE] = None
|
||||
) -> None:
|
||||
if element is None:
|
||||
try:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
except:
|
||||
pass
|
||||
"""Unlink IFC `element` or Blender `obj`.
|
||||
|
||||
If element is provided then it will be unlinked from the related Blender object.
|
||||
Blender object's IFC information is also will be purged.
|
||||
|
||||
If Blender object is provided then all IFC information will be purged from this object.
|
||||
Method won't be searching for related IFC elements as Blender object might come from
|
||||
different Blender session and there might be an ids/guids clash.
|
||||
|
||||
Only one argument must be provided and other should be omitted as they have different meaning.
|
||||
:raises TypeError: If both arguments or no arguments provided.
|
||||
|
||||
"""
|
||||
if not bool(element) ^ bool(obj):
|
||||
raise TypeError("Only one argument must be provided - element or obj.")
|
||||
|
||||
if obj:
|
||||
IfcStore.purge_blender_ifc_data(obj)
|
||||
return
|
||||
|
||||
assert element # Type checker.
|
||||
if obj is None:
|
||||
try:
|
||||
potential_obj = IfcStore.id_map[element.id()]
|
||||
potential_obj.name
|
||||
obj = potential_obj
|
||||
if tool.Blender.is_valid_data_block(potential_obj):
|
||||
obj = potential_obj
|
||||
IfcStore.purge_blender_ifc_data(obj)
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
if element:
|
||||
del IfcStore.id_map[element.id()]
|
||||
else:
|
||||
del IfcStore.id_map[obj.BIMObjectProperties.ifc_definition_id]
|
||||
del IfcStore.id_map[element.id()]
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
if element and hasattr(element, "GlobalId"):
|
||||
del IfcStore.guid_map[element.GlobalId]
|
||||
except:
|
||||
pass
|
||||
|
||||
if element and element.is_a("IfcSurfaceStyle"):
|
||||
obj.BIMMaterialProperties.ifc_style_id = 0
|
||||
elif obj:
|
||||
obj.BIMObjectProperties.ifc_definition_id = 0
|
||||
if global_id := getattr(element, "GlobalId", None):
|
||||
try:
|
||||
del IfcStore.guid_map[global_id]
|
||||
except:
|
||||
pass
|
||||
|
||||
if IfcStore.history:
|
||||
data = {}
|
||||
if element:
|
||||
data["id"] = element.id()
|
||||
data["guid"] = getattr(element, "GlobalId", None)
|
||||
data = OperationData(id=element.id(), guid=global_id)
|
||||
if obj:
|
||||
data["obj"] = obj.name
|
||||
IfcStore.history[-1]["operations"].append(
|
||||
{"rollback": IfcStore.rollback_unlink_element, "commit": IfcStore.commit_unlink_element, "data": data}
|
||||
Operation(rollback=IfcStore.rollback_unlink_element, commit=IfcStore.commit_unlink_element, data=data)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def purge_blender_ifc_data(obj: IFC_CONNECTED_TYPE) -> None:
|
||||
if isinstance(obj, bpy.types.Material):
|
||||
obj.BIMStyleProperties.ifc_definition_id = 0
|
||||
else: # bpy.types.Object
|
||||
obj.BIMObjectProperties.ifc_definition_id = 0
|
||||
|
||||
@staticmethod
|
||||
def execute_ifc_operator(operator: bpy.types.Operator, context: bpy.types.Context, is_invoke=False):
|
||||
blenderbim.last_actions.append({"type": "operator", "name": operator.bl_idname})
|
||||
@@ -393,15 +420,15 @@ class IfcStore:
|
||||
rollback = rollback or getattr(operator, "rollback", lambda data: True)
|
||||
commit = commit or getattr(operator, "commit", lambda data: True)
|
||||
if IfcStore.history and IfcStore.history[-1]["key"] == key:
|
||||
IfcStore.history[-1]["operations"].append({"rollback": rollback, "commit": commit, "data": data})
|
||||
IfcStore.history[-1]["operations"].append(OperationData(rollback=rollback, commit=commit, data=data))
|
||||
else:
|
||||
IfcStore.history.append(
|
||||
{"key": key, "operations": [{"rollback": rollback, "commit": commit, "data": data}]}
|
||||
TransactionStep(key=key, operations=[OperationData(rollback=rollback, commit=commit, data=data)])
|
||||
)
|
||||
IfcStore.future = []
|
||||
|
||||
@staticmethod
|
||||
def undo(until_key=None) -> None:
|
||||
def undo(until_key: str = None) -> None:
|
||||
BrickStore.undo()
|
||||
if not IfcStore.history:
|
||||
return
|
||||
@@ -416,7 +443,7 @@ class IfcStore:
|
||||
IfcStore.future.append(event)
|
||||
|
||||
@staticmethod
|
||||
def redo(until_key=None) -> None:
|
||||
def redo(until_key: str = None) -> None:
|
||||
BrickStore.redo()
|
||||
|
||||
if not IfcStore.future:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -49,7 +49,7 @@ class BoundaryDecorator:
|
||||
batch.draw(shader)
|
||||
|
||||
def __call__(self, context):
|
||||
self.addon_prefs = context.preferences.addons["blenderbim"].preferences
|
||||
self.addon_prefs = tool.Blender.get_addon_preferences()
|
||||
selected_elements_color = self.addon_prefs.decorator_color_selected
|
||||
unselected_elements_color = self.addon_prefs.decorator_color_unselected
|
||||
special_elements_color = self.addon_prefs.decorator_color_special
|
||||
|
||||
@@ -551,9 +551,9 @@ class HideBoundaries(bpy.types.Operator, tool.Ifc.Operator):
|
||||
for boundary in element.BoundedBy or []:
|
||||
boundary_obj = tool.Ifc.get_object(boundary)
|
||||
if boundary_obj:
|
||||
to_delete.add(boundary_obj)
|
||||
for boundary_obj in to_delete:
|
||||
tool.Ifc.unlink(obj=boundary_obj)
|
||||
to_delete.add((boundary, boundary_obj))
|
||||
for boundary, boundary_obj in to_delete:
|
||||
tool.Ifc.unlink(element=boundary)
|
||||
bpy.data.objects.remove(boundary_obj)
|
||||
context.scene.BIMBoundaryProperties.boundaries.clear()
|
||||
return {"FINISHED"}
|
||||
@@ -602,6 +602,11 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
|
||||
parent_boundaries = []
|
||||
|
||||
objs = context.selected_objects
|
||||
|
||||
if not any((element:=tool.Ifc.get_entity(obj)) and element.is_a("IfcSpace") for obj in objs):
|
||||
self.report({"INFO"}, "No IfcSpace elements selected.")
|
||||
return {"FINISHED"}
|
||||
|
||||
if len(objs) == 2:
|
||||
# The user may select two objects, a space and its related building element
|
||||
for obj in objs:
|
||||
|
||||
@@ -55,7 +55,7 @@ class ClashDecorator:
|
||||
batch.draw(shader)
|
||||
|
||||
def draw_text(self, context):
|
||||
self.addon_prefs = context.preferences.addons["blenderbim"].preferences
|
||||
self.addon_prefs = tool.Blender.get_addon_preferences()
|
||||
selected_elements_color = self.addon_prefs.decorator_color_selected
|
||||
unselected_elements_color = self.addon_prefs.decorator_color_unselected
|
||||
special_elements_color = self.addon_prefs.decorator_color_special
|
||||
@@ -75,7 +75,7 @@ class ClashDecorator:
|
||||
blf.draw(font_id, text) # Set your text here
|
||||
|
||||
def draw_geometry(self, context):
|
||||
self.addon_prefs = context.preferences.addons["blenderbim"].preferences
|
||||
self.addon_prefs = tool.Blender.get_addon_preferences()
|
||||
selected_elements_color = self.addon_prefs.decorator_color_selected
|
||||
unselected_elements_color = self.addon_prefs.decorator_color_unselected
|
||||
special_elements_color = self.addon_prefs.decorator_color_special
|
||||
|
||||
@@ -185,6 +185,8 @@ class ExportIfcCsv(bpy.types.Operator):
|
||||
|
||||
def invoke(self, context, event):
|
||||
props = context.scene.CsvProperties
|
||||
if props.format == "web":
|
||||
return self.execute(context)
|
||||
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, f".{props.format}")
|
||||
WindowManager = context.window_manager
|
||||
WindowManager.fileselect_add(self)
|
||||
@@ -222,6 +224,10 @@ class ExportIfcCsv(bpy.types.Operator):
|
||||
if attribute.formatting != "{{value}}" and "{{value}}" in attribute.formatting:
|
||||
formatting.append({"name": attribute.name, "format": attribute.formatting})
|
||||
|
||||
file_format = props.format
|
||||
if props.format == "web":
|
||||
file_format = "pd"
|
||||
|
||||
sep = props.csv_custom_delimiter if props.csv_delimiter == "CUSTOM" else props.csv_delimiter
|
||||
ifc_csv.export(
|
||||
ifc_file,
|
||||
@@ -229,7 +235,7 @@ class ExportIfcCsv(bpy.types.Operator):
|
||||
attributes,
|
||||
headers=headers,
|
||||
output=self.filepath,
|
||||
format=props.format,
|
||||
format=file_format,
|
||||
should_preserve_existing=props.should_preserve_existing,
|
||||
delimiter=sep,
|
||||
include_global_id=props.include_global_id,
|
||||
@@ -247,6 +253,8 @@ class ExportIfcCsv(bpy.types.Operator):
|
||||
if props.format != "csv" and props.should_generate_svg:
|
||||
schedule_creator = scheduler.Scheduler()
|
||||
schedule_creator.schedule(self.filepath, tool.Drawing.get_path_with_ext(self.filepath, "svg"))
|
||||
if props.format == "web":
|
||||
tool.Web.send_webui_data(extra_data=ifc_csv.dataframe.to_csv(index=False), event="csv_data")
|
||||
self.report({"INFO"}, f"Data is exported to {props.format.upper()}.")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -88,6 +88,7 @@ class CsvProperties(PropertyGroup):
|
||||
("csv", "csv", ""),
|
||||
("xlsx", "xlsx", ""),
|
||||
("ods", "ods", ""),
|
||||
("web", "web", ""),
|
||||
],
|
||||
name="Output format",
|
||||
default="csv",
|
||||
|
||||
@@ -90,12 +90,11 @@ class ConvertToBlender(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
for obj in bpy.data.objects:
|
||||
if obj.type in {"MESH", "EMPTY"}:
|
||||
obj.BIMObjectProperties.ifc_definition_id = 0
|
||||
tool.Ifc.unlink(obj=obj)
|
||||
if obj.data:
|
||||
obj.data.BIMMeshProperties.ifc_definition_id = 0
|
||||
for material in bpy.data.materials:
|
||||
material.BIMObjectProperties.ifc_definition_id = 0
|
||||
material.BIMMaterialProperties.ifc_style_id = 0
|
||||
tool.Ifc.unlink(obj=material)
|
||||
context.scene.BIMProperties.ifc_file = ""
|
||||
context.scene.BIMDebugProperties.attributes.clear()
|
||||
IfcStore.purge()
|
||||
|
||||
@@ -188,7 +188,6 @@ class ExecuteIfcDiff(bpy.types.Operator):
|
||||
tool.Ifc.set(ifc_diff.old)
|
||||
ifc_importer.file = ifc_diff.old
|
||||
ifc_importer.process_context_filter()
|
||||
ifc_importer.create_materials()
|
||||
ifc_importer.create_styles()
|
||||
|
||||
elements = {ifc_diff.old.by_guid(guid) for guid in ifc_diff.deleted_elements}
|
||||
@@ -204,7 +203,6 @@ class ExecuteIfcDiff(bpy.types.Operator):
|
||||
tool.Ifc.set(ifc_diff.new)
|
||||
ifc_importer.file = ifc_diff.new
|
||||
ifc_importer.process_context_filter()
|
||||
ifc_importer.create_materials()
|
||||
ifc_importer.create_styles()
|
||||
|
||||
elements = {ifc_diff.new.by_guid(guid) for guid in ifc_diff.added_elements}
|
||||
|
||||
@@ -71,6 +71,8 @@ classes = (
|
||||
operator.OpenReference,
|
||||
operator.OpenSchedule,
|
||||
operator.OpenSheet,
|
||||
operator.OrderTextLiteralDown,
|
||||
operator.OrderTextLiteralUp,
|
||||
operator.ReloadDrawingStyles,
|
||||
operator.RemoveDrawing,
|
||||
operator.RemoveDrawingFromSheet,
|
||||
|
||||
@@ -335,7 +335,7 @@ class BaseDecorator:
|
||||
"""`verts` should be in winspace with `(0,0,0)` in the screen left bottom corner, not in the center"""
|
||||
region = context.region
|
||||
if not color:
|
||||
color = context.preferences.addons["blenderbim"].preferences.decorations_colour
|
||||
color = tool.Blender.get_addon_preferences().decorations_colour
|
||||
|
||||
self.line_shader.bind()
|
||||
# POLYLINE_UNIFORM_COLOR specific uniforms
|
||||
@@ -404,7 +404,7 @@ class BaseDecorator:
|
||||
|
||||
font_id = self.font_id
|
||||
|
||||
color = context.preferences.addons["blenderbim"].preferences.decorations_colour
|
||||
color = tool.Blender.get_addon_preferences().decorations_colour
|
||||
|
||||
ang = -Vector((1, 0)).angle_signed(text_dir)
|
||||
cos = math.cos(ang)
|
||||
@@ -812,7 +812,7 @@ class AngleDecorator(BaseDecorator):
|
||||
arcs_color = None
|
||||
edges_color = UNSPECIAL_ELEMENT_COLOR
|
||||
if context.active_object == obj and obj.data.is_editmode:
|
||||
arcs_color = context.preferences.addons["blenderbim"].preferences.decorator_color_special
|
||||
arcs_color = tool.Blender.get_addon_preferences().decorator_color_special
|
||||
edges_color = None
|
||||
|
||||
self.draw_lines(
|
||||
@@ -1093,7 +1093,7 @@ class HiddenDecorator(BaseDecorator):
|
||||
else:
|
||||
verts, idxs = self.get_mesh_geom(obj)
|
||||
winspace_verts = worldspace_to_winspace(verts, context)
|
||||
color = [i for i in context.preferences.addons["blenderbim"].preferences.decorations_colour]
|
||||
color = [i for i in tool.Blender.get_addon_preferences().decorations_colour]
|
||||
color[3] = 0.5
|
||||
self.draw_lines(context, obj, winspace_verts, idxs, color)
|
||||
|
||||
@@ -1409,7 +1409,7 @@ class GridDecorator(BaseDecorator):
|
||||
# stem with gaps for circles
|
||||
add_verts_sequence([v0 + gap, v1 - gap], start_i, **out_kwargs)
|
||||
|
||||
color = [i for i in context.preferences.addons["blenderbim"].preferences.decorations_colour]
|
||||
color = [i for i in tool.Blender.get_addon_preferences().decorations_colour]
|
||||
color[3] = 0.5
|
||||
self.draw_lines(context, obj, output_verts, output_edges, color)
|
||||
self.draw_labels(context, obj, verts)
|
||||
@@ -1591,7 +1591,7 @@ class CutDecorator:
|
||||
cls.installed = None
|
||||
|
||||
def __call__(self, context):
|
||||
self.addon_prefs = context.preferences.addons["blenderbim"].preferences
|
||||
self.addon_prefs = tool.Blender.get_addon_preferences()
|
||||
selected_elements_color = self.addon_prefs.decorator_color_selected
|
||||
|
||||
all_vertices = []
|
||||
|
||||
@@ -279,7 +279,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
svg_path = self.combine_svgs(context, underlay_svg, linework_svg, annotation_svg)
|
||||
|
||||
tool.Drawing.open_with_user_command(
|
||||
context.preferences.addons["blenderbim"].preferences.svg_command, svg_path
|
||||
tool.Blender.get_addon_preferences().svg_command, svg_path
|
||||
)
|
||||
|
||||
if self.print_all:
|
||||
@@ -475,15 +475,15 @@ class CreateDrawing(bpy.types.Operator):
|
||||
with profile(f"Processing {context_type} context"):
|
||||
if not context or not drawing_elements:
|
||||
continue
|
||||
geom_settings = ifcopenshell.geom.settings(
|
||||
DISABLE_TRIANGULATION=True, INCLUDE_CURVES=True
|
||||
)
|
||||
geom_settings = ifcopenshell.geom.settings()
|
||||
geom_settings.set("dimensionality", ifcopenshell.ifcopenshell_wrapper.CURVES_SURFACES_AND_SOLIDS)
|
||||
geom_settings.set("iterator-output", ifcopenshell.ifcopenshell_wrapper.NATIVE)
|
||||
if ifc.by_id(context[0]).ContextType == "Plan" and "PLAN_VIEW" in target_view:
|
||||
offset = ifcopenshell.ifcopenshell_wrapper.float_array_3()
|
||||
# A 2mm Z offset to combat Z-fighting in plan or RCPs
|
||||
offset[2] = 0.002 if target_view == "PLAN_VIEW" else -0.002
|
||||
geom_settings.offset = offset
|
||||
geom_settings.set_context_ids(context)
|
||||
geom_settings.set("context-ids", context)
|
||||
it = ifcopenshell.geom.iterator(
|
||||
geom_settings, ifc, multiprocessing.cpu_count(), include=drawing_elements
|
||||
)
|
||||
@@ -507,7 +507,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
# All very hackish whilst prototyping
|
||||
exporter = blenderbim.bim.export_ifc.IfcExporter(None)
|
||||
exporter.file = tool.Ifc.get()
|
||||
invalidated_elements = exporter.sync_all_objects(skip_unlinking=True)
|
||||
invalidated_elements = exporter.sync_all_objects()
|
||||
invalidated_elements += exporter.sync_edited_objects()
|
||||
invalidated_guids = [e.GlobalId for e in invalidated_elements if hasattr(e, "GlobalId")]
|
||||
cache = IfcStore.get_cache()
|
||||
@@ -561,7 +561,8 @@ class CreateDrawing(bpy.types.Operator):
|
||||
if tool.Ifc.get() == ifc and self.camera_element not in drawing_elements:
|
||||
with profile("Camera element"):
|
||||
# The camera must always be included, regardless of any include/exclude filters.
|
||||
geom_settings = ifcopenshell.geom.settings(DISABLE_TRIANGULATION=True)
|
||||
geom_settings = ifcopenshell.geom.settings()
|
||||
geom_settings.set("iterator-output", ifcopenshell.ifcopenshell_wrapper.NATIVE)
|
||||
it = ifcopenshell.geom.iterator(geom_settings, ifc, include=[self.camera_element])
|
||||
for elem in it:
|
||||
self.serialiser.write(elem)
|
||||
@@ -874,11 +875,14 @@ class CreateDrawing(bpy.types.Operator):
|
||||
return svg_path
|
||||
|
||||
def setup_serialiser(self, target_view):
|
||||
self.svg_settings = ifcopenshell.geom.settings(
|
||||
DISABLE_TRIANGULATION=True, INCLUDE_CURVES=True
|
||||
)
|
||||
self.svg_settings = ifcopenshell.geom.settings()
|
||||
self.svg_settings.set("dimensionality", ifcopenshell.ifcopenshell_wrapper.CURVES_SURFACES_AND_SOLIDS)
|
||||
self.svg_settings.set("iterator-output", ifcopenshell.ifcopenshell_wrapper.NATIVE)
|
||||
self.svg_buffer = ifcopenshell.geom.serializers.buffer()
|
||||
self.serialiser = ifcopenshell.geom.serializers.svg(self.svg_buffer, self.svg_settings)
|
||||
self.serialiser_settings = ifcopenshell.geom.serializer_settings()
|
||||
self.serialiser = ifcopenshell.geom.serializers.svg(
|
||||
self.svg_buffer, self.svg_settings, self.serialiser_settings
|
||||
)
|
||||
self.serialiser.setWithoutStoreys(True)
|
||||
self.serialiser.setPolygonal(True)
|
||||
self.serialiser.setUseHlrPoly(True)
|
||||
@@ -982,7 +986,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
|
||||
obj = tool.Ifc.get_object(element)
|
||||
|
||||
if not obj: # This is a linked model object. For now, do nothing.
|
||||
if not obj: # This is a linked model object. For now, do nothing.
|
||||
continue
|
||||
|
||||
if not self.is_manifold(obj):
|
||||
@@ -1370,8 +1374,8 @@ class CreateSheets(bpy.types.Operator, Operator):
|
||||
),
|
||||
)
|
||||
|
||||
svg2pdf_command = context.preferences.addons["blenderbim"].preferences.svg2pdf_command
|
||||
svg2dxf_command = context.preferences.addons["blenderbim"].preferences.svg2dxf_command
|
||||
svg2pdf_command = tool.Blender.get_addon_preferences().svg2pdf_command
|
||||
svg2dxf_command = tool.Blender.get_addon_preferences().svg2dxf_command
|
||||
|
||||
if svg2pdf_command:
|
||||
# With great power comes great responsibility. Example:
|
||||
@@ -1389,9 +1393,9 @@ class CreateSheets(bpy.types.Operator, Operator):
|
||||
subprocess.run([replacements.get(c, c) for c in command])
|
||||
|
||||
if svg2pdf_command:
|
||||
tool.Drawing.open_with_user_command(context.preferences.addons["blenderbim"].preferences.pdf_command, pdf)
|
||||
tool.Drawing.open_with_user_command(tool.Blender.get_addon_preferences().pdf_command, pdf)
|
||||
else:
|
||||
tool.Drawing.open_with_user_command(context.preferences.addons["blenderbim"].preferences.svg_command, svg)
|
||||
tool.Drawing.open_with_user_command(tool.Blender.get_addon_preferences().svg_command, svg)
|
||||
|
||||
|
||||
class SelectAllDrawings(bpy.types.Operator):
|
||||
@@ -1459,7 +1463,7 @@ class OpenDrawing(bpy.types.Operator):
|
||||
|
||||
for drawing_uri in drawing_uris:
|
||||
tool.Drawing.open_with_user_command(
|
||||
context.preferences.addons["blenderbim"].preferences.svg_command, drawing_uri
|
||||
tool.Blender.get_addon_preferences().svg_command, drawing_uri
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -1553,6 +1557,14 @@ class ActivateDrawing(bpy.types.Operator):
|
||||
core.sync_references(tool.Ifc, tool.Collector, tool.Drawing, drawing=tool.Ifc.get().by_id(self.drawing))
|
||||
CutDecorator.install(context)
|
||||
tool.Drawing.show_decorations()
|
||||
|
||||
# Save drawing bounds to the .ifc file
|
||||
camera = context.scene.camera
|
||||
camera_props = camera.data.BIMCameraProperties
|
||||
if camera_props.update_representation(camera):
|
||||
bpy.ops.bim.update_representation(obj=camera.name, ifc_representation_class="")
|
||||
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -2340,6 +2352,32 @@ class RemoveTextLiteral(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class OrderTextLiteralUp(bpy.types.Operator):
|
||||
bl_idname = "bim.order_text_literal_up"
|
||||
bl_label = "Move Text Literal Up"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
literal_prop_id: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.active_object
|
||||
obj.BIMTextProperties.literals.move(self.literal_prop_id, self.literal_prop_id - 1)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class OrderTextLiteralDown(bpy.types.Operator):
|
||||
bl_idname = "bim.order_text_literal_down"
|
||||
bl_label = "Move Text Literal Down"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
literal_prop_id: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.active_object
|
||||
obj.BIMTextProperties.literals.move(self.literal_prop_id, self.literal_prop_id + 1)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AssignSelectedObjectAsProduct(bpy.types.Operator):
|
||||
bl_idname = "bim.assign_selected_as_product"
|
||||
bl_label = "Assign Selected Object As Product"
|
||||
@@ -2738,7 +2776,7 @@ class AddReferenceImage(bpy.types.Operator, Operator):
|
||||
obj.material_slots[0].material = material
|
||||
bpy.ops.bim.add_style()
|
||||
|
||||
style = ifc_file.by_id(material.BIMMaterialProperties.ifc_style_id)
|
||||
style = ifc_file.by_id(material.BIMStyleProperties.ifc_definition_id)
|
||||
tool.Style.assign_style_to_object(style, obj)
|
||||
|
||||
# TODO: IfcSurfaceStyleRendering is unnecessary here, added it only because
|
||||
|
||||
@@ -525,6 +525,10 @@ class BIM_PT_text(Panel):
|
||||
|
||||
row = box.row(align=True)
|
||||
row.label(text=f"Literal[{i}]:")
|
||||
if i > 0:
|
||||
row.operator("bim.order_text_literal_up", icon="TRIA_UP", text="").literal_prop_id = i
|
||||
if i < len(props.literals) - 1:
|
||||
row.operator("bim.order_text_literal_down", icon="TRIA_DOWN", text="").literal_prop_id = i
|
||||
row.operator("bim.remove_text_literal", icon="X", text="").literal_prop_id = i
|
||||
|
||||
# skip BoxAlignment since we're going to format it ourselves
|
||||
|
||||
@@ -24,6 +24,7 @@ from mathutils import Vector
|
||||
|
||||
|
||||
def refresh():
|
||||
ViewportData.is_loaded = False
|
||||
PlacementData.is_loaded = False
|
||||
DerivedCoordinatesData.is_loaded = False
|
||||
RepresentationsData.is_loaded = False
|
||||
@@ -31,6 +32,28 @@ def refresh():
|
||||
ConnectionsData.is_loaded = False
|
||||
|
||||
|
||||
class ViewportData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.is_loaded = True
|
||||
cls.data = {"mode": cls.mode()}
|
||||
|
||||
@classmethod
|
||||
def mode(cls):
|
||||
obj = bpy.context.active_object
|
||||
obj_mode = [("OBJECT", "IFC Object Mode", "", "OBJECT_DATAMODE", 0)]
|
||||
mesh_modes = [
|
||||
("OBJECT", "IFC Object Mode", "", "OBJECT_DATAMODE", 0),
|
||||
("EDIT", "IFC Edit Mode", "", "EDITMODE_HLT", 1),
|
||||
]
|
||||
if not obj or not tool.Blender.is_editable(obj):
|
||||
return obj_mode
|
||||
return mesh_modes
|
||||
|
||||
|
||||
class RepresentationsData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
@@ -39,7 +62,10 @@ class RepresentationsData:
|
||||
def load(cls):
|
||||
cls.data = {"representations": cls.representations()}
|
||||
cls.data["contexts"] = cls.contexts()
|
||||
|
||||
# Only after cls.representations().
|
||||
cls.data["shape_aspects"] = cls.shape_aspects()
|
||||
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
@@ -95,7 +121,11 @@ class RepresentationsData:
|
||||
return results
|
||||
|
||||
@classmethod
|
||||
def shape_aspects(cls):
|
||||
def shape_aspects(cls) -> list[tuple[str, str, str]]:
|
||||
"""Get list of current's representation shape aspects for prop's enum_items."""
|
||||
# Ignore objects without representations, e.g. IfcRelSpaceBoundary.
|
||||
if not cls.data["representations"]:
|
||||
return []
|
||||
obj = bpy.context.active_object
|
||||
if not obj.data:
|
||||
return []
|
||||
@@ -334,11 +364,13 @@ class PlacementData:
|
||||
obj = bpy.context.active_object
|
||||
if obj and props.has_blender_offset:
|
||||
xyz = cls.original_xyz(obj)
|
||||
cls.data.update({
|
||||
"original_x": str(xyz[0]),
|
||||
"original_y": str(xyz[1]),
|
||||
"original_z": str(xyz[2]),
|
||||
})
|
||||
cls.data.update(
|
||||
{
|
||||
"original_x": str(xyz[0]),
|
||||
"original_y": str(xyz[1]),
|
||||
"original_z": str(xyz[2]),
|
||||
}
|
||||
)
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -324,7 +324,7 @@ class UpdateRepresentation(bpy.types.Operator, Operator):
|
||||
if not obj.data:
|
||||
continue
|
||||
self.update_obj_mesh_representation(context, obj)
|
||||
IfcStore.edited_objs.discard(obj)
|
||||
tool.Ifc.finish_edit(obj)
|
||||
return {"FINISHED"}
|
||||
|
||||
def update_obj_mesh_representation(self, context, obj):
|
||||
@@ -1126,7 +1126,6 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator):
|
||||
location_diff = new_obj.location - base_obj_location
|
||||
new_obj.location = context.scene.cursor.location + location_diff
|
||||
|
||||
|
||||
if len(context.selected_objects) != 1:
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -1531,7 +1530,7 @@ class OverrideModeSetEdit(bpy.types.Operator):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
selected_objs = context.selected_objects or ([context.active_object] if context.active_object else [])
|
||||
selected_objs = tool.Blender.get_selected_objects()
|
||||
active_obj = context.active_object
|
||||
|
||||
if context.active_object:
|
||||
@@ -1542,23 +1541,13 @@ class OverrideModeSetEdit(bpy.types.Operator):
|
||||
return bpy.ops.bim.enable_editing_boundary_geometry()
|
||||
|
||||
for obj in selected_objs:
|
||||
if not obj:
|
||||
continue
|
||||
obj_supports_edit_mode, message = tool.Blender.object_supports_edit_mode(obj)
|
||||
if not obj_supports_edit_mode:
|
||||
self.report({"INFO"}, message)
|
||||
if not tool.Blender.is_editable(obj):
|
||||
self.report({"INFO"}, f"Object cannot be edited: {obj.name}")
|
||||
obj.select_set(False)
|
||||
continue
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
continue
|
||||
usage_type = tool.Model.get_usage_type(element)
|
||||
if usage_type is not None and usage_type not in ("PROFILE", "LAYER3"):
|
||||
# Parametric objects shall not be edited as meshes as they
|
||||
# can be modified to be incompatible with the parametric
|
||||
# constraints.
|
||||
obj.select_set(False)
|
||||
continue
|
||||
representation = tool.Geometry.get_active_representation(obj)
|
||||
if not representation:
|
||||
continue
|
||||
@@ -1566,6 +1555,7 @@ class OverrideModeSetEdit(bpy.types.Operator):
|
||||
obj.select_set(False)
|
||||
continue
|
||||
|
||||
usage_type = tool.Model.get_usage_type(element)
|
||||
is_profile = True
|
||||
representation_class = tool.Geometry.get_ifc_representation_class(element, representation)
|
||||
if usage_type == "PROFILE":
|
||||
@@ -1663,6 +1653,8 @@ class OverrideModeSetObject(bpy.types.Operator):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
if not context.active_object:
|
||||
return {"FINISHED"}
|
||||
for obj in self.edited_objs:
|
||||
if self.should_save:
|
||||
bpy.ops.bim.update_representation(obj=obj.name, ifc_representation_class="")
|
||||
@@ -1694,6 +1686,8 @@ class OverrideModeSetObject(bpy.types.Operator):
|
||||
return IfcStore.execute_ifc_operator(self, context, is_invoke=True)
|
||||
|
||||
def _invoke(self, context, event):
|
||||
if not context.active_object:
|
||||
return {"FINISHED"}
|
||||
self.is_valid = True
|
||||
|
||||
bpy.ops.object.mode_set(mode="EDIT", toggle=True)
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
import bpy
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim.prop import StrProperty, Attribute
|
||||
from blenderbim.bim.module.geometry.data import RepresentationsData
|
||||
from blenderbim.bim.module.geometry.data import RepresentationsData, ViewportData
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
@@ -48,6 +48,12 @@ def update_mode(self, context):
|
||||
bpy.ops.bim.override_mode_set_edit("INVOKE_DEFAULT")
|
||||
|
||||
|
||||
def get_mode(self, context):
|
||||
if not ViewportData.is_loaded:
|
||||
ViewportData.load()
|
||||
return ViewportData.data["mode"]
|
||||
|
||||
|
||||
def get_styles(self, context):
|
||||
# postponed import to avoid circular import
|
||||
from blenderbim.bim.module.material.data import MaterialsData
|
||||
@@ -129,13 +135,5 @@ class BIMGeometryProperties(PropertyGroup):
|
||||
# Navisworks workaround
|
||||
should_force_triangulation: BoolProperty(name="Force Triangulation", default=False)
|
||||
is_changing_mode: BoolProperty(name="Is Changing Mode", default=False)
|
||||
mode: EnumProperty(
|
||||
default="OBJECT",
|
||||
items=[
|
||||
("OBJECT", "IFC Object Mode", "", "OBJECT_DATAMODE", 0),
|
||||
("EDIT", "IFC Edit Mode", "", "EDITMODE_HLT", 1),
|
||||
],
|
||||
name="IFC Interaction Mode",
|
||||
update=update_mode,
|
||||
)
|
||||
mode: EnumProperty(items=get_mode, name="IFC Interaction Mode", update=update_mode)
|
||||
representation_from_object: PointerProperty(type=bpy.types.Object)
|
||||
|
||||
@@ -33,6 +33,8 @@ from blenderbim.bim.module.layer.data import LayersData
|
||||
|
||||
|
||||
def mode_menu(self, context):
|
||||
if not tool.Ifc.get():
|
||||
return
|
||||
row = self.layout.row(align=True)
|
||||
if context.scene.BIMGeometryProperties.mode == "EDIT":
|
||||
row.operator("bim.override_mode_set_object", icon="CANCEL", text="Discard Changes").should_save = False
|
||||
@@ -419,10 +421,11 @@ class BIM_PT_placement(Panel):
|
||||
row.label(text="Blender Offset", icon="TRACKING_REFINE_FORWARDS")
|
||||
row.label(text=context.active_object.BIMObjectProperties.blender_offset_type)
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=PlacementData.data["original_x"], icon="EMPTY_AXIS")
|
||||
row.label(text=PlacementData.data["original_y"])
|
||||
row.label(text=PlacementData.data["original_z"])
|
||||
if context.active_object.BIMObjectProperties.blender_offset_type != "NOT_APPLICABLE":
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=PlacementData.data["original_x"], icon="EMPTY_AXIS")
|
||||
row.label(text=PlacementData.data["original_y"])
|
||||
row.label(text=PlacementData.data["original_z"])
|
||||
|
||||
|
||||
class BIM_PT_derived_coordinates(Panel):
|
||||
|
||||
@@ -20,23 +20,31 @@ import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.EnableEditingGeoreferencing,
|
||||
operator.DisableEditingGeoreferencing,
|
||||
operator.EditGeoreferencing,
|
||||
operator.SetIfcGridNorth,
|
||||
operator.SetBlenderGridNorth,
|
||||
operator.SetIfcTrueNorth,
|
||||
operator.SetBlenderTrueNorth,
|
||||
operator.RemoveGeoreferencing,
|
||||
operator.AddGeoreferencing,
|
||||
operator.ConvertLocalToGlobal,
|
||||
operator.ConvertGlobalToLocal,
|
||||
operator.GetCursorLocation,
|
||||
operator.SetCursorLocation,
|
||||
operator.ConvertAngleToCoordinates,
|
||||
operator.ConvertGlobalToLocal,
|
||||
operator.ConvertLocalToGlobal,
|
||||
operator.DisableEditingGeoreferencing,
|
||||
operator.DisableEditingTrueNorth,
|
||||
operator.DisableEditingWCS,
|
||||
operator.EditGeoreferencing,
|
||||
operator.EditTrueNorth,
|
||||
operator.EditWCS,
|
||||
operator.EnableEditingGeoreferencing,
|
||||
operator.EnableEditingTrueNorth,
|
||||
operator.EnableEditingWCS,
|
||||
operator.GetCursorLocation,
|
||||
operator.ImportPlot,
|
||||
operator.RemoveGeoreferencing,
|
||||
operator.RemoveTrueNorth,
|
||||
operator.SetBlenderGridNorth,
|
||||
operator.SetBlenderTrueNorth,
|
||||
operator.SetIfcGridNorth,
|
||||
operator.SetIfcTrueNorth,
|
||||
prop.BIMGeoreferenceProperties,
|
||||
ui.BIM_PT_gis,
|
||||
ui.BIM_PT_gis_wcs,
|
||||
ui.BIM_PT_gis_true_north,
|
||||
ui.BIM_PT_gis_calculator,
|
||||
)
|
||||
|
||||
|
||||
@@ -18,8 +18,10 @@
|
||||
|
||||
|
||||
import bpy
|
||||
import ifcopenshell.util.geolocation
|
||||
import numpy as np
|
||||
import blenderbim.tool as tool
|
||||
import ifcopenshell.util.geolocation
|
||||
from ifcopenshell.util.doc import get_entity_doc
|
||||
|
||||
|
||||
def refresh():
|
||||
@@ -32,14 +34,28 @@ class GeoreferenceData:
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data["coordinate_operation_class"] = cls.coordinate_operation_class()
|
||||
cls.data["blender_derived_angle"] = cls.blender_derived_angle()
|
||||
cls.data["map_conversion"] = cls.map_conversion()
|
||||
cls.data["coordinate_operation"] = cls.coordinate_operation()
|
||||
cls.data["map_derived_angle"] = cls.map_derived_angle()
|
||||
cls.data["projected_crs"] = cls.projected_crs()
|
||||
cls.data["true_north"] = cls.true_north()
|
||||
cls.data["true_derived_angle"] = cls.true_derived_angle()
|
||||
cls.data["local_unit_symbol"] = cls.local_unit_symbol()
|
||||
cls.data["map_unit_symbol"] = cls.map_unit_symbol()
|
||||
cls.data["world_coordinate_system"] = cls.world_coordinate_system()
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def coordinate_operation_class(cls):
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
return []
|
||||
declaration = tool.Ifc.schema().declaration_by_name("IfcCoordinateOperation")
|
||||
declarations = ifcopenshell.util.schema.get_subtypes(declaration)
|
||||
names = [d.name() for d in declarations]
|
||||
version = tool.Ifc.get_schema()
|
||||
return [(c, c, get_entity_doc(version, c).get("description", "")) for c in sorted(names)]
|
||||
|
||||
@classmethod
|
||||
def blender_derived_angle(cls):
|
||||
props = bpy.context.scene.BIMGeoreferenceProperties
|
||||
@@ -54,36 +70,37 @@ class GeoreferenceData:
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def map_conversion(cls):
|
||||
def coordinate_operation(cls):
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
project = tool.Ifc.get().by_type("IfcProject")[0]
|
||||
map_conversion = ifcopenshell.util.element.get_pset(project, "ePSet_MapConversion")
|
||||
if not map_conversion:
|
||||
coordinate_operation = ifcopenshell.util.element.get_pset(project, "ePSet_MapConversion")
|
||||
if not coordinate_operation:
|
||||
return {}
|
||||
del map_conversion["id"]
|
||||
return map_conversion
|
||||
del coordinate_operation["id"]
|
||||
coordinate_operation["type"] = "ePSet_MapConversion"
|
||||
return coordinate_operation
|
||||
|
||||
for context in tool.Ifc.get().by_type("IfcGeometricRepresentationContext", include_subtypes=False):
|
||||
if context.HasCoordinateOperation:
|
||||
map_conversion = context.HasCoordinateOperation[0].get_info()
|
||||
del map_conversion["id"]
|
||||
del map_conversion["type"]
|
||||
del map_conversion["SourceCRS"]
|
||||
del map_conversion["TargetCRS"]
|
||||
return map_conversion
|
||||
coordinate_operation = context.HasCoordinateOperation[0].get_info()
|
||||
del coordinate_operation["id"]
|
||||
del coordinate_operation["SourceCRS"]
|
||||
del coordinate_operation["TargetCRS"]
|
||||
return coordinate_operation
|
||||
return {}
|
||||
|
||||
@classmethod
|
||||
def map_derived_angle(cls):
|
||||
if (
|
||||
cls.data["map_conversion"]
|
||||
and cls.data["map_conversion"].get("XAxisAbscissa", None) is not None
|
||||
and cls.data["map_conversion"].get("XAxisOrdinate", None) is not None
|
||||
cls.data["coordinate_operation"]
|
||||
and cls.data["coordinate_operation"].get("XAxisAbscissa", None) is not None
|
||||
and cls.data["coordinate_operation"].get("XAxisOrdinate", None) is not None
|
||||
):
|
||||
return str(
|
||||
round(
|
||||
ifcopenshell.util.geolocation.xaxis2angle(
|
||||
cls.data["map_conversion"]["XAxisAbscissa"], cls.data["map_conversion"]["XAxisOrdinate"]
|
||||
cls.data["coordinate_operation"]["XAxisAbscissa"],
|
||||
cls.data["coordinate_operation"]["XAxisOrdinate"],
|
||||
),
|
||||
3,
|
||||
)
|
||||
@@ -130,4 +147,36 @@ class GeoreferenceData:
|
||||
@classmethod
|
||||
def true_derived_angle(cls):
|
||||
if cls.data["true_north"]:
|
||||
return str(round(ifcopenshell.util.geolocation.yaxis2angle(*cls.data["true_north"][0:2]), 3))
|
||||
return str(round(ifcopenshell.util.geolocation.yaxis2angle(*cls.data["true_north"][:2]), 3))
|
||||
|
||||
@classmethod
|
||||
def local_unit_symbol(cls):
|
||||
if not (unit := ifcopenshell.util.unit.get_project_unit(tool.Ifc.get(), "LENGTHUNIT")):
|
||||
return "n/a"
|
||||
return ifcopenshell.util.unit.get_unit_symbol(unit)
|
||||
|
||||
@classmethod
|
||||
def map_unit_symbol(cls):
|
||||
if tool.Ifc.get().schema == "IFC2X3":
|
||||
return cls.local_unit_symbol()
|
||||
if crs := tool.Ifc.get().by_type("IfcProjectedCRS"):
|
||||
crs = crs[0]
|
||||
if not (unit := crs.MapUnit):
|
||||
return cls.local_unit_symbol()
|
||||
return ifcopenshell.util.unit.get_unit_symbol(unit)
|
||||
return "n/a"
|
||||
|
||||
@classmethod
|
||||
def world_coordinate_system(cls):
|
||||
wcs = ifcopenshell.util.geolocation.get_wcs(tool.Ifc.get())
|
||||
result = {}
|
||||
if wcs is None:
|
||||
result["has_transformation"] = False
|
||||
return result
|
||||
if np.allclose(wcs, np.eye(4)):
|
||||
result["has_transformation"] = False
|
||||
else:
|
||||
result["has_transformation"] = True
|
||||
result["rotation"] = str(round(ifcopenshell.util.geolocation.yaxis2angle(*wcs[:, 1][:2]), 3))
|
||||
result["x"], result["y"], result["z"] = wcs[:, 3][:3]
|
||||
return result
|
||||
|
||||
@@ -29,7 +29,7 @@ class AddGeoreferencing(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_description = "Add a new georeference"
|
||||
|
||||
def _execute(self, context):
|
||||
core.add_georeferencing(tool.Ifc)
|
||||
core.add_georeferencing(tool.Georeference)
|
||||
|
||||
|
||||
class EnableEditingGeoreferencing(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -106,21 +106,6 @@ class GetCursorLocation(bpy.types.Operator, tool.Ifc.Operator):
|
||||
core.get_cursor_location(tool.Georeference)
|
||||
|
||||
|
||||
class SetCursorLocation(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.set_cursor_location"
|
||||
bl_label = "Set Cursor Location"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Move cursor location to the specified coordinates"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
props = context.scene.BIMGeoreferenceProperties
|
||||
return tool.Ifc.get() and props.coordinate_output.count(",") == 2
|
||||
|
||||
def _execute(self, context):
|
||||
core.set_cursor_location(tool.Georeference)
|
||||
|
||||
|
||||
class SetIfcTrueNorth(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.set_ifc_true_north"
|
||||
bl_label = "Set IFC True North"
|
||||
@@ -151,7 +136,7 @@ class ConvertLocalToGlobal(bpy.types.Operator, tool.Ifc.Operator):
|
||||
def poll(cls, context):
|
||||
file = tool.Ifc.get()
|
||||
props = context.scene.BIMGeoreferenceProperties
|
||||
return file and props.coordinate_input.count(",") == 2
|
||||
return file and props.local_coordinates.count(",") == 2
|
||||
|
||||
def _execute(self, context):
|
||||
core.convert_local_to_global(tool.Georeference)
|
||||
@@ -167,7 +152,7 @@ class ConvertGlobalToLocal(bpy.types.Operator, tool.Ifc.Operator):
|
||||
def poll(cls, context):
|
||||
file = tool.Ifc.get()
|
||||
props = context.scene.BIMGeoreferenceProperties
|
||||
return file and file.by_type("IfcUnitAssignment") and props.coordinate_input.count(",") == 2
|
||||
return file and file.by_type("IfcUnitAssignment") and props.local_coordinates.count(",") == 2
|
||||
|
||||
def _execute(self, context):
|
||||
core.convert_global_to_local(tool.Georeference)
|
||||
@@ -204,3 +189,73 @@ class ImportPlot(bpy.types.Operator, tool.Ifc.Operator):
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
class EnableEditingWCS(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.enable_editing_wcs"
|
||||
bl_label = "Enable Editing WCS"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Enable editing WCS"
|
||||
|
||||
def _execute(self, context):
|
||||
core.enable_editing_wcs(tool.Georeference)
|
||||
|
||||
|
||||
class EditWCS(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.edit_wcs"
|
||||
bl_label = "Edit WCS"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Edit the WCS"
|
||||
|
||||
def _execute(self, context):
|
||||
core.edit_wcs(tool.Ifc, tool.Georeference)
|
||||
|
||||
|
||||
class DisableEditingWCS(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.disable_editing_wcs"
|
||||
bl_label = "Disable Editing WCS"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Close editing panel"
|
||||
|
||||
def _execute(self, context):
|
||||
core.disable_editing_wcs(tool.Georeference)
|
||||
|
||||
|
||||
class EnableEditingTrueNorth(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.enable_editing_true_north"
|
||||
bl_label = "Enable Editing True North"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Enable editing True North"
|
||||
|
||||
def _execute(self, context):
|
||||
core.enable_editing_true_north(tool.Georeference)
|
||||
|
||||
|
||||
class EditTrueNorth(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.edit_true_north"
|
||||
bl_label = "Edit True North"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Edit the True North"
|
||||
|
||||
def _execute(self, context):
|
||||
core.edit_true_north(tool.Ifc, tool.Georeference)
|
||||
|
||||
|
||||
class DisableEditingTrueNorth(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.disable_editing_true_north"
|
||||
bl_label = "Disable Editing True North"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Close editing panel"
|
||||
|
||||
def _execute(self, context):
|
||||
core.disable_editing_true_north(tool.Georeference)
|
||||
|
||||
|
||||
class RemoveTrueNorth(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.remove_true_north"
|
||||
bl_label = "Remove True North"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Remove true north"
|
||||
|
||||
def _execute(self, context):
|
||||
core.remove_true_north(tool.Ifc)
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
from blenderbim.bim.prop import StrProperty, Attribute
|
||||
import ifcopenshell.util.geolocation
|
||||
from blenderbim.bim.prop import Attribute
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
@@ -29,26 +30,104 @@ from bpy.props import (
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
from blenderbim.bim.module.georeference.data import GeoreferenceData
|
||||
|
||||
|
||||
def get_coordinate_operation_class(self, context):
|
||||
if not GeoreferenceData.is_loaded:
|
||||
GeoreferenceData.load()
|
||||
return GeoreferenceData.data["coordinate_operation_class"]
|
||||
|
||||
|
||||
def update_true_north_angle(self, context):
|
||||
if self.is_changing_angle:
|
||||
return
|
||||
self.is_changing_angle = True
|
||||
try:
|
||||
x, y = ifcopenshell.util.geolocation.angle2yaxis(float(self.true_north_angle))
|
||||
self.true_north_abscissa = str(x)
|
||||
self.true_north_ordinate = str(y)
|
||||
except:
|
||||
pass
|
||||
self.is_changing_angle = False
|
||||
|
||||
|
||||
def update_true_north_vector(self, context):
|
||||
if self.is_changing_angle:
|
||||
return
|
||||
self.is_changing_angle = True
|
||||
try:
|
||||
x = float(self.true_north_abscissa)
|
||||
y = float(self.true_north_ordinate)
|
||||
self.true_north_angle = str(round(ifcopenshell.util.geolocation.yaxis2angle(x, y), 7))
|
||||
except:
|
||||
pass
|
||||
self.is_changing_angle = False
|
||||
|
||||
|
||||
def update_grid_north_angle(self, context):
|
||||
if self.is_changing_angle:
|
||||
return
|
||||
self.is_changing_angle = True
|
||||
try:
|
||||
x, y = ifcopenshell.util.geolocation.angle2xaxis(float(self.grid_north_angle))
|
||||
self.x_axis_abscissa = str(x)
|
||||
self.x_axis_ordinate = str(y)
|
||||
self.x_axis_is_null = False
|
||||
except:
|
||||
pass
|
||||
self.is_changing_angle = False
|
||||
|
||||
|
||||
def update_grid_north_vector(self, context):
|
||||
if self.is_changing_angle:
|
||||
return
|
||||
self.is_changing_angle = True
|
||||
try:
|
||||
x = float(self.x_axis_abscissa)
|
||||
y = float(self.x_axis_ordinate)
|
||||
self.grid_north_angle = str(round(ifcopenshell.util.geolocation.xaxis2angle(x, y), 7))
|
||||
self.x_axis_is_null = False
|
||||
except:
|
||||
pass
|
||||
self.is_changing_angle = False
|
||||
|
||||
|
||||
class BIMGeoreferenceProperties(PropertyGroup):
|
||||
coordinate_operation_class: bpy.props.EnumProperty(
|
||||
items=get_coordinate_operation_class, name="Coordinate Operation Class"
|
||||
)
|
||||
is_changing_angle: BoolProperty(name="Is Changing Angle", default=False)
|
||||
is_editing: BoolProperty(name="Is Editing")
|
||||
map_conversion: CollectionProperty(name="Map Conversion", type=Attribute)
|
||||
is_editing_wcs: BoolProperty(name="Is Editing WCS")
|
||||
is_editing_true_north: BoolProperty(name="Is Editing True North")
|
||||
coordinate_operation: CollectionProperty(name="Coordinate Operation", type=Attribute)
|
||||
projected_crs: CollectionProperty(name="Projected CRS", type=Attribute)
|
||||
coordinate_input: StringProperty(name="Coordinate Input", description='Formatted "x,y,z" (without quotes)')
|
||||
coordinate_output: StringProperty(name="Coordinate Output", description='Formatted "x,y,z" (without quotes)')
|
||||
angle_degree_input_x: FloatProperty(name="Angle Degree Input", description="Angle (in degrees) rel to Easting")
|
||||
angle_degree_input_y: FloatProperty(name="Angle Degree Input", description="Angle (in degrees) rel to +Y")
|
||||
x_axis_abscissa_output: StringProperty(name="X Axis Abscissa Ordinate Output", description="X axis abscissa and ordinate", )
|
||||
x_axis_ordinate_output: StringProperty(name="X Axis Abscissa Ordinate Output", description="X axis abscissa and ordinate", )
|
||||
y_axis_abscissa_output: StringProperty(name="Y Axis Abscissa Ordinate Output", description="Y axis abscissa and ordinate", )
|
||||
y_axis_ordinate_output: StringProperty(name="Y Axis Abscissa Ordinate Output", description="Y axis abscissa and ordinate", )
|
||||
local_coordinates: StringProperty(
|
||||
name="Local Coordinates", description='Formatted "x,y,z" (without quotes)', default="0,0,0"
|
||||
)
|
||||
map_coordinates: StringProperty(
|
||||
name="Map Coordinates", description='Formatted "x,y,z" (without quotes)', default="0,0,0"
|
||||
)
|
||||
grid_north_angle: StringProperty(name="Grid North Angle", update=update_grid_north_angle)
|
||||
x_axis_abscissa: StringProperty(name="X Axis Abscissa", update=update_grid_north_vector)
|
||||
x_axis_ordinate: StringProperty(name="X Axis Ordinate", update=update_grid_north_vector)
|
||||
x_axis_is_null: BoolProperty(name="X Axis Is Null")
|
||||
host_model_origin: StringProperty(name="Host Model Origin")
|
||||
model_origin: StringProperty(name="Model Origin")
|
||||
has_blender_offset: BoolProperty(name="Has Blender Offset")
|
||||
blender_eastings: StringProperty(name="Blender Eastings", default="0")
|
||||
blender_northings: StringProperty(name="Blender Northings", default="0")
|
||||
blender_orthogonal_height: StringProperty(name="Blender Orthogonal Height", default="0")
|
||||
blender_x_axis_abscissa: StringProperty(name="Blender X Axis Abscissa", default="1")
|
||||
blender_x_axis_ordinate: StringProperty(name="Blender X Axis Ordinate", default="0")
|
||||
has_true_north: BoolProperty(name="Has True North", default=True)
|
||||
true_north_abscissa: StringProperty(name="True North Abscissa")
|
||||
true_north_ordinate: StringProperty(name="True North Ordinate")
|
||||
blender_offset_x: StringProperty(name="Blender Offset X", default="0")
|
||||
blender_offset_y: StringProperty(name="Blender Offset Y", default="0")
|
||||
blender_offset_z: StringProperty(name="Blender Offset Z", default="0")
|
||||
true_north_angle: StringProperty(name="True North Angle", update=update_true_north_angle)
|
||||
true_north_abscissa: StringProperty(name="True North Abscissa", update=update_true_north_vector)
|
||||
true_north_ordinate: StringProperty(name="True North Ordinate", update=update_true_north_vector)
|
||||
wcs_x: StringProperty(name="WCS X", default="0")
|
||||
wcs_y: StringProperty(name="WCS Y", default="0")
|
||||
wcs_z: StringProperty(name="WCS Z", default="0")
|
||||
wcs_rotation: StringProperty(name="WCS Rotation", default="0")
|
||||
|
||||
@@ -40,8 +40,9 @@ class BIM_PT_gis(Panel):
|
||||
GeoreferenceData.load()
|
||||
|
||||
if props.is_editing:
|
||||
return self.draw_editable_ui(context)
|
||||
self.draw_ui(context)
|
||||
self.draw_editable_ui(context)
|
||||
else:
|
||||
self.draw_ui(context)
|
||||
|
||||
def draw_editable_ui(self, context):
|
||||
props = context.scene.BIMGeoreferenceProperties
|
||||
@@ -53,53 +54,54 @@ class BIM_PT_gis(Panel):
|
||||
draw_attributes(props.projected_crs, self.layout)
|
||||
|
||||
row = self.layout.row()
|
||||
row.label(text="Map Conversion", icon="GRID")
|
||||
row.label(text="Coordinate Operation", icon="GRID")
|
||||
|
||||
for attribute in props.map_conversion:
|
||||
if attribute.name == "Scale" and hasattr(context.scene, "sun_pos_properties"):
|
||||
for attribute in props.coordinate_operation:
|
||||
if attribute.name == "XAxisAbscissa":
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.set_ifc_grid_north", text="Set IFC North")
|
||||
row.operator("bim.set_blender_grid_north", text="Set Blender North")
|
||||
draw_attribute(attribute, self.layout.row())
|
||||
|
||||
row = self.layout.row()
|
||||
row.label(text="True North", icon="LIGHT_SUN")
|
||||
row = self.layout.row()
|
||||
row.prop(props, "has_true_north")
|
||||
row = self.layout.row()
|
||||
row.prop(props, "true_north_abscissa")
|
||||
row = self.layout.row()
|
||||
row.prop(props, "true_north_ordinate")
|
||||
if hasattr(context.scene, "sun_pos_properties"):
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.set_ifc_true_north", text="Set IFC North")
|
||||
row.operator("bim.set_blender_true_north", text="Set Blender North")
|
||||
row.prop(props, "grid_north_angle", text="Angle")
|
||||
row.prop(props, "x_axis_is_null", icon="RADIOBUT_OFF" if props.x_axis_is_null else "RADIOBUT_ON", text="")
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "x_axis_abscissa", text="XAxis Abscissa")
|
||||
row.prop(props, "x_axis_is_null", icon="RADIOBUT_OFF" if props.x_axis_is_null else "RADIOBUT_ON", text="")
|
||||
elif attribute.name == "XAxisOrdinate":
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "x_axis_ordinate", text="XAxis Ordinate")
|
||||
row.prop(props, "x_axis_is_null", icon="RADIOBUT_OFF" if props.x_axis_is_null else "RADIOBUT_ON", text="")
|
||||
if hasattr(context.scene, "sun_pos_properties"):
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.set_ifc_grid_north", text="Set IFC North")
|
||||
row.operator("bim.set_blender_grid_north", text="Set Blender North")
|
||||
else:
|
||||
draw_attribute(attribute, self.layout.row())
|
||||
|
||||
def draw_ui(self, context):
|
||||
props = context.scene.BIMGeoreferenceProperties
|
||||
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
row = self.layout.row()
|
||||
row.label(text="IFC2X3 Fallback In Use", icon="ERROR")
|
||||
row.label(text="IFC2X3 Fallback In Use", icon="INFO")
|
||||
|
||||
if not GeoreferenceData.data["projected_crs"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Not Georeferenced")
|
||||
row = self.layout.row()
|
||||
row.label(text="Not Georeferenced", icon="ERROR")
|
||||
if tool.Ifc.get_schema() != "IFC2X3":
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "coordinate_operation_class", text="")
|
||||
row.operator("bim.add_georeferencing", icon="ADD", text="")
|
||||
|
||||
if props.has_blender_offset:
|
||||
row = self.layout.row()
|
||||
row.label(text="Blender Offset", icon="TRACKING_REFINE_FORWARDS")
|
||||
row.label(text="Blender Session Origin", icon="TRACKING_REFINE_FORWARDS")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Eastings")
|
||||
row.label(text=f"Eastings ({GeoreferenceData.data['local_unit_symbol']})")
|
||||
row.label(text=props.blender_eastings)
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Northings")
|
||||
row.label(text=f"Northings ({GeoreferenceData.data['local_unit_symbol']})")
|
||||
row.label(text=props.blender_northings)
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="OrthogonalHeight")
|
||||
row.label(text=f"OrthogonalHeight ({GeoreferenceData.data['local_unit_symbol']})")
|
||||
row.label(text=props.blender_orthogonal_height)
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="XAxisAbscissa")
|
||||
@@ -125,30 +127,140 @@ class BIM_PT_gis(Panel):
|
||||
row.label(text=key)
|
||||
row.label(text=str(value))
|
||||
|
||||
if GeoreferenceData.data["map_conversion"]:
|
||||
if GeoreferenceData.data["coordinate_operation"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Map Conversion", icon="GRID")
|
||||
row.label(text="Coordinate Operation", icon="GRID")
|
||||
|
||||
for key, value in GeoreferenceData.data["map_conversion"].items():
|
||||
for key, value in GeoreferenceData.data["coordinate_operation"].items():
|
||||
if value is None:
|
||||
continue
|
||||
if key == "type":
|
||||
key = "Type"
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=key)
|
||||
if key in ("Eastings", "Northings", "OrthogonalHeight"):
|
||||
row.label(text=f"{key} ({GeoreferenceData.data['map_unit_symbol']})")
|
||||
else:
|
||||
row.label(text=key)
|
||||
row.label(text=str(value))
|
||||
if key == "XAxisOrdinate":
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Derived Angle")
|
||||
row.label(text=GeoreferenceData.data["map_derived_angle"])
|
||||
|
||||
if GeoreferenceData.data["true_north"]:
|
||||
row = self.layout.row()
|
||||
row.label(text="True North", icon="LIGHT_SUN")
|
||||
|
||||
class BIM_PT_gis_true_north(Panel):
|
||||
bl_idname = "BIM_PT_gis_true_north"
|
||||
bl_label = "True North"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_gis"
|
||||
|
||||
def draw(self, context):
|
||||
if not GeoreferenceData.is_loaded:
|
||||
GeoreferenceData.load()
|
||||
|
||||
self.props = context.scene.BIMGeoreferenceProperties
|
||||
|
||||
if self.props.is_editing_true_north:
|
||||
self.draw_editable_ui(context)
|
||||
else:
|
||||
self.draw_ui()
|
||||
|
||||
def draw_editable_ui(self, context):
|
||||
row = self.layout.row()
|
||||
row.prop(self.props, "true_north_angle", text="Angle")
|
||||
row = self.layout.row()
|
||||
row.prop(self.props, "true_north_abscissa", text="Abscissa")
|
||||
row = self.layout.row()
|
||||
row.prop(self.props, "true_north_ordinate", text="Ordinate")
|
||||
if hasattr(context.scene, "sun_pos_properties"):
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Vector")
|
||||
row.label(text=str(GeoreferenceData.data["true_north"][0:2])[1:-1])
|
||||
row.operator("bim.set_ifc_true_north", text="Set IFC North")
|
||||
row.operator("bim.set_blender_true_north", text="Set Blender North")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.edit_true_north", icon="CHECKMARK")
|
||||
row.operator("bim.disable_editing_true_north", icon="CANCEL", text="")
|
||||
|
||||
def draw_ui(self):
|
||||
if GeoreferenceData.data["true_north"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Abscissa")
|
||||
row.label(text=str(GeoreferenceData.data["true_north"][0]))
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Ordinate")
|
||||
row.label(text=str(GeoreferenceData.data["true_north"][1]))
|
||||
row.operator("bim.enable_editing_true_north", icon="GREASEPENCIL", text="")
|
||||
row.operator("bim.remove_true_north", icon="X", text="")
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Derived Angle")
|
||||
row.label(text=GeoreferenceData.data["true_derived_angle"])
|
||||
else:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="No True North Found", icon="LIGHT_SUN")
|
||||
row.operator("bim.enable_editing_true_north", icon="GREASEPENCIL", text="")
|
||||
|
||||
|
||||
class BIM_PT_gis_wcs(Panel):
|
||||
bl_idname = "BIM_PT_gis_wcs"
|
||||
bl_label = "World Coordinate System"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_gis"
|
||||
|
||||
def draw(self, context):
|
||||
if not GeoreferenceData.is_loaded:
|
||||
GeoreferenceData.load()
|
||||
|
||||
props = context.scene.BIMGeoreferenceProperties
|
||||
|
||||
if props.is_editing_wcs:
|
||||
self.draw_editable_ui(context)
|
||||
else:
|
||||
self.draw_ui()
|
||||
|
||||
def draw_ui(self):
|
||||
if GeoreferenceData.data["world_coordinate_system"]["has_transformation"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Unrecommended Transformation Found", icon="ERROR")
|
||||
row.operator("bim.enable_editing_wcs", icon="GREASEPENCIL", text="")
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=f"X ({GeoreferenceData.data['local_unit_symbol']})")
|
||||
row.label(text=str(GeoreferenceData.data["world_coordinate_system"]["x"]))
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=f"Y ({GeoreferenceData.data['local_unit_symbol']})")
|
||||
row.label(text=str(GeoreferenceData.data["world_coordinate_system"]["y"]))
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=f"Z ({GeoreferenceData.data['local_unit_symbol']})")
|
||||
row.label(text=str(GeoreferenceData.data["world_coordinate_system"]["z"]))
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Rotation")
|
||||
row.label(text=str(GeoreferenceData.data["world_coordinate_system"]["rotation"]))
|
||||
else:
|
||||
row = self.layout.row()
|
||||
row.label(text="No WCS Transformation", icon="CHECKMARK")
|
||||
row.operator("bim.enable_editing_wcs", icon="GREASEPENCIL", text="")
|
||||
|
||||
def draw_editable_ui(self, context):
|
||||
props = context.scene.BIMGeoreferenceProperties
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="World Coordinate System", icon="EMPTY_ARROWS")
|
||||
row.operator("bim.edit_wcs", icon="CHECKMARK", text="")
|
||||
row.operator("bim.disable_editing_wcs", icon="CANCEL", text="")
|
||||
|
||||
row = self.layout.row()
|
||||
row.prop(props, "wcs_x")
|
||||
row = self.layout.row()
|
||||
row.prop(props, "wcs_y")
|
||||
row = self.layout.row()
|
||||
row.prop(props, "wcs_z")
|
||||
row = self.layout.row()
|
||||
row.prop(props, "wcs_rotation")
|
||||
|
||||
|
||||
class BIM_PT_gis_calculator(Panel):
|
||||
@@ -161,33 +273,17 @@ class BIM_PT_gis_calculator(Panel):
|
||||
bl_parent_id = "BIM_PT_gis"
|
||||
|
||||
def draw(self, context):
|
||||
if not GeoreferenceData.is_loaded:
|
||||
GeoreferenceData.load()
|
||||
|
||||
props = context.scene.BIMGeoreferenceProperties
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "coordinate_input", text="Input")
|
||||
row.prop(props, "local_coordinates", text=f"Local ({GeoreferenceData.data['local_unit_symbol']})")
|
||||
row.operator("bim.get_cursor_location", text="", icon="TRACKER")
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "coordinate_output", text="Output")
|
||||
row.operator("bim.set_cursor_location", text="", icon="TRACKER")
|
||||
row = self.layout.row()
|
||||
row.prop(props, "map_coordinates", text=f"Map ({GeoreferenceData.data['map_unit_symbol']})")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.convert_local_to_global", text="Local to Global")
|
||||
row.operator("bim.convert_global_to_local", text="Global to Local")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Orientation Calculator", icon="TRACKING_REFINE_FORWARDS")
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "angle_degree_input_x", text="Angle (°) rel to Easting")
|
||||
row.operator("bim.convert_angle_to_coord", text="", icon="FILE_REFRESH").type = "rel_x"
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "x_axis_abscissa_output", text="XAxis Abscissa")
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "x_axis_ordinate_output", text="XAxis Ordinate")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "angle_degree_input_y", text="Angle (°) rel to to +Y")
|
||||
row.operator("bim.convert_angle_to_coord", text="", icon="FILE_REFRESH").type = "rel_y"
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "y_axis_abscissa_output", text="North Abscissa")
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "y_axis_ordinate_output", text="North Ordinate")
|
||||
|
||||
@@ -58,7 +58,6 @@ classes = (
|
||||
operator.SelectByMaterial,
|
||||
operator.UnassignMaterial,
|
||||
operator.UnassignMaterialStyle,
|
||||
operator.UnlinkMaterial,
|
||||
prop.Material,
|
||||
prop.BIMMaterialProperties,
|
||||
prop.BIMObjectMaterialProperties,
|
||||
|
||||
@@ -118,7 +118,6 @@ class AddMaterial(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.add_material"
|
||||
bl_label = "Add Material"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty(name="Material Name")
|
||||
name: bpy.props.StringProperty(default="Default")
|
||||
category: bpy.props.StringProperty(default="")
|
||||
description: bpy.props.StringProperty(default="")
|
||||
@@ -135,8 +134,7 @@ class AddMaterial(bpy.types.Operator, tool.Ifc.Operator):
|
||||
row.prop(self, "category", text="Category")
|
||||
|
||||
def _execute(self, context):
|
||||
obj = bpy.data.materials.get(self.obj) if self.obj else None
|
||||
core.add_material(tool.Ifc, tool.Material, tool.Style, obj=obj, name=self.name, category=self.category, description=self.description)
|
||||
core.add_material(tool.Ifc, tool.Material, name=self.name, category=self.category, description=self.description)
|
||||
material_prop_purge()
|
||||
|
||||
|
||||
@@ -182,7 +180,7 @@ class RemoveMaterial(bpy.types.Operator, tool.Ifc.Operator):
|
||||
material: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
res = core.remove_material(tool.Ifc, tool.Material, tool.Style, material=tool.Ifc.get().by_id(self.material))
|
||||
res = core.remove_material(tool.Ifc, tool.Material, material=tool.Ifc.get().by_id(self.material))
|
||||
if not res:
|
||||
self.report({"ERROR"}, "Material is used in material sets and cannot be removed.")
|
||||
return {"CANCELLED"}
|
||||
@@ -198,16 +196,6 @@ class RemoveMaterialSet(bpy.types.Operator, tool.Ifc.Operator):
|
||||
core.remove_material_set(tool.Ifc, tool.Material, material=tool.Ifc.get().by_id(self.material))
|
||||
|
||||
|
||||
class UnlinkMaterial(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.unlink_material"
|
||||
bl_label = "Unlink Material"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
material: bpy.props.IntProperty(name="Material ID")
|
||||
|
||||
def _execute(self, context):
|
||||
core.unlink_material(tool.Ifc, obj=tool.Ifc.get_object(tool.Ifc.get().by_id(self.material)))
|
||||
|
||||
|
||||
class AssignMaterial(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.assign_material"
|
||||
bl_label = "Assign Material"
|
||||
|
||||
@@ -69,8 +69,6 @@ class BIM_PT_materials(Panel):
|
||||
op.material = material.ifc_definition_id
|
||||
op = row.operator("bim.enable_editing_material_style", text="", icon="SHADING_RENDERED")
|
||||
op.material = material.ifc_definition_id
|
||||
op = row.operator("bim.unlink_material", icon="UNLINKED", text="")
|
||||
op.material = material.ifc_definition_id
|
||||
row.operator("bim.remove_material", text="", icon="X").material = material.ifc_definition_id
|
||||
|
||||
self.draw_editing_ui()
|
||||
@@ -152,7 +150,7 @@ class BIM_PT_object_material(Panel):
|
||||
if not ObjectMaterialData.data["materials"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="No Materials Available")
|
||||
row.operator("bim.add_material", icon="ADD", text="").obj = ""
|
||||
row.operator("bim.add_material", icon="ADD", text="")
|
||||
return
|
||||
|
||||
if ObjectMaterialData.data["type_material"]:
|
||||
|
||||
@@ -78,7 +78,7 @@ class ProfileDecorator:
|
||||
self.draw_batch("TRIS", vertices_coords, faces_color, face_indices)
|
||||
|
||||
def __call__(self, context, get_custom_bmesh=None, draw_faces=False, exit_edit_mode_callback=None):
|
||||
self.addon_prefs = context.preferences.addons["blenderbim"].preferences
|
||||
self.addon_prefs = tool.Blender.get_addon_preferences()
|
||||
selected_elements_color = self.addon_prefs.decorator_color_selected
|
||||
unselected_elements_color = self.addon_prefs.decorator_color_unselected
|
||||
special_elements_color = self.addon_prefs.decorator_color_special
|
||||
|
||||
@@ -224,7 +224,7 @@ class FilledOpeningGenerator:
|
||||
else:
|
||||
opening_obj = tool.Ifc.get_object(opening)
|
||||
if opening_obj:
|
||||
tool.Ifc.unlink(obj=opening_obj)
|
||||
tool.Ifc.unlink(element=opening)
|
||||
bpy.data.objects.remove(opening_obj)
|
||||
|
||||
filling_obj = tool.Ifc.get_object(filling)
|
||||
@@ -541,20 +541,23 @@ class AddBoolean(Operator, tool.Ifc.Operator):
|
||||
obj1, obj2 = context.selected_objects
|
||||
element1 = tool.Ifc.get_entity(obj1)
|
||||
element2 = tool.Ifc.get_entity(obj2)
|
||||
if element1 and element2:
|
||||
return {"FINISHED"}
|
||||
if not element1 and not element2:
|
||||
if not (bool(element1) ^ bool(element2)):
|
||||
self.report({"INFO"}, "One of the selected objects should be blender object and another IFC object.")
|
||||
return {"FINISHED"}
|
||||
|
||||
# element1 - IFC object, element2 - blender object.
|
||||
if element2 and not element1:
|
||||
obj1, obj2 = obj2, obj1
|
||||
element1, element2 = element2, element1
|
||||
if not obj1.data or not hasattr(obj1.data, "BIMMeshProperties"):
|
||||
|
||||
representation = tool.Geometry.get_active_representation(obj1)
|
||||
if not representation:
|
||||
self.report({"INFO"}, "No representation found for the selected IFC object.")
|
||||
return {"FINISHED"}
|
||||
representation = tool.Ifc.get().by_id(obj1.data.BIMMeshProperties.ifc_definition_id)
|
||||
|
||||
if not obj2.data or len(obj2.data.polygons) <= 4: # It takes 4 faces to create a closed solid
|
||||
mesh_data = {"type": "IfcHalfSpaceSolid", "matrix": obj1.matrix_world.inverted() @ obj2.matrix_world}
|
||||
elif obj2.data:
|
||||
else:
|
||||
mesh_data = {"type": "Mesh", "blender_obj": obj1, "blender_void": obj2}
|
||||
|
||||
booleans = ifcopenshell.api.run(
|
||||
@@ -574,7 +577,6 @@ class AddBoolean(Operator, tool.Ifc.Operator):
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
tool.Ifc.unlink(obj=obj2)
|
||||
bpy.data.objects.remove(obj2)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -772,9 +774,9 @@ class HideOpenings(Operator, tool.Ifc.Operator):
|
||||
for opening in openings:
|
||||
opening_obj = tool.Ifc.get_object(opening)
|
||||
if opening_obj:
|
||||
to_delete.add(opening_obj)
|
||||
for opening_obj in to_delete:
|
||||
tool.Ifc.unlink(obj=opening_obj)
|
||||
to_delete.add((opening, opening_obj))
|
||||
for opening, opening_obj in to_delete:
|
||||
tool.Ifc.unlink(element=opening)
|
||||
bpy.data.objects.remove(opening_obj)
|
||||
tool.Model.clear_scene_openings()
|
||||
return {"FINISHED"}
|
||||
@@ -824,7 +826,7 @@ class EditOpenings(Operator, tool.Ifc.Operator):
|
||||
building_objs.update(
|
||||
self.get_all_building_objects_of_similar_openings(opening)
|
||||
) # NB this has nothing to do with clone similar_opening
|
||||
tool.Ifc.unlink(element=opening, obj=opening_obj)
|
||||
tool.Ifc.unlink(element=opening)
|
||||
bpy.data.objects.remove(opening_obj)
|
||||
|
||||
tool.Model.reload_body_representation(building_objs)
|
||||
@@ -902,7 +904,7 @@ class DecorationsHandler:
|
||||
batch.draw(shader)
|
||||
|
||||
def __call__(self, context):
|
||||
self.addon_prefs = context.preferences.addons["blenderbim"].preferences
|
||||
self.addon_prefs = tool.Blender.get_addon_preferences()
|
||||
selected_elements_color = self.addon_prefs.decorator_color_selected
|
||||
unselected_elements_color = self.addon_prefs.decorator_color_unselected
|
||||
special_elements_color = self.addon_prefs.decorator_color_special
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
import bpy
|
||||
import mathutils
|
||||
import numpy as np
|
||||
from functools import reduce
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
@@ -39,7 +40,7 @@ from mathutils import Vector, Matrix
|
||||
from bpy_extras.object_utils import AddObjectHelper
|
||||
from . import prop
|
||||
import json
|
||||
from typing import Any, Union
|
||||
from typing import Any, Union, Optional, assert_never
|
||||
|
||||
|
||||
class EnableAddType(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -513,88 +514,49 @@ def regenerate_profile_usage(usecase_path, ifc_file, settings):
|
||||
|
||||
|
||||
def ensure_material_assigned(usecase_path: str, ifc_file: ifcopenshell.file, settings: dict[str, Any]) -> None:
|
||||
if usecase_path == "material.assign_material":
|
||||
if not settings.get("material", None):
|
||||
return
|
||||
elements = settings["products"]
|
||||
elements = settings["products"]
|
||||
material = settings.get("material")
|
||||
if material:
|
||||
assigned_material = settings["material"]
|
||||
else:
|
||||
elements = []
|
||||
for rel in ifc_file.by_type("IfcRelAssociatesMaterial"):
|
||||
if rel.RelatingMaterial == settings["material"] or [
|
||||
e for e in ifc_file.traverse(rel.RelatingMaterial) if e == settings["material"]
|
||||
]:
|
||||
elements.extend(rel.RelatedObjects)
|
||||
material_type: ifcopenshell.util.element.MATERIAL_TYPE = settings["type"]
|
||||
element = elements[0]
|
||||
if material_type == "IfcMaterial":
|
||||
assigned_material = ifcopenshell.util.element.get_material(element, should_inherit=False)
|
||||
assert assigned_material # Type checker.
|
||||
# Material usages just inherit the style from the type material, so can't override it.
|
||||
elif material_type in ("IfcMaterialLayerSetUsage", "IfcMaterialProfileSetUsage"):
|
||||
return
|
||||
# If type is Set and no material argument were provided, then Set was just created
|
||||
# and not yet have any IfcMaterials.
|
||||
elif material_type in ("IfcMaterialConstituentSet", "IfcMaterialLayerSet", "IfcMaterialProfileSet"):
|
||||
return
|
||||
elif material_type == "IfcMaterialList":
|
||||
assert False, "Current assign_material implementation requires 'material' argument for IfcMaterialList."
|
||||
else:
|
||||
assert_never(material_type)
|
||||
|
||||
update_blender_ifc_materials(elements)
|
||||
for element in elements[:]:
|
||||
if element.is_a("IfcElementType"):
|
||||
elements.extend(tool.Model.get_occurrences_without_material_override(element))
|
||||
|
||||
tool.Model.apply_ifc_material_changes(elements, assigned_material=settings["material"])
|
||||
|
||||
|
||||
def ensure_material_unassigned(usecase_path: str, ifc_file: ifcopenshell.file, settings: dict[str, Any]) -> None:
|
||||
elements = settings["products"]
|
||||
if elements[0].is_a("IfcElementType"):
|
||||
elements.extend(ifcopenshell.util.element.get_types(elements[0]))
|
||||
update_blender_ifc_materials(elements)
|
||||
|
||||
# unassign_material could be called when product is about to get removed
|
||||
# and representation might be already removed.
|
||||
elements = [
|
||||
e
|
||||
for e in elements
|
||||
if (obj := tool.Ifc.get_object(e))
|
||||
and obj.data
|
||||
and tool.Ifc.get_entity_by_id(obj.data.BIMMeshProperties.ifc_definition_id)
|
||||
]
|
||||
|
||||
def update_blender_ifc_materials(elements: list[ifcopenshell.entity_instance]) -> None:
|
||||
"""update mesh blender materials that have ifc material connected to them
|
||||
by replacing them with `blender_material`"""
|
||||
# since different elements can share meshes (e.g. occurrecnes without openings)
|
||||
# we need to make sure not to affect them accidentally
|
||||
meshes_users: dict[bpy.types.Mesh, set[bpy.types.Object]] = dict()
|
||||
for obj in bpy.data.objects:
|
||||
if not obj.data:
|
||||
continue
|
||||
meshes_users.setdefault(obj.data, set()).add(obj)
|
||||
|
||||
objects: set[bpy.types.Object] = set()
|
||||
for element in elements:
|
||||
obj: bpy.types.Object = tool.Ifc.get_object(element)
|
||||
if not obj or not obj.data:
|
||||
continue
|
||||
objects.add(obj)
|
||||
|
||||
meshes: set[bpy.types.Mesh] = {obj.data for obj in objects}
|
||||
|
||||
for mesh in meshes:
|
||||
mesh_users = meshes_users[mesh]
|
||||
if not mesh_users.issubset(objects):
|
||||
continue
|
||||
|
||||
# NOTE: we need `obj` as removing materials and appending them to `mesh.materials`
|
||||
# will mess up mesh faces material indices
|
||||
|
||||
# NOTE: we make an assumption here that all mesh users
|
||||
# have the same material - they either inherit it from the type
|
||||
# or type doesn't have a material.
|
||||
#
|
||||
# If we add option to UI to add materials overriding type materials
|
||||
# then this assumption won't be safe anymore
|
||||
|
||||
obj = next(iter(mesh_users))
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
current_material = ifcopenshell.util.element.get_material(element)
|
||||
if current_material:
|
||||
current_material = tool.Ifc.get_object(current_material)
|
||||
|
||||
material_replaced = False
|
||||
|
||||
for material_slot in obj.material_slots:
|
||||
material = material_slot.material
|
||||
if material is None:
|
||||
continue
|
||||
ifc_material = tool.Ifc.get_entity(material)
|
||||
# it's blender material for style, so ignore it
|
||||
if not ifc_material:
|
||||
continue
|
||||
if ifc_material == current_material:
|
||||
continue
|
||||
material_slot.material = current_material
|
||||
material_replaced = True
|
||||
|
||||
if not material_replaced and current_material:
|
||||
mesh.materials.append(current_material)
|
||||
|
||||
# clear empty slots
|
||||
for i, material in reversed(list(enumerate(mesh.materials[:]))):
|
||||
if material is None:
|
||||
mesh.materials.pop(index=i)
|
||||
for element in elements[:]:
|
||||
if element.is_a("IfcElementType"):
|
||||
elements.extend(tool.Model.get_occurrences_without_material_override(element))
|
||||
tool.Model.apply_ifc_material_changes(elements)
|
||||
|
||||
@@ -108,7 +108,7 @@ def update_railing_modifier_ifc_data(context):
|
||||
tool.Model.replace_object_ifc_representation(body, obj, model_representation)
|
||||
|
||||
# hacky way to ensure tha ifc representation won't get tessellated at project save
|
||||
IfcStore.edited_objs.discard(obj)
|
||||
tool.Ifc.finish_edit(obj)
|
||||
|
||||
elif props.railing_type == "FRAMELESS_PANEL":
|
||||
tool.Ifc.edit(obj)
|
||||
|
||||
@@ -17,34 +17,34 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import ifcopenshell.file
|
||||
import blenderbim.bim.handler
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.ifc import IFC_CONNECTED_TYPE
|
||||
from typing import Any
|
||||
|
||||
|
||||
def sync_name(usecase_path, ifc_file, settings):
|
||||
def sync_name(usecase_path: str, ifc_file: ifcopenshell.file, settings: dict[str, Any]) -> None:
|
||||
if usecase_path == "attribute.edit_attributes":
|
||||
element = settings["product"]
|
||||
elif usecase_path == "style.edit_presentation_style":
|
||||
element = settings["style"]
|
||||
else:
|
||||
raise Exception(f"Unsupported usecase: '{usecase_path}'.")
|
||||
|
||||
element: ifcopenshell.entity_instance
|
||||
if "Name" not in settings["attributes"]:
|
||||
return
|
||||
obj = tool.Ifc.get_object(element)
|
||||
if not obj:
|
||||
return
|
||||
obj: IFC_CONNECTED_TYPE
|
||||
if isinstance(obj, bpy.types.Object):
|
||||
new_name = "{}/{}".format(element.is_a(), settings["attributes"]["Name"] or "Unnamed")
|
||||
collection = obj.BIMObjectProperties.collection
|
||||
if collection:
|
||||
collection.name = new_name
|
||||
elif isinstance(obj, bpy.types.Material):
|
||||
new_name = settings["attributes"]["Name"] or "Unnamed"
|
||||
material = tool.Ifc.get_entity(obj)
|
||||
if material and material != element:
|
||||
material.Name = new_name
|
||||
style = tool.Style.get_style(obj)
|
||||
if style and style != element:
|
||||
style.Name = new_name
|
||||
collection = obj.BIMObjectProperties.collection
|
||||
if collection:
|
||||
collection.name = new_name
|
||||
obj.name = new_name
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
|
||||
@@ -30,6 +30,7 @@ classes = (
|
||||
operator.ChangeLibraryElement,
|
||||
operator.CreateClippingPlane,
|
||||
operator.CreateProject,
|
||||
operator.ClearRecentIFCProjects,
|
||||
operator.DisableCulling,
|
||||
operator.DisableEditingHeader,
|
||||
operator.EditHeader,
|
||||
@@ -58,7 +59,6 @@ classes = (
|
||||
operator.UnassignLibraryDeclaration,
|
||||
operator.UnlinkIfc,
|
||||
operator.UnloadLink,
|
||||
operator.UnloadProject,
|
||||
workspace.ExploreHotkey,
|
||||
prop.LibraryElement,
|
||||
prop.FilterCategory,
|
||||
@@ -66,6 +66,8 @@ classes = (
|
||||
prop.BIMProjectProperties,
|
||||
ui.BIM_MT_new_project,
|
||||
ui.BIM_MT_project,
|
||||
ui.BIM_PT_new_project_wizard,
|
||||
ui.BIM_MT_recent_projects,
|
||||
ui.BIM_PT_project,
|
||||
ui.BIM_PT_project_library,
|
||||
ui.BIM_PT_links,
|
||||
|
||||
@@ -43,7 +43,7 @@ class ProjectData:
|
||||
|
||||
@classmethod
|
||||
def get_export_schema(cls):
|
||||
return [(s, s, "") for s in IfcStore.schema_identifiers]
|
||||
return [(s, "IFC4X3" if s == "IFC4X3_ADD2" else s, "") for s in IfcStore.schema_identifiers]
|
||||
|
||||
@classmethod
|
||||
def library_file(cls):
|
||||
|
||||
@@ -64,7 +64,7 @@ class ProjectDecorator:
|
||||
batch.draw(shader)
|
||||
|
||||
def __call__(self, context):
|
||||
self.addon_prefs = context.preferences.addons["blenderbim"].preferences
|
||||
self.addon_prefs = tool.Blender.get_addon_preferences()
|
||||
selected_elements_color = self.addon_prefs.decorator_color_selected
|
||||
unselected_elements_color = self.addon_prefs.decorator_color_unselected
|
||||
special_elements_color = self.addon_prefs.decorator_color_special
|
||||
@@ -128,7 +128,7 @@ class ClippingPlaneDecorator:
|
||||
batch.draw(shader)
|
||||
|
||||
def __call__(self, context):
|
||||
self.addon_prefs = context.preferences.addons["blenderbim"].preferences
|
||||
self.addon_prefs = tool.Blender.get_addon_preferences()
|
||||
selected_elements_color = self.addon_prefs.decorator_color_selected
|
||||
unselected_elements_color = self.addon_prefs.decorator_color_unselected
|
||||
special_elements_color = self.addon_prefs.decorator_color_special
|
||||
|
||||
@@ -19,10 +19,12 @@
|
||||
import os
|
||||
import bpy
|
||||
import time
|
||||
import json
|
||||
import logging
|
||||
import tempfile
|
||||
import traceback
|
||||
import subprocess
|
||||
import datetime
|
||||
import numpy as np
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
@@ -43,12 +45,11 @@ from blenderbim.bim.ui import IFCFileSelector
|
||||
from blenderbim.bim import import_ifc
|
||||
from blenderbim.bim import export_ifc
|
||||
from collections import defaultdict
|
||||
import json
|
||||
from math import radians
|
||||
from pathlib import Path
|
||||
from mathutils import Vector, Matrix
|
||||
from bpy.app.handlers import persistent
|
||||
from ifcopenshell.geom import ShapeElementType, ShapeType
|
||||
from ifcopenshell.geom import ShapeElementType
|
||||
from blenderbim.bim.module.project.data import LinksData
|
||||
from blenderbim.bim.module.project.decorator import ProjectDecorator, ClippingPlaneDecorator
|
||||
from typing import Union
|
||||
@@ -116,7 +117,6 @@ class CreateProject(bpy.types.Operator):
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMProjectProperties
|
||||
template = None if props.template_file == "0" else props.template_file
|
||||
blenderbim.bim.schema.reload(props.export_schema)
|
||||
if tool.Blender.is_default_scene():
|
||||
for obj in bpy.data.objects:
|
||||
bpy.data.objects.remove(obj)
|
||||
@@ -125,6 +125,7 @@ class CreateProject(bpy.types.Operator):
|
||||
for mat in bpy.data.materials:
|
||||
bpy.data.materials.remove(mat)
|
||||
core.create_project(tool.Ifc, tool.Project, tool.Spatial, schema=props.export_schema, template=template)
|
||||
blenderbim.bim.schema.reload(tool.Ifc.get().schema_identifier)
|
||||
tool.Blender.register_toolbar()
|
||||
|
||||
def rollback(self, data):
|
||||
@@ -470,8 +471,7 @@ class AppendLibraryElement(bpy.types.Operator):
|
||||
ifc_import_settings = import_ifc.IfcImportSettings.factory(context, IfcStore.path, logger)
|
||||
ifc_importer = import_ifc.IfcImporter(ifc_import_settings)
|
||||
ifc_importer.file = self.file
|
||||
blender_material = ifc_importer.create_material(element)
|
||||
self.import_material_styles(blender_material, element, ifc_importer)
|
||||
self.import_material_styles(element, ifc_importer)
|
||||
|
||||
def import_product_from_ifc(self, element: ifcopenshell.entity_instance, context: bpy.types.Context) -> None:
|
||||
self.file = IfcStore.get_file()
|
||||
@@ -515,8 +515,7 @@ class AppendLibraryElement(bpy.types.Operator):
|
||||
for material in ifcopenshell.util.element.get_materials(element):
|
||||
if IfcStore.get_element(material.id()):
|
||||
continue
|
||||
blender_material = ifc_importer.create_material(material)
|
||||
self.import_material_styles(blender_material, material, ifc_importer)
|
||||
self.import_material_styles(material, ifc_importer)
|
||||
|
||||
def import_styles(self, element: ifcopenshell.entity_instance, ifc_importer: import_ifc.IfcImporter) -> None:
|
||||
if element.is_a("IfcTypeProduct"):
|
||||
@@ -533,7 +532,6 @@ class AppendLibraryElement(bpy.types.Operator):
|
||||
|
||||
def import_material_styles(
|
||||
self,
|
||||
blender_material: bpy.types.Material,
|
||||
material: ifcopenshell.entity_instance,
|
||||
ifc_importer: import_ifc.IfcImporter,
|
||||
) -> None:
|
||||
@@ -541,7 +539,7 @@ class AppendLibraryElement(bpy.types.Operator):
|
||||
return
|
||||
for element in self.file.traverse(material.HasRepresentation[0]):
|
||||
if element.is_a("IfcSurfaceStyle") and not IfcStore.get_element(element.id()):
|
||||
ifc_importer.create_style(element, blender_material)
|
||||
ifc_importer.create_style(element)
|
||||
|
||||
|
||||
class EnableEditingHeader(bpy.types.Operator):
|
||||
@@ -658,6 +656,46 @@ class LoadProject(bpy.types.Operator, IFCFileSelector):
|
||||
is_advanced: bpy.props.BoolProperty(name="Enable Advanced Mode", default=False)
|
||||
use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=False)
|
||||
should_start_fresh_session: bpy.props.BoolProperty(name="Should Start Fresh Session", default=True)
|
||||
use_detailed_tooltip: bpy.props.BoolProperty(default=False, options={"HIDDEN"})
|
||||
|
||||
@classmethod
|
||||
def description(cls, context, properties):
|
||||
tooltip = cls.bl_description
|
||||
if not properties.use_detailed_tooltip:
|
||||
return tooltip
|
||||
|
||||
filepath = properties.filepath
|
||||
if not filepath:
|
||||
return tooltip
|
||||
filepath = Path(filepath)
|
||||
tooltip += f".\n\n"
|
||||
if not filepath.exists():
|
||||
tooltip += "File does not exist"
|
||||
return tooltip
|
||||
|
||||
def get_modified_date(st_mtime: float) -> str:
|
||||
mod_time = datetime.datetime.fromtimestamp(st_mtime)
|
||||
|
||||
today = datetime.date.today()
|
||||
if mod_time.date() == today:
|
||||
return f"Today {mod_time.strftime('%I:%M %p')}"
|
||||
elif mod_time.date() == today - datetime.timedelta(days=1):
|
||||
return f"Yesterday {mod_time.strftime('%I:%M %p')}"
|
||||
return mod_time.strftime("%d %b %Y")
|
||||
|
||||
def get_file_size(size_bytes: float) -> str:
|
||||
if size_bytes < 1024 * 1024: # Less than 1 MiB
|
||||
size = size_bytes / 1024
|
||||
return f"{size:.0f} KiB"
|
||||
else:
|
||||
size = size_bytes / (1024 * 1024)
|
||||
return f"{size:.1f} MiB"
|
||||
|
||||
file_stat = filepath.stat()
|
||||
tooltip += f"Modified: {get_modified_date(file_stat.st_mtime)}"
|
||||
tooltip += f"\nSize: {get_file_size(file_stat.st_size)}"
|
||||
|
||||
return tooltip
|
||||
|
||||
def execute(self, context):
|
||||
@persistent
|
||||
@@ -686,10 +724,13 @@ class LoadProject(bpy.types.Operator, IFCFileSelector):
|
||||
for obj in bpy.data.objects:
|
||||
bpy.data.objects.remove(obj)
|
||||
|
||||
context.scene.BIMProperties.ifc_file = self.get_filepath()
|
||||
filepath = Path(self.get_filepath())
|
||||
context.scene.BIMProperties.ifc_file = str(filepath)
|
||||
context.scene.BIMProjectProperties.is_loading = True
|
||||
context.scene.BIMProjectProperties.total_elements = len(tool.Ifc.get().by_type("IfcElement"))
|
||||
tool.Blender.register_toolbar()
|
||||
tool.Blender.add_recent_ifc_project(filepath.absolute())
|
||||
|
||||
if not self.is_advanced:
|
||||
bpy.ops.bim.load_project_elements()
|
||||
except:
|
||||
@@ -706,16 +747,13 @@ class LoadProject(bpy.types.Operator, IFCFileSelector):
|
||||
IFCFileSelector.draw(self, context)
|
||||
|
||||
|
||||
class UnloadProject(bpy.types.Operator):
|
||||
bl_idname = "bim.unload_project"
|
||||
bl_label = "Unload Project"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Unload the IFC project"
|
||||
class ClearRecentIFCProjects(bpy.types.Operator):
|
||||
bl_idname = "bim.clear_recent_ifc_projects"
|
||||
bl_label = "Clear Recent IFC Projects List"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def execute(self, context):
|
||||
IfcStore.purge()
|
||||
context.scene.BIMProperties.ifc_file = ""
|
||||
context.scene.BIMProjectProperties.is_loading = False
|
||||
tool.Blender.clear_recent_ifc_projects()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -859,7 +897,19 @@ class LinkIfc(bpy.types.Operator):
|
||||
directory: bpy.props.StringProperty(subtype="DIR_PATH")
|
||||
filter_glob: bpy.props.StringProperty(default="*.ifc", options={"HIDDEN"})
|
||||
use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=False)
|
||||
false_origin: bpy.props.StringProperty(name="False Origin", default="0,0,0")
|
||||
use_cache: bpy.props.BoolProperty(name="Use Cache", default=True)
|
||||
|
||||
def draw(self, context):
|
||||
pprops = context.scene.BIMProjectProperties
|
||||
row = self.layout.row()
|
||||
row.prop(self, "use_relative_path")
|
||||
row = self.layout.row()
|
||||
row.prop(self, "use_cache")
|
||||
row = self.layout.row()
|
||||
row.prop(pprops, "false_origin_mode")
|
||||
if pprops.false_origin_mode == "MANUAL":
|
||||
row = self.layout.row()
|
||||
row.prop(pprops, "false_origin")
|
||||
|
||||
def execute(self, context):
|
||||
start = time.time()
|
||||
@@ -876,7 +926,7 @@ class LinkIfc(bpy.types.Operator):
|
||||
except:
|
||||
pass # Perhaps on another drive or something
|
||||
new.name = filepath
|
||||
status = bpy.ops.bim.load_link(filepath=filepath, false_origin=self.false_origin)
|
||||
status = bpy.ops.bim.load_link(filepath=filepath, use_cache=self.use_cache)
|
||||
if status == {"CANCELLED"}:
|
||||
error_msg = (
|
||||
f'Error processing IFC file "{self.filepath}" '
|
||||
@@ -954,7 +1004,7 @@ class LoadLink(bpy.types.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Load the selected file"
|
||||
filepath: bpy.props.StringProperty()
|
||||
false_origin: bpy.props.StringProperty(name="False Origin", default="0,0,0")
|
||||
use_cache: bpy.props.BoolProperty(name="Use Cache", default=True)
|
||||
|
||||
def execute(self, context):
|
||||
self.filepath = self.filepath.replace("\\", "/")
|
||||
@@ -986,12 +1036,34 @@ class LoadLink(bpy.types.Operator):
|
||||
blend_filepath = self.filepath + ".cache.blend"
|
||||
h5_filepath = self.filepath + ".cache.h5"
|
||||
|
||||
if not self.use_cache and os.path.exists(blend_filepath):
|
||||
os.remove(blend_filepath)
|
||||
|
||||
if not os.path.exists(blend_filepath):
|
||||
pprops = bpy.context.scene.BIMProjectProperties
|
||||
gprops = bpy.context.scene.BIMGeoreferenceProperties
|
||||
tool.Loader.calculate_model_origin(tool.Ifc.get())
|
||||
code = f"""
|
||||
import bpy
|
||||
|
||||
def run():
|
||||
bpy.ops.bim.load_linked_project(filepath="{self.filepath}", false_origin="{self.false_origin}")
|
||||
gprops = bpy.context.scene.BIMGeoreferenceProperties
|
||||
# Our model origin becomes their host model origin
|
||||
gprops.host_model_origin = "{gprops.model_origin}"
|
||||
gprops.has_blender_offset = {gprops.has_blender_offset}
|
||||
gprops.blender_eastings = "{gprops.blender_eastings}"
|
||||
gprops.blender_northings = "{gprops.blender_northings}"
|
||||
gprops.blender_orthogonal_height = "{gprops.blender_orthogonal_height}"
|
||||
gprops.blender_offset_x = "{gprops.blender_offset_x}"
|
||||
gprops.blender_offset_y = "{gprops.blender_offset_y}"
|
||||
gprops.blender_offset_z = "{gprops.blender_offset_z}"
|
||||
gprops.blender_x_axis_abscissa = "{gprops.blender_x_axis_abscissa}"
|
||||
gprops.blender_x_axis_ordinate = "{gprops.blender_x_axis_ordinate}"
|
||||
pprops = bpy.context.scene.BIMProjectProperties
|
||||
pprops.distance_limit = {pprops.distance_limit}
|
||||
pprops.false_origin_mode = "{pprops.false_origin_mode}"
|
||||
pprops.false_origin = "{pprops.false_origin}"
|
||||
bpy.ops.bim.load_linked_project(filepath="{self.filepath}")
|
||||
bpy.ops.wm.save_as_mainfile(filepath="{blend_filepath}")
|
||||
|
||||
try:
|
||||
@@ -1011,8 +1083,26 @@ except Exception as e:
|
||||
if not os.path.exists(blend_filepath) or os.stat(blend_filepath).st_mtime < t:
|
||||
return {"CANCELLED"}
|
||||
|
||||
self.set_model_origin_from_link()
|
||||
|
||||
self.link_blend(blend_filepath)
|
||||
|
||||
def set_model_origin_from_link(self):
|
||||
if tool.Ifc.get():
|
||||
return # The current model's coordinates always take priority.
|
||||
|
||||
json_filepath = self.filepath + ".cache.json"
|
||||
if not os.path.exists(json_filepath):
|
||||
return
|
||||
|
||||
with open(json_filepath, "r") as f:
|
||||
data = json.load(f)
|
||||
|
||||
gprops = bpy.context.scene.BIMGeoreferenceProperties
|
||||
for prop in ("model_origin", "blender_x_axis_abscissa", "blender_x_axis_ordinate"):
|
||||
if (value := data.get(prop, None)) is not None:
|
||||
setattr(gprops, prop, value)
|
||||
|
||||
|
||||
class ReloadLink(bpy.types.Operator):
|
||||
bl_idname = "bim.reload_link"
|
||||
@@ -1248,7 +1338,6 @@ class LoadLinkedProject(bpy.types.Operator):
|
||||
bl_label = "Load a project for viewing only."
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filepath: bpy.props.StringProperty()
|
||||
false_origin: bpy.props.StringProperty(name="False Origin", default="0,0,0")
|
||||
|
||||
def execute(self, context):
|
||||
import ifcpatch
|
||||
@@ -1257,6 +1346,9 @@ class LoadLinkedProject(bpy.types.Operator):
|
||||
|
||||
start = time.time()
|
||||
|
||||
pprops = bpy.context.scene.BIMProjectProperties
|
||||
gprops = bpy.context.scene.BIMGeoreferenceProperties
|
||||
|
||||
self.filepath = self.filepath.replace("\\", "/")
|
||||
print("Processing", self.filepath)
|
||||
|
||||
@@ -1272,32 +1364,56 @@ class LoadLinkedProject(bpy.types.Operator):
|
||||
print("Finished writing property database")
|
||||
|
||||
logger = logging.getLogger("ImportIFC")
|
||||
ifc_import_settings = import_ifc.IfcImportSettings.factory(context, IfcStore.path, logger)
|
||||
ifc_importer = import_ifc.IfcImporter(ifc_import_settings)
|
||||
ifc_importer.file = self.file
|
||||
ifc_importer.process_context_filter()
|
||||
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file)
|
||||
tool.Loader.set_unit_scale(self.unit_scale)
|
||||
tool.Loader.set_settings(import_ifc.IfcImportSettings.factory(context, IfcStore.path, logger))
|
||||
tool.Loader.settings.contexts = ifcopenshell.util.representation.get_prioritised_contexts(self.file)
|
||||
tool.Loader.settings.context_settings = tool.Loader.create_settings()
|
||||
|
||||
self.elements = set(self.file.by_type("IfcElement"))
|
||||
if self.file.schema in ("IFC2X3", "IFC4"):
|
||||
self.elements |= set(self.file.by_type("IfcProxy"))
|
||||
self.elements |= set(self.file.by_type("IfcSite"))
|
||||
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"))
|
||||
self.elements = list(self.elements)
|
||||
|
||||
model_origin = np.array(ifcopenshell.util.geolocation.auto_xyz2enh(self.file, 0, 0, 0))
|
||||
false_origin = np.array([float(o.strip()) for o in self.false_origin.split(",")])
|
||||
model_offset = model_origin - false_origin
|
||||
zero_origin = np.array((0, 0, 0))
|
||||
has_model_offset = not np.allclose(model_offset, zero_origin)
|
||||
if tool.Loader.settings.false_origin_mode == "MANUAL" and tool.Loader.settings.false_origin:
|
||||
tool.Loader.set_manual_blender_offset(self.file)
|
||||
elif tool.Loader.settings.false_origin_mode == "AUTOMATIC":
|
||||
if gprops.host_model_origin:
|
||||
tool.Loader.settings.false_origin = list(map(float, gprops.host_model_origin.split(",")))
|
||||
tool.Loader.set_manual_blender_offset(self.file)
|
||||
else:
|
||||
tool.Loader.guess_false_origin(self.file)
|
||||
|
||||
for settings in ifc_importer.context_settings:
|
||||
settings = ifcopenshell.geom.settings()
|
||||
tool.Loader.calculate_model_origin(self.file)
|
||||
self.json_filepath = self.filepath + ".cache.json"
|
||||
data = {
|
||||
"host_model_origin": gprops.host_model_origin,
|
||||
"model_origin": gprops.model_origin,
|
||||
"has_blender_offset": gprops.has_blender_offset,
|
||||
"blender_eastings": gprops.blender_eastings,
|
||||
"blender_northings": gprops.blender_northings,
|
||||
"blender_orthogonal_height": gprops.blender_orthogonal_height,
|
||||
"blender_offset_x": gprops.blender_offset_x,
|
||||
"blender_offset_y": gprops.blender_offset_y,
|
||||
"blender_offset_z": gprops.blender_offset_z,
|
||||
"blender_x_axis_abscissa": gprops.blender_x_axis_abscissa,
|
||||
"blender_x_axis_ordinate": gprops.blender_x_axis_ordinate,
|
||||
"distance_limit": pprops.distance_limit,
|
||||
"false_origin_mode": pprops.false_origin_mode,
|
||||
"false_origin": pprops.false_origin,
|
||||
}
|
||||
with open(self.json_filepath, "w") as f:
|
||||
json.dump(data, f)
|
||||
|
||||
if has_model_offset:
|
||||
offset = ifcopenshell.ifcopenshell_wrapper.float_array_3()
|
||||
offset[0], offset[1], offset[2] = model_offset
|
||||
settings.offset = offset
|
||||
for settings in tool.Loader.settings.context_settings:
|
||||
if not self.elements:
|
||||
break
|
||||
|
||||
results = set()
|
||||
iterator = ifcopenshell.geom.iterator(
|
||||
settings, self.file, multiprocessing.cpu_count(), include=self.elements
|
||||
)
|
||||
@@ -1313,16 +1429,17 @@ class LoadLinkedProject(bpy.types.Operator):
|
||||
chunked_materials = []
|
||||
chunked_material_ids = []
|
||||
material_offset = 0
|
||||
max_slot_index = 0
|
||||
chunk_size = 10000
|
||||
r4 = np.array([[0, 0, 0, 1]])
|
||||
offset = 0
|
||||
|
||||
ci = 0
|
||||
if iterator.initialize():
|
||||
while True:
|
||||
shape = iterator.get()
|
||||
if len(shape.geometry.faces) > 1000 and self.is_local(shape): # 333 tris
|
||||
results.add(self.file.by_id(shape.id))
|
||||
|
||||
# Elements with a lot of geometry benefit from instancing to save memory
|
||||
if len(shape.geometry.faces) > 1000: # 333 tris
|
||||
self.process_occurrence(shape)
|
||||
if not iterator.next():
|
||||
if not chunked_verts:
|
||||
@@ -1363,17 +1480,25 @@ class LoadLinkedProject(bpy.types.Operator):
|
||||
ms = np.vstack([default_mat, np.frombuffer(shape.geometry.colors_buffer).reshape((-1, 4))])
|
||||
mi = np.frombuffer(shape.geometry.material_ids_buffer, dtype=np.int32)
|
||||
for geom_material_idx, geom_material in enumerate(shape.geometry.materials):
|
||||
if not geom_material.has_diffuse:
|
||||
if not geom_material.instance_id():
|
||||
ms[geom_material_idx + 1] = (0.8, 0.8, 0.8, 1)
|
||||
chunked_materials.append(ms)
|
||||
chunked_material_ids.append(mi + material_offset + 1)
|
||||
material_offset += len(ms)
|
||||
|
||||
M4 = np.frombuffer(shape.transformation_buffer).reshape((4, 3))
|
||||
M4 = np.concatenate((M4.T, r4))
|
||||
matrix = np.frombuffer(shape.transformation_buffer).reshape((4, 4), order="F")
|
||||
if gprops.has_blender_offset:
|
||||
matrix = ifcopenshell.util.geolocation.global2local(
|
||||
matrix,
|
||||
float(gprops.blender_offset_x) * self.unit_scale,
|
||||
float(gprops.blender_offset_y) * self.unit_scale,
|
||||
float(gprops.blender_offset_z) * self.unit_scale,
|
||||
float(gprops.blender_x_axis_abscissa),
|
||||
float(gprops.blender_x_axis_ordinate),
|
||||
)
|
||||
vs = np.frombuffer(shape.geometry.verts_buffer).reshape((-1, 3))
|
||||
vs = np.hstack((vs, np.ones((len(vs), 1))))
|
||||
vs = (np.asmatrix(M4) * np.asmatrix(vs).T).T.A
|
||||
vs = (np.asmatrix(matrix) * np.asmatrix(vs).T).T.A
|
||||
vs = vs[:, :3].flatten()
|
||||
fs = np.frombuffer(shape.geometry.faces_buffer, dtype=np.int32)
|
||||
chunked_verts.append(vs)
|
||||
@@ -1419,7 +1544,6 @@ class LoadLinkedProject(bpy.types.Operator):
|
||||
chunked_materials = []
|
||||
chunked_material_ids = []
|
||||
material_offset = 0
|
||||
max_slot_index = 0
|
||||
offset = 0
|
||||
|
||||
if not iterator.next():
|
||||
@@ -1449,20 +1573,12 @@ class LoadLinkedProject(bpy.types.Operator):
|
||||
chunked_guid_ids,
|
||||
)
|
||||
break
|
||||
self.elements -= results
|
||||
|
||||
bpy.context.scene.collection.children.link(self.collection)
|
||||
print("Finished", time.time() - start)
|
||||
break
|
||||
bpy.context.scene.collection.children.link(self.collection)
|
||||
print("Finished", time.time() - start)
|
||||
return {"FINISHED"}
|
||||
|
||||
def is_local(self, shape: ShapeElementType) -> bool:
|
||||
m = shape.transformation.matrix
|
||||
if max([abs(co) for co in (m[9], m[10], m[11])]) > 1000:
|
||||
return False
|
||||
elif max([abs(co) for co in shape.geometry.verts[0:3]]) > 1000:
|
||||
return False
|
||||
return True
|
||||
|
||||
def process_occurrence(self, shape: ShapeElementType) -> None:
|
||||
element = self.file.by_id(shape.id)
|
||||
faces = shape.geometry.faces
|
||||
@@ -1476,6 +1592,27 @@ class LoadLinkedProject(bpy.types.Operator):
|
||||
if not mesh:
|
||||
mesh = bpy.data.meshes.new("Mesh")
|
||||
|
||||
geometry = shape.geometry
|
||||
gprops = bpy.context.scene.BIMGeoreferenceProperties
|
||||
if (
|
||||
gprops.has_blender_offset
|
||||
and geometry.verts
|
||||
and tool.Loader.is_point_far_away(
|
||||
(geometry.verts[0], geometry.verts[1], geometry.verts[2]), is_meters=True
|
||||
)
|
||||
):
|
||||
# Shift geometry close to the origin based off that first vert it found
|
||||
verts_array = np.array(geometry.verts)
|
||||
offset = np.array([-geometry.verts[0], -geometry.verts[1], -geometry.verts[2]])
|
||||
offset_verts = verts_array + np.tile(offset, len(verts_array) // 3)
|
||||
verts = offset_verts.tolist()
|
||||
|
||||
mesh["has_cartesian_point_offset"] = True
|
||||
mesh["cartesian_point_offset"] = f"{geometry.verts[0]},{geometry.verts[1]},{geometry.verts[2]}"
|
||||
else:
|
||||
verts = geometry.verts
|
||||
mesh["has_cartesian_point_offset"] = False
|
||||
|
||||
material_to_slot = {}
|
||||
max_slot_index = 0
|
||||
|
||||
@@ -1483,7 +1620,7 @@ class LoadLinkedProject(bpy.types.Operator):
|
||||
alpha = 1.0
|
||||
if material.has_transparency and material.transparency > 0:
|
||||
alpha = 1.0 - material.transparency
|
||||
if material.has_diffuse:
|
||||
if material.instance_id():
|
||||
diffuse = material.diffuse + (alpha,)
|
||||
else:
|
||||
diffuse = (0.8, 0.8, 0.8, 1) # Blender's default material
|
||||
@@ -1523,7 +1660,7 @@ class LoadLinkedProject(bpy.types.Operator):
|
||||
self.meshes[shape.geometry.id] = mesh
|
||||
|
||||
obj = bpy.data.objects.new(tool.Loader.get_name(element), mesh)
|
||||
obj.matrix_world = Matrix(mat.tolist())
|
||||
obj.matrix_world = tool.Loader.apply_blender_offset_to_matrix_world(obj, mat)
|
||||
|
||||
obj["guids"] = [shape.guid]
|
||||
obj["guid_ids"] = [len(mesh.polygons)]
|
||||
|
||||
@@ -143,34 +143,32 @@ class BIMProjectProperties(PropertyGroup):
|
||||
filter_query: StringProperty(name="Filter Query")
|
||||
should_filter_spatial_elements: BoolProperty(name="Filter Spatial Elements", default=False)
|
||||
should_use_cpu_multiprocessing: BoolProperty(name="CPU Multiprocessing", default=True)
|
||||
merge_mode: bpy.props.EnumProperty(
|
||||
items=[
|
||||
("NONE", "None", "No objects are merged"),
|
||||
("IFC_CLASS", "IFC Class", "One object per IFC class"),
|
||||
("IFC_TYPE", "IFC Type", "One object per IFC construction type"),
|
||||
("MATERIAL", "Material", "One object per material"),
|
||||
],
|
||||
name="Merge Mode",
|
||||
default="NONE",
|
||||
)
|
||||
should_merge_materials_by_colour: BoolProperty(name="Merge Materials by Colour", default=False)
|
||||
should_stream: BoolProperty(name="Stream Data From IFC-SPF (Only for advanced users)", default=False)
|
||||
should_load_geometry: BoolProperty(name="Load Geometry", default=True)
|
||||
should_use_native_meshes: BoolProperty(name="Native Meshes", default=False)
|
||||
should_clean_mesh: BoolProperty(name="Clean Meshes", default=False)
|
||||
should_cache: BoolProperty(name="Cache", default=False)
|
||||
is_coordinating: BoolProperty(name="For Coordination Only", default=False)
|
||||
deflection_tolerance: FloatProperty(name="Deflection Tolerance", default=0.001)
|
||||
angular_tolerance: FloatProperty(name="Angular Tolerance", default=0.5)
|
||||
void_limit: IntProperty(name="Void Limit", default=30)
|
||||
distance_limit: FloatProperty(name="Distance Limit", default=1000, subtype="DISTANCE")
|
||||
false_origin_mode: bpy.props.EnumProperty(
|
||||
items=[
|
||||
(
|
||||
"AUTOMATIC",
|
||||
"Automatic",
|
||||
"An automatic false origin will be detected from geometry with large coordinates",
|
||||
),
|
||||
("MANUAL", "Manual", "You can specify the false origin coordinates"),
|
||||
("DISABLED", "Disabled", "The model in original local coordinates will be shown as is"),
|
||||
],
|
||||
name="False Origin Mode",
|
||||
default="AUTOMATIC",
|
||||
)
|
||||
false_origin: StringProperty(
|
||||
name="False Origin",
|
||||
description=(
|
||||
"False origin that will be used to offset the entire model.\n"
|
||||
"(0,0,0) value is interpreted as an unset false origin - false origin will be guessed based on Distance Limit.\n"
|
||||
"False origin is defined in project units"
|
||||
),
|
||||
description="False origin in project units that the Blender origin will correlate to",
|
||||
default="0,0,0",
|
||||
)
|
||||
element_offset: IntProperty(name="Element Offset", default=0)
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
import os
|
||||
import blenderbim.bim
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim.helper import prop_with_search
|
||||
from bpy.types import Panel, Menu, UIList
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
@@ -36,6 +37,28 @@ class BIM_MT_project(Menu):
|
||||
self.layout.operator("bim.new_project", text="New Project Wizard").preset = "wizard"
|
||||
|
||||
|
||||
class BIM_MT_recent_projects(Menu):
|
||||
bl_idname = "BIM_MT_recent_projects"
|
||||
bl_label = "Open Recent IFC Project"
|
||||
|
||||
def draw(self, context):
|
||||
paths = tool.Blender.get_recent_ifc_projects()
|
||||
if not paths:
|
||||
self.layout.label(text="No Recent IFC Projects")
|
||||
return
|
||||
|
||||
for path in paths:
|
||||
op = self.layout.operator(
|
||||
"bim.load_project", text=path.name, icon_value=blenderbim.bim.icons["IFC"].icon_id
|
||||
)
|
||||
op.filepath = str(path)
|
||||
op.should_start_fresh_session = True
|
||||
op.use_detailed_tooltip = True
|
||||
|
||||
self.layout.separator()
|
||||
self.layout.operator("bim.clear_recent_ifc_projects", icon="TRASH")
|
||||
|
||||
|
||||
class BIM_MT_new_project(Menu):
|
||||
bl_idname = "BIM_MT_new_project"
|
||||
bl_label = "New Project"
|
||||
@@ -44,6 +67,7 @@ class BIM_MT_new_project(Menu):
|
||||
self.layout.operator_context = "INVOKE_DEFAULT"
|
||||
op = self.layout.operator("bim.load_project", text="Open IFC Project", icon="FILEBROWSER")
|
||||
op.should_start_fresh_session = True
|
||||
self.layout.menu("BIM_MT_recent_projects", icon="NONE")
|
||||
# Do we need to set it back to exec default?
|
||||
# self.layout.operator_context = "EXEC_DEFAULT"
|
||||
self.layout.separator()
|
||||
@@ -67,6 +91,7 @@ def file_menu(self, context):
|
||||
self.layout.menu("BIM_MT_project", icon="COLLECTION_NEW")
|
||||
op = self.layout.operator("bim.load_project", text="Open IFC Project", icon="FILEBROWSER")
|
||||
op.should_start_fresh_session = True
|
||||
self.layout.menu("BIM_MT_recent_projects", icon="NONE")
|
||||
self.layout.separator()
|
||||
op = self.layout.operator("bim.save_project", icon="FILE_TICK", text="Save IFC Project")
|
||||
op.should_save_as = False
|
||||
@@ -96,13 +121,14 @@ class BIM_PT_project(Panel):
|
||||
pprops = context.scene.BIMProjectProperties
|
||||
self.file = IfcStore.get_file()
|
||||
if pprops.is_loading:
|
||||
self.draw_load_ui(context)
|
||||
self.draw_advanced_loading_ui(context)
|
||||
elif self.file or props.ifc_file:
|
||||
self.draw_project_ui(context)
|
||||
else:
|
||||
self.draw_create_project_ui(context)
|
||||
if props.ifc_file:
|
||||
self.draw_loaded_project_ui(context)
|
||||
else:
|
||||
self.draw_unsaved_project_ui(context)
|
||||
|
||||
def draw_load_ui(self, context):
|
||||
def draw_advanced_loading_ui(self, context):
|
||||
pprops = context.scene.BIMProjectProperties
|
||||
prop_with_search(self.layout, pprops, "filter_mode")
|
||||
if pprops.filter_mode in ["DECOMPOSITION", "IFC_CLASS", "IFC_TYPE"]:
|
||||
@@ -137,10 +163,6 @@ class BIM_PT_project(Panel):
|
||||
row = self.layout.row()
|
||||
row.prop(pprops, "should_merge_materials_by_colour")
|
||||
row = self.layout.row()
|
||||
row.prop(pprops, "is_coordinating")
|
||||
if pprops.is_coordinating:
|
||||
prop_with_search(self.layout, pprops, "merge_mode")
|
||||
row = self.layout.row()
|
||||
row.prop(pprops, "deflection_tolerance")
|
||||
row = self.layout.row()
|
||||
row.prop(pprops, "angular_tolerance")
|
||||
@@ -149,7 +171,10 @@ class BIM_PT_project(Panel):
|
||||
row = self.layout.row()
|
||||
row.prop(pprops, "distance_limit")
|
||||
row = self.layout.row()
|
||||
row.prop(pprops, "false_origin")
|
||||
row.prop(pprops, "false_origin_mode")
|
||||
if pprops.false_origin_mode == "MANUAL":
|
||||
row = self.layout.row()
|
||||
row.prop(pprops, "false_origin")
|
||||
|
||||
row = self.layout.row()
|
||||
row.label(text="Element Range")
|
||||
@@ -159,14 +184,9 @@ class BIM_PT_project(Panel):
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.load_project_elements")
|
||||
row.operator("bim.unload_project", text="", icon="CANCEL")
|
||||
|
||||
def draw_project_ui(self, context):
|
||||
props = context.scene.BIMProperties
|
||||
def draw_editing_buttons(self, context, row):
|
||||
pprops = context.scene.BIMProjectProperties
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=os.path.basename(props.ifc_file) or "No File Found", icon="FILE")
|
||||
|
||||
if IfcStore.get_file():
|
||||
if pprops.is_editing:
|
||||
row.operator("bim.edit_header", icon="CHECKMARK", text="")
|
||||
@@ -174,6 +194,10 @@ class BIM_PT_project(Panel):
|
||||
else:
|
||||
row.operator("bim.enable_editing_header", icon="GREASEPENCIL", text="")
|
||||
|
||||
def draw_editable_file_info(self, context):
|
||||
pprops = context.scene.BIMProjectProperties
|
||||
|
||||
if IfcStore.get_file():
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="IFC Schema", icon="FILE_CACHE")
|
||||
row.label(text=IfcStore.get_file().schema)
|
||||
@@ -201,27 +225,61 @@ class BIM_PT_project(Panel):
|
||||
if "[" in mvd:
|
||||
mvd = mvd.split("[")[1][0:-1]
|
||||
row.label(text=mvd)
|
||||
|
||||
else:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="File Not Loaded", icon="ERROR")
|
||||
|
||||
if props.ifc_file:
|
||||
row = self.layout.row(align=True)
|
||||
if context.scene.BIMProperties.is_dirty:
|
||||
row.label(text="Saved*", icon="EXPORT")
|
||||
else:
|
||||
row.label(text="Saved", icon="EXPORT")
|
||||
row.label(text=ProjectData.data["last_saved"])
|
||||
def draw_unsaved_project_ui(self, context):
|
||||
# file name row
|
||||
file_name_row = self.layout.row(align=True)
|
||||
file_name_row.label(text="No File Found", icon="FILE")
|
||||
self.draw_editing_buttons(context, file_name_row)
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "ifc_file", text="")
|
||||
row.operator("bim.select_ifc_file", icon="FILE_FOLDER", text="")
|
||||
row.operator("bim.unload_project", text="", icon="CANCEL")
|
||||
# main section
|
||||
self.draw_editable_file_info(context)
|
||||
|
||||
# file path row and actions section
|
||||
row = self.layout.row()
|
||||
row.label(text="File Not Saved", icon="ERROR")
|
||||
|
||||
def draw_loaded_project_ui(self, context):
|
||||
# file name row
|
||||
props = context.scene.BIMProperties
|
||||
file_name_row = self.layout.row(align=True)
|
||||
file_name_row.label(text=os.path.basename(props.ifc_file), icon="FILE")
|
||||
self.draw_editing_buttons(context, file_name_row)
|
||||
|
||||
# main section
|
||||
self.draw_editable_file_info(context)
|
||||
|
||||
# file path row and actions section
|
||||
row = self.layout.row(align=True)
|
||||
if context.scene.BIMProperties.is_dirty:
|
||||
row.label(text="Saved*", icon="EXPORT")
|
||||
else:
|
||||
row = self.layout.row()
|
||||
row.label(text="File Not Saved", icon="ERROR")
|
||||
row.label(text="Saved", icon="EXPORT")
|
||||
row.label(text=ProjectData.data["last_saved"])
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "ifc_file", text="")
|
||||
row.operator("bim.select_ifc_file", icon="FILE_FOLDER", text="")
|
||||
row.operator("bim.unload_project", text="", icon="CANCEL")
|
||||
|
||||
|
||||
class BIM_PT_new_project_wizard(Panel):
|
||||
bl_label = "New Project Wizard"
|
||||
bl_idname = "BIM_PT_new_project_wizard"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_options = {"HIDE_HEADER"}
|
||||
bl_parent_id = "BIM_PT_tab_new_project_wizard"
|
||||
|
||||
def draw(self, context):
|
||||
self.layout.use_property_decorate = False
|
||||
self.layout.use_property_split = True
|
||||
|
||||
def draw_create_project_ui(self, context):
|
||||
props = context.scene.BIMProperties
|
||||
pprops = context.scene.BIMProjectProperties
|
||||
prop_with_search(self.layout, pprops, "export_schema")
|
||||
|
||||
@@ -79,7 +79,7 @@ def draw_enumerated_property(prop, layout, copy_operator=None):
|
||||
|
||||
|
||||
def get_active_pset_obj_name(context, obj_type):
|
||||
if obj_type in ("Object", "Material", "MaterialSet", "MaterialSetItem"):
|
||||
if obj_type in ("Object", "MaterialSet", "MaterialSetItem"):
|
||||
return context.active_object.name
|
||||
return ""
|
||||
|
||||
@@ -138,7 +138,7 @@ def draw_psetqto_ui(context, pset_id, pset, props, layout, obj_type, allow_remov
|
||||
else:
|
||||
has_props_displayed = False
|
||||
for prop in pset["Properties"]:
|
||||
if context.preferences.addons["blenderbim"].preferences.should_hide_empty_props and (
|
||||
if tool.Blender.get_addon_preferences().should_hide_empty_props and (
|
||||
prop["NominalValue"] is None or prop["NominalValue"] == ""
|
||||
):
|
||||
continue
|
||||
|
||||
@@ -644,11 +644,8 @@ def get_opening_area(
|
||||
|
||||
bl_opening_obj = bpy.data.objects.new("MyObject", mesh)
|
||||
|
||||
opening_type = (
|
||||
ifc_opening_element.PredefinedType
|
||||
if ifc_opening_element.PredefinedType is not None
|
||||
else get_opening_type(bl_opening_obj, obj)
|
||||
)
|
||||
if not (opening_type := ifcopenshell.util.element.get_predefined_type(ifc_opening_element)):
|
||||
opening_type = get_opening_type(bl_opening_obj, obj)
|
||||
|
||||
if ignore_recesses and opening_type == "RECESS":
|
||||
continue
|
||||
|
||||
@@ -19,9 +19,11 @@
|
||||
from collections import defaultdict
|
||||
import bpy
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.schema
|
||||
from ifcopenshell.util.doc import get_entity_doc, get_predefined_type_doc, get_class_suggestions
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from typing import Union
|
||||
|
||||
|
||||
def refresh():
|
||||
@@ -29,6 +31,7 @@ def refresh():
|
||||
|
||||
|
||||
class IfcClassData:
|
||||
element: Union[ifcopenshell.entity_instance, None] = None
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@@ -40,6 +43,7 @@ class IfcClassData:
|
||||
cls.data["ifc_classes"] = cls.ifc_classes()
|
||||
cls.data["ifc_classes_suggestions"] = cls.ifc_classes_suggestions() # Call AFTER cls.ifc_classes()
|
||||
cls.data["contexts"] = cls.contexts()
|
||||
|
||||
cls.data["has_entity"] = cls.has_entity()
|
||||
cls.data["name"] = cls.name()
|
||||
cls.data["has_inherited_predefined_type"] = cls.has_inherited_predefined_type()
|
||||
@@ -146,15 +150,27 @@ class IfcClassData:
|
||||
return results
|
||||
|
||||
@classmethod
|
||||
def has_entity(cls):
|
||||
try:
|
||||
return tool.Ifc.get_entity(bpy.context.active_object)
|
||||
except:
|
||||
pass
|
||||
def has_entity(cls) -> Union[ifcopenshell.entity_instance, None]:
|
||||
obj = bpy.context.active_object
|
||||
cls.element = None
|
||||
if not obj:
|
||||
return
|
||||
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
return
|
||||
|
||||
# Double check
|
||||
linked_obj = tool.Ifc.get_object(element)
|
||||
if linked_obj != obj:
|
||||
return
|
||||
|
||||
cls.element = element
|
||||
return cls.element
|
||||
|
||||
@classmethod
|
||||
def name(cls):
|
||||
element = tool.Ifc.get_entity(bpy.context.view_layer.objects.active)
|
||||
def name(cls) -> Union[str, None]:
|
||||
element = cls.element
|
||||
if not element:
|
||||
return
|
||||
name = element.is_a()
|
||||
@@ -164,24 +180,24 @@ class IfcClassData:
|
||||
return name
|
||||
|
||||
@classmethod
|
||||
def has_inherited_predefined_type(cls):
|
||||
element = tool.Ifc.get_entity(bpy.context.view_layer.objects.active)
|
||||
def has_inherited_predefined_type(cls) -> bool:
|
||||
element = cls.element
|
||||
if not element:
|
||||
return
|
||||
return False
|
||||
if element_type := ifcopenshell.util.element.get_type(element):
|
||||
# Allow for None due to https://github.com/buildingSMART/IFC4.3.x-development/issues/818
|
||||
return ifcopenshell.util.element.get_predefined_type(element_type) not in ("NOTDEFINED", None)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def ifc_class(cls):
|
||||
element = tool.Ifc.get_entity(bpy.context.view_layer.objects.active)
|
||||
def ifc_class(cls) -> Union[str, None]:
|
||||
element = cls.element
|
||||
if element:
|
||||
return element.is_a()
|
||||
|
||||
@classmethod
|
||||
def can_reassign_class(cls):
|
||||
element = tool.Ifc.get_entity(bpy.context.view_layer.objects.active)
|
||||
def can_reassign_class(cls) -> bool:
|
||||
element = cls.element
|
||||
if element:
|
||||
if element.is_a("IfcOpeningElement") or element.is_a("IfcOpeningStandardCase"):
|
||||
return False
|
||||
@@ -190,3 +206,4 @@ class IfcClassData:
|
||||
for product in cls.ifc_products():
|
||||
if element.is_a(product[0]):
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -214,9 +214,16 @@ class AssignClass(bpy.types.Operator, Operator):
|
||||
class UnlinkObject(bpy.types.Operator):
|
||||
bl_idname = "bim.unlink_object"
|
||||
bl_label = "Unlink Object"
|
||||
bl_description = (
|
||||
"Unlink Blender object from it's linked IFC element.\n\n"
|
||||
"You can either remove element the blender object is linked to from IFC or keep it. "
|
||||
"Note that keeping the unlinked element in IFC might lead to unpredictable issues "
|
||||
"and should be used only by advanced users"
|
||||
)
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty(name="Object Name")
|
||||
should_delete: bpy.props.BoolProperty(name="Delete IFC Element", default=True)
|
||||
skip_invoke: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
@@ -231,8 +238,7 @@ class UnlinkObject(bpy.types.Operator):
|
||||
for obj in objects:
|
||||
was_active_object = obj == context.active_object
|
||||
|
||||
if obj in IfcStore.edited_objs:
|
||||
IfcStore.edited_objs.remove(obj)
|
||||
tool.Ifc.finish_edit(obj)
|
||||
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element and self.should_delete:
|
||||
@@ -268,6 +274,8 @@ class UnlinkObject(bpy.types.Operator):
|
||||
|
||||
obj = obj_copy
|
||||
obj.name = object_name
|
||||
elif element:
|
||||
tool.Ifc.unlink(element)
|
||||
|
||||
tool.Root.unlink_object(obj)
|
||||
for collection in obj.users_collection:
|
||||
@@ -284,6 +292,8 @@ class UnlinkObject(bpy.types.Operator):
|
||||
row.prop(self, "should_delete")
|
||||
|
||||
def invoke(self, context, event):
|
||||
if self.skip_invoke:
|
||||
return self.execute(context)
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
|
||||
|
||||
@@ -47,7 +47,9 @@ class BIM_PT_class(Panel):
|
||||
if not IfcClassData.data["has_entity"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="IFC Element Not Found")
|
||||
row.operator("bim.unlink_object", icon="UNLINKED", text="")
|
||||
op = row.operator("bim.unlink_object", icon="UNLINKED", text="")
|
||||
op.should_delete = False
|
||||
op.skip_invoke = True
|
||||
return
|
||||
if props.is_reassigning_class:
|
||||
row = self.layout.row(align=True)
|
||||
|
||||
@@ -52,6 +52,7 @@ classes = (
|
||||
prop.BIMStyleProperties,
|
||||
ui.BIM_PT_styles,
|
||||
ui.BIM_UL_styles,
|
||||
ui.BIM_PT_style,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -20,10 +20,12 @@ import bpy
|
||||
import ifcopenshell
|
||||
import blenderbim.tool as tool
|
||||
from ifcopenshell.util.doc import get_entity_doc
|
||||
from typing import Union
|
||||
|
||||
|
||||
def refresh():
|
||||
StylesData.is_loaded = False
|
||||
BlenderMaterialStyleData.is_loaded = False
|
||||
|
||||
|
||||
class StylesData:
|
||||
@@ -67,3 +69,55 @@ class StylesData:
|
||||
@classmethod
|
||||
def total_styles(cls):
|
||||
return len(tool.Ifc.get().by_type("IfcPresentationStyle"))
|
||||
|
||||
|
||||
class BlenderMaterialStyleData:
|
||||
style: Union[ifcopenshell.entity_instance, None] = None
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {
|
||||
"is_linked_to_style": cls.is_linked_to_style(),
|
||||
# After is_linked_to_style.
|
||||
"linked_style_name": cls.linked_style_name(),
|
||||
}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def is_linked_to_style(cls) -> bool:
|
||||
ifc_file = tool.Ifc.get()
|
||||
if not ifc_file:
|
||||
return False
|
||||
context = bpy.context
|
||||
cls.linked_style = None
|
||||
obj = context.active_object
|
||||
if not obj:
|
||||
return False
|
||||
material = obj.active_material
|
||||
if not material:
|
||||
return False
|
||||
props = material.BIMStyleProperties
|
||||
style_id = props.ifc_definition_id
|
||||
style = tool.Ifc.get_entity_by_id(style_id)
|
||||
if not style:
|
||||
return False
|
||||
|
||||
# Double check that it's the right material
|
||||
# and not material from other Blender session.
|
||||
linked_material = tool.Ifc.get_object(style)
|
||||
if linked_material != material:
|
||||
return False
|
||||
cls.style = style
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def linked_style_name(cls) -> Union[str, None]:
|
||||
"""Return str if Blender Material is linked to IFC style or None if it's not.
|
||||
|
||||
Method will return "Unnamed" if style name is None.
|
||||
"""
|
||||
if cls.style:
|
||||
return cls.style.Name or "Unnamed"
|
||||
return None
|
||||
|
||||
@@ -25,7 +25,6 @@ import blenderbim.core.style as core
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.api.style
|
||||
import ifcopenshell.util.representation
|
||||
from blenderbim.bim.module.style.prop import switch_shading
|
||||
from pathlib import Path
|
||||
from mathutils import Vector
|
||||
|
||||
@@ -80,7 +79,7 @@ class RemoveStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
style: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.remove_style(tool.Ifc, tool.Material, tool.Style, style=tool.Ifc.get().by_id(self.style))
|
||||
core.remove_style(tool.Ifc, tool.Style, style=tool.Ifc.get().by_id(self.style))
|
||||
|
||||
|
||||
class AddStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -96,45 +95,87 @@ class AddStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
class UnlinkStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.unlink_style"
|
||||
bl_label = "Unlink Style"
|
||||
bl_description = (
|
||||
"Unlink Blender material from it's linked IFC style.\n\n"
|
||||
"You can either remove style the material is linked to from IFC or keep it. "
|
||||
"Note that keeping the unlinked style in IFC might lead to unpredictable issues "
|
||||
"and should be used only by advanced users"
|
||||
)
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
style: bpy.props.IntProperty(default=0)
|
||||
blender_material: bpy.props.StringProperty(name="Blender Material")
|
||||
should_delete: bpy.props.BoolProperty(name="Delete IFC Style", default=True)
|
||||
|
||||
def draw(self, context):
|
||||
row = self.layout.row()
|
||||
row.prop(self, "should_delete")
|
||||
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
def _execute(self, context):
|
||||
core.unlink_style(tool.Ifc, style=tool.Ifc.get().by_id(self.style))
|
||||
# TODO: move to core.style
|
||||
# Don't check blender_material and style_id as this operator is only called from UI.
|
||||
assert isinstance(self.blender_material, str) # Type checker.
|
||||
material = bpy.data.materials[self.blender_material]
|
||||
style_id = material.BIMStyleProperties.ifc_definition_id
|
||||
style = tool.Ifc.get_entity_by_id(style_id)
|
||||
|
||||
# Material is linked to a style from a different project.
|
||||
if not style or tool.Ifc.get_object(style) != material:
|
||||
tool.Ifc.unlink(obj=material)
|
||||
return {"FINISHED"}
|
||||
|
||||
if self.should_delete:
|
||||
# Create a copy that will be removed
|
||||
# and leave user with unlinked original material.
|
||||
# It's needed so we don't need to search everywhere original
|
||||
# material was used and replace it with the unlinked version.
|
||||
material_copy = material.copy()
|
||||
tool.Ifc.unlink(element=style)
|
||||
tool.Ifc.link(style, material_copy)
|
||||
core.remove_style(tool.Ifc, tool.Style, style)
|
||||
else:
|
||||
tool.Ifc.unlink(element=style)
|
||||
|
||||
# Ensure there won't be any style sync on project save:
|
||||
# bim.update_representation would create new IfcSurfaceStyle
|
||||
# for unlinked blender material.
|
||||
updated_meshes = set()
|
||||
for obj in bpy.data.objects:
|
||||
mesh = obj.data
|
||||
if not isinstance(mesh, bpy.types.Mesh):
|
||||
continue
|
||||
if not mesh.BIMMeshProperties.ifc_definition_id:
|
||||
continue
|
||||
if mesh in updated_meshes:
|
||||
continue
|
||||
if not mesh.materials.get(material.name):
|
||||
continue
|
||||
tool.Geometry.record_object_materials(obj)
|
||||
updated_meshes.add(mesh)
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EnableEditingStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
class EnableEditingStyle(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_style"
|
||||
bl_label = "Enable Editing Style"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
style: bpy.props.IntProperty(default=0)
|
||||
|
||||
def _execute(self, context):
|
||||
props = bpy.context.scene.BIMStylesProperties
|
||||
style = tool.Ifc.get().by_id(self.style)
|
||||
props.is_editing_style = style.id()
|
||||
props.is_editing_class = "IfcSurfaceStyle"
|
||||
attributes = props.attributes
|
||||
attributes.clear()
|
||||
blenderbim.bim.helper.import_attributes2(style, attributes)
|
||||
def execute(self, context):
|
||||
core.enable_editing_style(tool.Style, tool.Ifc.get().by_id(self.style))
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DisableEditingStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
class DisableEditingStyle(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_editing_style"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_label = "Disable Editing Style"
|
||||
|
||||
def _execute(self, context):
|
||||
props = bpy.context.scene.BIMStylesProperties
|
||||
|
||||
style = tool.Ifc.get().by_id(props.is_editing_style)
|
||||
material = tool.Ifc.get_object(style)
|
||||
tool.Style.reload_material_from_ifc(material)
|
||||
props.is_editing_style = 0
|
||||
|
||||
# restore selected style type
|
||||
material = tool.Ifc.get_object(style)
|
||||
material.BIMStyleProperties.active_style_type = material.BIMStyleProperties.active_style_type
|
||||
def execute(self, context):
|
||||
core.disable_editing_style(tool.Style)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EditStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -143,12 +184,7 @@ class EditStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
props = bpy.context.scene.BIMStylesProperties
|
||||
style = tool.Ifc.get().by_id(props.is_editing_style)
|
||||
attributes = blenderbim.bim.helper.export_attributes(props.attributes)
|
||||
ifcopenshell.api.run("style.edit_presentation_style", tool.Ifc.get(), style=style, attributes=attributes)
|
||||
props.is_editing_style = 0
|
||||
core.load_styles(tool.Style, style_type=props.style_type)
|
||||
core.edit_style(tool.Ifc, tool.Style)
|
||||
|
||||
|
||||
class UpdateCurrentStyle(bpy.types.Operator):
|
||||
@@ -180,7 +216,7 @@ class UpdateCurrentStyle(bpy.types.Operator):
|
||||
|
||||
for obj in context.selected_objects:
|
||||
for mat in obj.data.materials:
|
||||
if mat and mat.BIMMaterialProperties.ifc_style_id != 0:
|
||||
if mat and mat.BIMStyleProperties.ifc_definition_id != 0:
|
||||
mat.BIMStyleProperties.active_style_type = current_style_type
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -628,12 +664,12 @@ class EnableEditingSurfaceStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
active_style_type = material.BIMStyleProperties.active_style_type
|
||||
if self.ifc_class == "IfcExternallyDefinedSurfaceStyle" and active_style_type != "External":
|
||||
if tool.Style.has_blender_external_style(style_elements):
|
||||
switch_shading(material, "External")
|
||||
tool.Style.switch_shading(material, "External")
|
||||
elif (
|
||||
self.ifc_class in ("IfcSurfaceStyleShading", "IfcSurfaceStyleRendering", "IfcSurfaceStyleWithTextures")
|
||||
and active_style_type != "Shading"
|
||||
):
|
||||
switch_shading(material, "Shading")
|
||||
tool.Style.switch_shading(material, "Shading")
|
||||
|
||||
|
||||
class EditSurfaceStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -847,7 +883,7 @@ class SaveUVToStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
obj = context.active_object
|
||||
|
||||
# find active style item
|
||||
style_id = material.BIMMaterialProperties.ifc_style_id
|
||||
style_id = material.BIMStyleProperties.ifc_definition_id
|
||||
style = ifc_file.by_id(style_id)
|
||||
|
||||
def get_active_representation_items():
|
||||
|
||||
@@ -74,7 +74,7 @@ STYLE_TYPES = [
|
||||
|
||||
def update_shading_styles(self, context):
|
||||
for mat in bpy.data.materials:
|
||||
if mat.BIMMaterialProperties.ifc_style_id == 0:
|
||||
if mat.BIMStyleProperties.ifc_definition_id == 0:
|
||||
continue
|
||||
tool.Style.change_current_style_type(mat, self.active_style_type)
|
||||
|
||||
@@ -253,50 +253,18 @@ class BIMStylesProperties(PropertyGroup):
|
||||
)
|
||||
|
||||
|
||||
def switch_shading(blender_material: bpy.types.Material, style_type: Literal["External", "Shading"]) -> None:
|
||||
if style_type == "External":
|
||||
try:
|
||||
bpy.ops.bim.activate_external_style(material_name=blender_material.name)
|
||||
except RuntimeError as error:
|
||||
if str(error).startswith("Error: Error loading external style for "):
|
||||
return
|
||||
raise error
|
||||
elif style_type == "Shading":
|
||||
style_elements = tool.Style.get_style_elements(blender_material)
|
||||
rendering_style = None
|
||||
texture_style = None
|
||||
|
||||
for surface_style in style_elements.values():
|
||||
if surface_style.is_a() == "IfcSurfaceStyleShading":
|
||||
tool.Loader.create_surface_style_shading(blender_material, surface_style)
|
||||
elif surface_style.is_a("IfcSurfaceStyleRendering"):
|
||||
rendering_style = surface_style
|
||||
tool.Loader.create_surface_style_rendering(blender_material, surface_style)
|
||||
elif surface_style.is_a("IfcSurfaceStyleWithTextures"):
|
||||
texture_style = surface_style
|
||||
|
||||
if rendering_style and texture_style:
|
||||
tool.Loader.create_surface_style_with_textures(blender_material, rendering_style, texture_style)
|
||||
|
||||
|
||||
def update_shading_style(self, context):
|
||||
blender_material = self.id_data
|
||||
style_elements = tool.Style.get_style_elements(blender_material)
|
||||
if self.active_style_type == "External":
|
||||
if tool.Style.has_blender_external_style(style_elements):
|
||||
switch_shading(blender_material, self.active_style_type)
|
||||
tool.Style.switch_shading(blender_material, self.active_style_type)
|
||||
elif self.active_style_type == "Shading":
|
||||
switch_shading(blender_material, self.active_style_type)
|
||||
tool.Style.record_shading(blender_material)
|
||||
tool.Style.switch_shading(blender_material, self.active_style_type)
|
||||
|
||||
|
||||
class BIMStyleProperties(PropertyGroup):
|
||||
# TODO: remove, as attributes already moved to styles ui
|
||||
attributes: CollectionProperty(name="Attributes", type=Attribute)
|
||||
is_editing: BoolProperty(name="Is Editing")
|
||||
external_style_attributes: CollectionProperty(name="External Style Attributes", type=Attribute)
|
||||
is_editing_external_style: BoolProperty(name="Is Editing External Style")
|
||||
|
||||
ifc_definition_id: IntProperty(name="IFC Definition ID")
|
||||
active_style_type: EnumProperty(
|
||||
name="Active Style Type",
|
||||
description="Update current blender material to match style type",
|
||||
|
||||
@@ -21,8 +21,7 @@ import blenderbim.bim.helper
|
||||
import blenderbim.tool as tool
|
||||
from bpy.types import Panel, UIList
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.style.data import StylesData
|
||||
from bl_ui.properties_material import MaterialButtonsPanel
|
||||
from blenderbim.bim.module.style.data import StylesData, BlenderMaterialStyleData
|
||||
|
||||
|
||||
class BIM_PT_styles(Panel):
|
||||
@@ -67,8 +66,6 @@ class BIM_PT_styles(Panel):
|
||||
row.operator("bim.select_by_style", text="", icon="RESTRICT_SELECT_OFF").style = style.ifc_definition_id
|
||||
op = row.operator("bim.assign_style_to_selected", text="", icon="BRUSH_DATA")
|
||||
op.style_id = style.ifc_definition_id
|
||||
op = row.operator("bim.unlink_style", text="", icon="UNLINKED")
|
||||
op.style = style.ifc_definition_id
|
||||
op = row.operator("bim.enable_editing_style", text="", icon="GREASEPENCIL")
|
||||
op.style = style.ifc_definition_id
|
||||
row.operator("bim.remove_style", text="", icon="X").style = style.ifc_definition_id
|
||||
@@ -291,3 +288,43 @@ class BIM_UL_styles(UIList):
|
||||
row2.label(text="", icon="LIGHT_POINT")
|
||||
elif style.name == "IfcExternallyDefinedSurfaceStyle":
|
||||
row2.label(text="", icon="APPEND_BLEND")
|
||||
|
||||
|
||||
class BIM_PT_style(Panel):
|
||||
bl_label = "Style"
|
||||
bl_idname = "BIM_PT_style"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "material"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return bool(tool.Ifc.get() and (material := context.material) and material.BIMStyleProperties.ifc_definition_id)
|
||||
|
||||
def draw(self, context):
|
||||
# NOTE: this UI is needed only to indicate whether blender material is linked to IFC
|
||||
# and shouldn't be overloaded with any other features (they should be added to the general Styles UI).
|
||||
|
||||
if not BlenderMaterialStyleData.is_loaded:
|
||||
BlenderMaterialStyleData.load()
|
||||
|
||||
material = context.material
|
||||
style_id = material.BIMStyleProperties.ifc_definition_id
|
||||
row = self.layout.row(align=True)
|
||||
|
||||
if style_id and not BlenderMaterialStyleData.data["is_linked_to_style"]:
|
||||
row.label(text="Material has linked IFC from a different project.")
|
||||
op = row.operator("bim.unlink_style", icon="UNLINKED", text="")
|
||||
op.blender_material = material.name
|
||||
return
|
||||
else:
|
||||
row.label(text="Material is linked to an IFC style.")
|
||||
op = row.operator("bim.unlink_style", icon="UNLINKED", text="")
|
||||
op.blender_material = material.name
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="IFC Style ID:")
|
||||
row.label(text=str(style_id))
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="IFC Style Name:")
|
||||
row.label(text=BlenderMaterialStyleData.data["linked_style_name"])
|
||||
|
||||
@@ -89,7 +89,7 @@ class SystemDecorator:
|
||||
self.draw_batch("TRIS", vertices_coords, faces_color, face_indices)
|
||||
|
||||
def __call__(self, context, get_custom_bmesh=None, draw_faces=False, exit_edit_mode_callback=None):
|
||||
self.addon_prefs = context.preferences.addons["blenderbim"].preferences
|
||||
self.addon_prefs = tool.Blender.get_addon_preferences()
|
||||
selected_elements_color = self.addon_prefs.decorator_color_selected
|
||||
unselected_elements_color = self.addon_prefs.decorator_color_unselected
|
||||
special_elements_color = self.addon_prefs.decorator_color_special
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
import bpy
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.representation
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.geometry
|
||||
@@ -74,6 +75,7 @@ class AddOpening(bpy.types.Operator, tool.Ifc.Operator):
|
||||
obj1, obj2 = obj2, obj1
|
||||
element1, element2 = element2, element1
|
||||
|
||||
# element1 - voided element, element2 - opening.
|
||||
if element1.is_a("IfcOpeningElement"):
|
||||
self.report({"INFO"}, "You can't add an opening to another opening.")
|
||||
continue
|
||||
@@ -127,7 +129,7 @@ class AddOpening(bpy.types.Operator, tool.Ifc.Operator):
|
||||
)
|
||||
|
||||
if not has_visible_openings:
|
||||
tool.Ifc.unlink(obj=obj2)
|
||||
tool.Ifc.unlink(element=element2)
|
||||
bpy.data.objects.remove(obj2)
|
||||
|
||||
context.view_layer.objects.active = obj1
|
||||
@@ -148,7 +150,7 @@ class RemoveOpening(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
if opening_obj:
|
||||
opening_obj.name = "/".join(opening_obj.name.split("/")[1:])
|
||||
tool.Ifc.unlink(obj=opening_obj)
|
||||
tool.Ifc.unlink(element=opening)
|
||||
|
||||
ifcopenshell.api.run("void.remove_opening", tool.Ifc.get(), opening=opening)
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user