mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-20 23:36:20 +00:00
Merge branch 'IfcOpenShell:v0.7.0' into pr-daily-builds
This commit is contained in:
@@ -3,8 +3,18 @@ name: Publish-blenderbim-multiplatform
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
paths:
|
paths:
|
||||||
- 'src/blenderbim/**'
|
|
||||||
- '.github/workflows/ci-blenderbim-matrix.yml'
|
- '.github/workflows/ci-blenderbim-matrix.yml'
|
||||||
|
- 'src/blenderbim/**'
|
||||||
|
- 'src/ifcopenshell-python/ifcopenshell/**'
|
||||||
|
- 'src/bcf/src/bcf/**'
|
||||||
|
- 'src/ifcclash/ifcclash/**'
|
||||||
|
- 'src/ifccobie/**'
|
||||||
|
- 'src/ifcdiff/**'
|
||||||
|
- 'src/ifccsv/**'
|
||||||
|
- 'src/ifcpatch/ifcpatch/**'
|
||||||
|
- 'src/ifc4d/ifc4d/**'
|
||||||
|
- 'src/ifc5d/ifc5d/**'
|
||||||
|
- 'src/ifccityjson/**'
|
||||||
branches:
|
branches:
|
||||||
- v0.7.0
|
- v0.7.0
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
name: ci_py_only
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
paths:
|
||||||
|
- 'src/**'
|
||||||
|
- 'test/**'
|
||||||
|
- 'conda/**'
|
||||||
|
- 'cmake/**'
|
||||||
|
- '.github/workflows/ci_py_only.yml'
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
activate:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: |
|
||||||
|
github.repository == 'IfcOpenShell/IfcOpenShell' &&
|
||||||
|
!contains(github.event.head_commit.message, 'skip ci')
|
||||||
|
steps:
|
||||||
|
- run: echo ok go
|
||||||
|
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
needs: activate
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
- name: Install C++ dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt update
|
||||||
|
sudo apt-get install --no-install-recommends \
|
||||||
|
git cmake gcc g++ \
|
||||||
|
libboost-date-time-dev \
|
||||||
|
libboost-filesystem-dev \
|
||||||
|
libboost-iostreams-dev \
|
||||||
|
libboost-program-options-dev \
|
||||||
|
libboost-regex-dev \
|
||||||
|
libboost-system-dev \
|
||||||
|
libboost-thread-dev \
|
||||||
|
python3-all-dev python3-pip \
|
||||||
|
swig libpcre3-dev libxml2-dev \
|
||||||
|
libtbb-dev nlohmann-json3-dev \
|
||||||
|
libocct-foundation-dev libocct-modeling-algorithms-dev libocct-modeling-data-dev libocct-ocaf-dev libocct-visualization-dev libocct-data-exchange-dev \
|
||||||
|
libhdf5-dev libcgal-dev
|
||||||
|
|
||||||
|
- name: ccache
|
||||||
|
uses: hendrikmuhs/ccache-action@v1
|
||||||
|
|
||||||
|
- name: Build ifcopenshell
|
||||||
|
run: |
|
||||||
|
mkdir build && cd build
|
||||||
|
cmake \
|
||||||
|
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
||||||
|
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
|
||||||
|
-DCMAKE_BUILD_TYPE=Release \
|
||||||
|
-DCMAKE_PREFIX_PATH=/usr \
|
||||||
|
-DCMAKE_SYSTEM_PREFIX_PATH=/usr \
|
||||||
|
-DOCC_INCLUDE_DIR=/usr/include/opencascade \
|
||||||
|
-DOCC_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
|
||||||
|
-DPYTHON_EXECUTABLE:FILEPATH=/usr/bin/python3 \
|
||||||
|
-DPYTHON_INCLUDE_DIR:PATH=/usr/include/python3.8 \
|
||||||
|
-DPYTHON_LIBRARY:FILEPATH=/usr/lib/x86_64-linux-gnu/libpython3.8.so \
|
||||||
|
-DCOLLADA_SUPPORT=Off \
|
||||||
|
"-DSCHEMA_VERSIONS=2x3;4" \
|
||||||
|
-DBUILD_CONVERT=Off \
|
||||||
|
-DGLTF_SUPPORT=On \
|
||||||
|
-DJSON_INCLUDE_DIR=/usr/include \
|
||||||
|
-DCGAL_INCLUDE_DIR=/usr/include \
|
||||||
|
-DGMP_INCLUDE_DIR=/usr/include \
|
||||||
|
-DMPFR_INCLUDE_DIR=/usr/include \
|
||||||
|
-DGMP_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
|
||||||
|
-DMPFR_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
|
||||||
|
-DHDF5_INCLUDE_DIR=/usr/include/hdf5/serial \
|
||||||
|
../cmake
|
||||||
|
sudo make -j $(nproc)
|
||||||
|
sudo make install
|
||||||
|
|
||||||
|
- name: Install Python dependencies
|
||||||
|
run: |
|
||||||
|
sudo /usr/bin/python -m pip install -U pip
|
||||||
|
sudo /usr/bin/python -m pip install xmlschema numpy lxml
|
||||||
|
sudo /usr/bin/python -m pip install src/bcf
|
||||||
|
sudo /usr/bin/python -m pip install pytest
|
||||||
|
sudo /usr/bin/python -m pip install isodate
|
||||||
|
sudo /usr/bin/python -m pip install lark
|
||||||
|
sudo /usr/bin/python -m pip install networkx
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: |
|
||||||
|
cd test
|
||||||
|
sudo /usr/bin/python tests.py
|
||||||
|
cd ../src/ifcopenshell-python
|
||||||
|
mv ifcopenshell ifcopenshell-local # Force testing on installed module
|
||||||
|
make test
|
||||||
@@ -62,7 +62,7 @@ jobs:
|
|||||||
-DPYTHON_INCLUDE_DIR:PATH=/usr/include/python3.8 \
|
-DPYTHON_INCLUDE_DIR:PATH=/usr/include/python3.8 \
|
||||||
-DPYTHON_LIBRARY:FILEPATH=/usr/lib/x86_64-linux-gnu/libpython3.8.so \
|
-DPYTHON_LIBRARY:FILEPATH=/usr/lib/x86_64-linux-gnu/libpython3.8.so \
|
||||||
-DCOLLADA_SUPPORT=Off \
|
-DCOLLADA_SUPPORT=Off \
|
||||||
"-DSCHEMA_VERSIONS=2x3;4" \
|
"-DSCHEMA_VERSIONS=2x3;4;4x3" \
|
||||||
-DGLTF_SUPPORT=On \
|
-DGLTF_SUPPORT=On \
|
||||||
-DJSON_INCLUDE_DIR=/usr/include \
|
-DJSON_INCLUDE_DIR=/usr/include \
|
||||||
-DCGAL_INCLUDE_DIR=/usr/include \
|
-DCGAL_INCLUDE_DIR=/usr/include \
|
||||||
|
|||||||
+1
-1
@@ -10,4 +10,4 @@
|
|||||||
url = https://github.com/IfcOpenShell/svgfill
|
url = https://github.com/IfcOpenShell/svgfill
|
||||||
[submodule "src/ifcopenshell-python/test/Sample-BIM-Files"]
|
[submodule "src/ifcopenshell-python/test/Sample-BIM-Files"]
|
||||||
path = src/ifcopenshell-python/test/Sample-BIM-Files
|
path = src/ifcopenshell-python/test/Sample-BIM-Files
|
||||||
url = https://github.com/atomczak/Sample-BIM-Files
|
url = https://github.com/IfcOpenShell/ids-test-files
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Do not remove this test for UTF-8: if “Ω” doesn’t appear as greek uppercase omega letter enclosed in quotation marks, you should use an editor that supports UTF-8, not this one. -->
|
||||||
|
<package xmlns="http://schemas.microsoft.com/packaging/2015/06/nuspec.xsd">
|
||||||
|
<metadata>
|
||||||
|
<id>blenderbim-nightly</id>
|
||||||
|
<version>blenderbim_build_version</version>
|
||||||
|
<packageSourceUrl>https://github.com/IfcOpenShell/IfcOpenShell</packageSourceUrl>
|
||||||
|
<owners>fbpyr</owners>
|
||||||
|
<!-- == SOFTWARE SPECIFIC SECTION == -->
|
||||||
|
<title>BlenderBIM install nightly</title>
|
||||||
|
<authors>Dion Moult and many more, see: https://github.com/IfcOpenShell/IfcOpenShell/graphs/contributors</authors>
|
||||||
|
<projectUrl>https://github.com/IfcOpenShell/IfcOpenShell</projectUrl>
|
||||||
|
<iconUrl>https://rawcdn.githack.com/fbpyr/IfcOpenShell/9662598ad4a7e4b6cd8980e430d07263835d0eda/choco/blenderbim/blenderbim.png</iconUrl>
|
||||||
|
<!-- <copyright>Year Software Vendor</copyright> -->
|
||||||
|
<licenseUrl>https://github.com/IfcOpenShell/IfcOpenShell/blob/v0.7.0/COPYING</licenseUrl>
|
||||||
|
<requireLicenseAcceptance>true</requireLicenseAcceptance>
|
||||||
|
<projectSourceUrl>https://github.com/IfcOpenShell/IfcOpenShell</projectSourceUrl>
|
||||||
|
<docsUrl>https://blenderbim.org/docs/</docsUrl>
|
||||||
|
<!--<mailingListUrl></mailingListUrl>-->
|
||||||
|
<bugTrackerUrl>https://github.com/IfcOpenShell/IfcOpenShell/issues</bugTrackerUrl>
|
||||||
|
<tags>blender bim blenderbim python opensource foss</tags>
|
||||||
|
<summary>opensource bim</summary>
|
||||||
|
<description>opensource bim addon for blender</description>
|
||||||
|
<!-- <releaseNotes>__REPLACE_OR_REMOVE__MarkDown_Okay</releaseNotes> -->
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency id="blender" version="latest_blender_version_maj_min_pat" />
|
||||||
|
<dependency id="7zip" version="22.1"/>
|
||||||
|
</dependencies>
|
||||||
|
</metadata>
|
||||||
|
<files>
|
||||||
|
<file src="tools\**" target="tools" />
|
||||||
|
</files>
|
||||||
|
</package>
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 4.9 KiB |
@@ -0,0 +1,78 @@
|
|||||||
|
import sys
|
||||||
|
import re
|
||||||
|
import datetime
|
||||||
|
from urllib import request
|
||||||
|
|
||||||
|
|
||||||
|
def request_repo_info(url: str):
|
||||||
|
req = request.Request(url)
|
||||||
|
resp = request.urlopen(req)
|
||||||
|
if not resp.status == 200:
|
||||||
|
print(f"[ERROR] could not contact server: {url}")
|
||||||
|
quit(1)
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
if sys.argv[1] == "--do_choco_release?":
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
blenderbim_date = now.strftime("%y%m%d")
|
||||||
|
url = "https://github.com/IfcOpenShell/IfcOpenShell/releases/latest"
|
||||||
|
resp = request_repo_info(url)
|
||||||
|
if blenderbim_date in resp.url:
|
||||||
|
print("do_choco_release", end="")
|
||||||
|
|
||||||
|
|
||||||
|
elif sys.argv[1] == "--latest_blender_release_min_maj_pat?":
|
||||||
|
re_blender_version_min_maj_pat = r"Latest Version.+<span>Blender (\d+\.\d+\.\d+)</span>"
|
||||||
|
url = "https://community.chocolatey.org/packages/blender"
|
||||||
|
resp = request_repo_info(url)
|
||||||
|
html_txt = str(resp.read())
|
||||||
|
found = re.findall(re_blender_version_min_maj_pat, html_txt)
|
||||||
|
if found:
|
||||||
|
print(found[0], end="")
|
||||||
|
|
||||||
|
|
||||||
|
elif sys.argv[1] == "--latest_blender_release_min_maj?":
|
||||||
|
re_blender_version_min_maj = r"Latest Version.+<span>Blender (\d+\.\d+)\..+</span>"
|
||||||
|
url = "https://community.chocolatey.org/packages/blender"
|
||||||
|
resp = request_repo_info(url)
|
||||||
|
html_txt = str(resp.read())
|
||||||
|
found = re.findall(re_blender_version_min_maj, html_txt)
|
||||||
|
if found:
|
||||||
|
print(found[0], end="")
|
||||||
|
|
||||||
|
elif sys.argv[1] == "--latest_blender_python_version_maj_min?":
|
||||||
|
# get latest blender version first
|
||||||
|
re_blender_version_min_maj_pat = r"Latest Version.+<span>Blender (\d+\.\d+\.\d+)</span>"
|
||||||
|
url = "https://community.chocolatey.org/packages/blender"
|
||||||
|
resp = request_repo_info(url)
|
||||||
|
html_txt = str(resp.read())
|
||||||
|
found = re.findall(re_blender_version_min_maj_pat, html_txt)
|
||||||
|
if found:
|
||||||
|
latest_blender_version_tag = f"v{found[0]}"
|
||||||
|
re_blender_python_version_maj_min = r"SET\(PYTHON_VERSION (\d+.\d+) "
|
||||||
|
url = f"https://raw.githubusercontent.com/blender/blender/{latest_blender_version_tag}/build_files/cmake/Modules/FindPythonLibsUnix.cmake"
|
||||||
|
resp = request_repo_info(url)
|
||||||
|
html_txt = str(resp.read())
|
||||||
|
found = re.findall(re_blender_python_version_maj_min, html_txt)
|
||||||
|
if found:
|
||||||
|
print(found[0], end="")
|
||||||
|
|
||||||
|
|
||||||
|
elif sys.argv[1] == "--pyver?":
|
||||||
|
# get latest blender version first
|
||||||
|
re_blender_version_min_maj_pat = r"Latest Version.+<span>Blender (\d+\.\d+\.\d+)</span>"
|
||||||
|
url = "https://community.chocolatey.org/packages/blender"
|
||||||
|
resp = request_repo_info(url)
|
||||||
|
html_txt = str(resp.read())
|
||||||
|
found = re.findall(re_blender_version_min_maj_pat, html_txt)
|
||||||
|
if found:
|
||||||
|
latest_blender_version_tag = f"v{found[0]}"
|
||||||
|
re_blender_python_version_maj_min = r"SET\(PYTHON_VERSION (\d+.\d+) "
|
||||||
|
url = f"https://raw.githubusercontent.com/blender/blender/{latest_blender_version_tag}/build_files/cmake/Modules/FindPythonLibsUnix.cmake"
|
||||||
|
resp = request_repo_info(url)
|
||||||
|
html_txt = str(resp.read())
|
||||||
|
found = re.findall(re_blender_python_version_maj_min, html_txt)
|
||||||
|
if found:
|
||||||
|
print(f"py{found[0].replace('.', '')}", end="")
|
||||||
|
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
print("[INFO] inserting dynamic chocolatey package parameters")
|
||||||
|
HERE_DIR = Path(__file__).parent.absolute()
|
||||||
|
# print(f"[INFO] HERE_DIR: {HERE_DIR}")
|
||||||
|
|
||||||
|
topics = {
|
||||||
|
"spec": {
|
||||||
|
"path": HERE_DIR / "blenderbim.nuspec",
|
||||||
|
"env_vars": {
|
||||||
|
"latest_blender_version_maj_min_pat",
|
||||||
|
"blenderbim_build_version",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"install": {
|
||||||
|
"path": HERE_DIR / "tools" / "chocolateyinstall.ps1",
|
||||||
|
"env_vars": {
|
||||||
|
"url_blenderbim_py310_win_zip",
|
||||||
|
"sha256sum_blenderbim_py310_win_zip",
|
||||||
|
"latest_blender_version_maj_min",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"uninstall": {
|
||||||
|
"path": HERE_DIR / "tools" / "chocolateyuninstall.ps1",
|
||||||
|
"env_vars": {
|
||||||
|
"latest_blender_version_maj_min",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for topic, info in topics.items():
|
||||||
|
print(f"[INFO] {topic}:")
|
||||||
|
for env_var_name in info["env_vars"]:
|
||||||
|
print(f"[INFO] {env_var_name} exists?: {os.environ[env_var_name]}")
|
||||||
|
|
||||||
|
with open(info["path"], encoding="utf-8") as txt:
|
||||||
|
content = txt.read()
|
||||||
|
|
||||||
|
for env_var_name in info["env_vars"]:
|
||||||
|
# print(f"[INFO] replace: {env_var_name}")
|
||||||
|
content = content.replace(env_var_name, os.environ[env_var_name])
|
||||||
|
|
||||||
|
with open(info["path"], "w", encoding="utf-8") as txt:
|
||||||
|
txt.write(content)
|
||||||
|
print(f"[INFO] written: {info['path']}")
|
||||||
|
|
||||||
|
print("[INFO] inserting dynamic chocolatey package parameters successful")
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
$processName = "blender"
|
||||||
|
$blenderIsRunning = Get-Process -Name $processName -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
if($blenderIsRunning -eq $null) {
|
||||||
|
echo "Blender is not running ready for next action."
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Warning "$processName is still running - installer cannot safely proceed to next action."
|
||||||
|
Write-Warning "Please retry after closing all running blender applications."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
|
$url64 = 'url_blenderbim_py310_win_zip'
|
||||||
|
$checksum64 = 'sha256sum_blenderbim_py310_win_zip'
|
||||||
|
$checksumType64 = 'sha256'
|
||||||
|
|
||||||
|
$appDataUserDir = [System.Environment]::GetEnvironmentVariable('appdata')
|
||||||
|
$unzipTargetDir = "$appDataUserDir\Blender Foundation\Blender\latest_blender_version_maj_min\scripts\addons"
|
||||||
|
|
||||||
|
$chocoBaseDir = [System.Environment]::GetEnvironmentVariable('ChocolateyInstall')
|
||||||
|
$addonEnable = "$chocoBaseDir\lib\blenderbim\tools\enable_blenderbim_addon.py"
|
||||||
|
|
||||||
|
$programFilesDir = [System.Environment]::GetEnvironmentVariable('ProgramFiles')
|
||||||
|
$blenderExePath = "$env:ProgramFiles\Blender Foundation\Blender latest_blender_version_maj_min\blender.exe"
|
||||||
|
|
||||||
|
$processName = "blender"
|
||||||
|
$blenderIsRunning = Get-Process -Name $processName -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
|
||||||
|
if($blenderIsRunning -eq $null) {
|
||||||
|
echo "attempting to install blenderbim."
|
||||||
|
Install-ChocolateyZipPackage -PackageName $env:ChocolateyPackageName -Url64 $url64 -Checksum64 $checksum64 -checksumType $checksumType64 -UnzipLocation $unzipTargetDir
|
||||||
|
Start-Process -FilePath $blenderExePath -ArgumentList "-b", "-y", "--python", "$addonEnable"
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Warning "$processName is still running - blenderbim cannot be safely installed."
|
||||||
|
Write-Warning "Please retry after closing all running blender applications."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
$appDataUserDir = [System.Environment]::GetEnvironmentVariable('appdata')
|
||||||
|
$unzippedDir = "$appDataUserDir\Blender Foundation\Blender\latest_blender_version_maj_min\scripts\addons\blenderbim"
|
||||||
|
|
||||||
|
$chocoBaseDir = [System.Environment]::GetEnvironmentVariable('ChocolateyInstall')
|
||||||
|
$addonDisable = "$chocoBaseDir\lib\blenderbim\tools\disable_blenderbim_addon.py"
|
||||||
|
|
||||||
|
$programFilesDir = [System.Environment]::GetEnvironmentVariable('ProgramFiles')
|
||||||
|
$blenderExePath = "$env:ProgramFiles\Blender Foundation\Blender latest_blender_version_maj_min\blender.exe"
|
||||||
|
|
||||||
|
$processName = "blender"
|
||||||
|
$blenderIsRunning = Get-Process -Name $processName -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
if($blenderIsRunning -eq $null) {
|
||||||
|
echo "attempting to uninstall blenderbim."
|
||||||
|
Start-Process -FilePath $blenderExePath -ArgumentList "-b", "-y", "--python", "$addonDisable"
|
||||||
|
Remove-Item -LiteralPath $unzippedDir -Force -Recurse
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Warning "$processName is still running - blenderbim cannot be safely uninstalled."
|
||||||
|
Write-Warning "Please retry after closing all running blender applications."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import bpy
|
||||||
|
|
||||||
|
|
||||||
|
bpy.ops.preferences.addon_disable(module='blenderbim')
|
||||||
|
bpy.ops.wm.save_userpref()
|
||||||
|
|
||||||
|
print("disabled blenderbim addon successfully.")
|
||||||
|
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import bpy
|
||||||
|
|
||||||
|
|
||||||
|
bpy.ops.preferences.addon_enable(module='blenderbim')
|
||||||
|
bpy.ops.wm.save_userpref()
|
||||||
|
|
||||||
|
print("enabled blenderbim addon successfully.")
|
||||||
|
|
||||||
+10
-3
@@ -620,13 +620,16 @@ function(files_for_ifc_version IFC_VERSION RESULT_NAME)
|
|||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
if(NOT SCHEMA_VERSIONS)
|
if(NOT SCHEMA_VERSIONS)
|
||||||
set(SCHEMA_VERSIONS "2x3" "4" "4x1" "4x2" "4x3_rc1" "4x3_rc2" "4x3_rc3" "4x3_rc4")
|
set(SCHEMA_VERSIONS "2x3" "4" "4x1" "4x2" "4x3_rc1" "4x3_rc2" "4x3_rc3" "4x3_rc4" "4x3")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
foreach(s ${SCHEMA_VERSIONS})
|
foreach(s ${SCHEMA_VERSIONS})
|
||||||
add_definitions(-DHAS_SCHEMA_${s})
|
add_definitions(-DHAS_SCHEMA_${s})
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|
||||||
|
string(REPLACE ";" ")(" schema_version_seq "(${SCHEMA_VERSIONS})")
|
||||||
|
ADD_DEFINITIONS(-DSCHEMA_SEQ=${schema_version_seq})
|
||||||
|
|
||||||
if(COMPILE_SCHEMA)
|
if(COMPILE_SCHEMA)
|
||||||
# @todo, this appears to be untested at the moment
|
# @todo, this appears to be untested at the moment
|
||||||
|
|
||||||
@@ -696,7 +699,7 @@ if (BUILD_IFCGEOM)
|
|||||||
endforeach()
|
endforeach()
|
||||||
set(IFCOPENSHELL_LIBRARIES ${IFCOPENSHELL_LIBRARIES} IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES} IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES})
|
set(IFCOPENSHELL_LIBRARIES ${IFCOPENSHELL_LIBRARIES} IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES} IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES})
|
||||||
endif()
|
endif()
|
||||||
if (BUILD_CONVERT)
|
if (BUILD_CONVERT OR BUILD_IFCPYTHON)
|
||||||
foreach(s ${SCHEMA_VERSIONS})
|
foreach(s ${SCHEMA_VERSIONS})
|
||||||
set(SERIALIZER_SCHEMA_LIBRARIES ${SERIALIZER_SCHEMA_LIBRARIES} Serializers_ifc${s})
|
set(SERIALIZER_SCHEMA_LIBRARIES ${SERIALIZER_SCHEMA_LIBRARIES} Serializers_ifc${s})
|
||||||
endforeach()
|
endforeach()
|
||||||
@@ -769,7 +772,7 @@ TARGET_LINK_LIBRARIES(IfcGeom IfcParse ${IFCGEOM_SCHEMA_LIBRARIES} ${CMAKE_THREA
|
|||||||
|
|
||||||
endif(BUILD_IFCGEOM)
|
endif(BUILD_IFCGEOM)
|
||||||
|
|
||||||
if (BUILD_CONVERT)
|
if (BUILD_CONVERT OR BUILD_IFCPYTHON)
|
||||||
|
|
||||||
# Serializers
|
# Serializers
|
||||||
file(GLOB SERIALIZERS_H_FILES ../src/serializers/*.h)
|
file(GLOB SERIALIZERS_H_FILES ../src/serializers/*.h)
|
||||||
@@ -790,6 +793,10 @@ set_target_properties(Serializers PROPERTIES COMPILE_FLAGS "-DIFC_GEOM_EXPORTS $
|
|||||||
|
|
||||||
TARGET_LINK_LIBRARIES(Serializers ${SERIALIZER_SCHEMA_LIBRARIES} ${OPENCOLLADA_LIBRARIES})
|
TARGET_LINK_LIBRARIES(Serializers ${SERIALIZER_SCHEMA_LIBRARIES} ${OPENCOLLADA_LIBRARIES})
|
||||||
|
|
||||||
|
endif(BUILD_CONVERT or BUILD_IFCPYTHON)
|
||||||
|
|
||||||
|
if (BUILD_CONVERT)
|
||||||
|
|
||||||
# IfcConvert
|
# IfcConvert
|
||||||
file(GLOB IFCCONVERT_CPP_FILES ../src/ifcconvert/*.cpp)
|
file(GLOB IFCCONVERT_CPP_FILES ../src/ifcconvert/*.cpp)
|
||||||
file(GLOB IFCCONVERT_H_FILES ../src/ifcconvert/*.h)
|
file(GLOB IFCCONVERT_H_FILES ../src/ifcconvert/*.h)
|
||||||
|
|||||||
@@ -210,9 +210,9 @@ endif
|
|||||||
|
|
||||||
# Required by IFCDiff
|
# Required by IFCDiff
|
||||||
mkdir dist/working
|
mkdir dist/working
|
||||||
cd dist/working && wget https://github.com/Moult/deepdiff/archive/master.zip
|
cd dist/working && wget https://files.pythonhosted.org/packages/0f/ca/caead2949fbb824c7142e3774fa841aa853bb4d4331b440da8c8514dfc6f/deepdiff-5.8.1.tar.gz
|
||||||
cd dist/working && unzip master.zip
|
cd dist/working && tar -xzvf deepdiff*
|
||||||
cp -r dist/working/deepdiff-master/deepdiff dist/blenderbim/libs/site/packages/
|
cp -r dist/working/deepdiff-5.8.1/deepdiff dist/blenderbim/libs/site/packages/
|
||||||
rm -rf dist/working
|
rm -rf dist/working
|
||||||
|
|
||||||
# Required by deepdiff
|
# Required by deepdiff
|
||||||
|
|||||||
@@ -92,7 +92,9 @@ classes = [
|
|||||||
operator.SelectIfcFile,
|
operator.SelectIfcFile,
|
||||||
operator.SelectSchemaDir,
|
operator.SelectSchemaDir,
|
||||||
operator.SelectURIAttribute,
|
operator.SelectURIAttribute,
|
||||||
|
operator.EditBlenderCollection,
|
||||||
prop.StrProperty,
|
prop.StrProperty,
|
||||||
|
operator.BIM_OT_enum_property_search, # /!\ Register AFTER prop.StrProperty
|
||||||
prop.ObjProperty,
|
prop.ObjProperty,
|
||||||
prop.Attribute,
|
prop.Attribute,
|
||||||
prop.ModuleVisibility,
|
prop.ModuleVisibility,
|
||||||
|
|||||||
@@ -38,11 +38,14 @@ def draw_attribute(attribute, layout, copy_operator=None):
|
|||||||
if not value_name:
|
if not value_name:
|
||||||
layout.label(text=attribute.name)
|
layout.label(text=attribute.name)
|
||||||
return
|
return
|
||||||
layout.prop(
|
if value_name == "enum_value":
|
||||||
attribute,
|
prop_with_search(layout, attribute, "enum_value", text=attribute.name)
|
||||||
value_name,
|
else:
|
||||||
text=attribute.name,
|
layout.prop(
|
||||||
)
|
attribute,
|
||||||
|
value_name,
|
||||||
|
text=attribute.name,
|
||||||
|
)
|
||||||
if attribute.is_optional:
|
if attribute.is_optional:
|
||||||
layout.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
|
layout.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
|
||||||
if copy_operator:
|
if copy_operator:
|
||||||
@@ -105,6 +108,39 @@ def export_attributes(props, callback=None):
|
|||||||
return attributes
|
return attributes
|
||||||
|
|
||||||
|
|
||||||
|
def prop_with_search(layout, data, prop_name, **kwargs):
|
||||||
|
# kwargs are layout.prop arguments (text, icon, etc.)
|
||||||
|
row = layout.row(align=True)
|
||||||
|
# Magick courtesy of https://blender.stackexchange.com/a/203443/86891
|
||||||
|
row.context_pointer_set(name="data", data=data)
|
||||||
|
row.prop(data, prop_name, **kwargs)
|
||||||
|
op = row.operator("bim.enum_property_search", text="", icon="VIEWZOOM")
|
||||||
|
op.prop_name = prop_name
|
||||||
|
|
||||||
|
|
||||||
|
def get_enum_items(data, prop_name, context):
|
||||||
|
# Retrieve items from a dynamic EnumProperty, which is otherwise not supported
|
||||||
|
# Or throws an error in the console when the items callback returns an empty list
|
||||||
|
# See https://blender.stackexchange.com/q/215781/86891
|
||||||
|
prop = data.__annotations__[prop_name]
|
||||||
|
items = prop.keywords.get("items")
|
||||||
|
if items is None:
|
||||||
|
return
|
||||||
|
if not isinstance(items, (list, tuple)):
|
||||||
|
# items are retrieved through a callback, not a static list :
|
||||||
|
items = items(data, context)
|
||||||
|
return items
|
||||||
|
|
||||||
|
|
||||||
|
# hack to close popup
|
||||||
|
# https://blender.stackexchange.com/a/202576/130742
|
||||||
|
def close_operator_panel(event):
|
||||||
|
x, y = event.mouse_x, event.mouse_y
|
||||||
|
bpy.context.window.cursor_warp(10, 10)
|
||||||
|
move_back = lambda: bpy.context.window.cursor_warp(x, y)
|
||||||
|
bpy.app.timers.register(move_back, first_interval=0.01)
|
||||||
|
|
||||||
|
|
||||||
class IfcHeaderExtractor:
|
class IfcHeaderExtractor:
|
||||||
def __init__(self, filepath: str):
|
def __init__(self, filepath: str):
|
||||||
self.filepath = filepath
|
self.filepath = filepath
|
||||||
|
|||||||
@@ -245,8 +245,8 @@ class IfcImporter:
|
|||||||
self.create_structural_items()
|
self.create_structural_items()
|
||||||
self.profile_code("Create structural items")
|
self.profile_code("Create structural items")
|
||||||
if not self.ifc_import_settings.is_coordinating:
|
if not self.ifc_import_settings.is_coordinating:
|
||||||
self.create_type_products()
|
self.create_element_types()
|
||||||
self.profile_code("Create type products")
|
self.profile_code("Create element types")
|
||||||
self.place_objects_in_collections()
|
self.place_objects_in_collections()
|
||||||
self.profile_code("Place objects in collections")
|
self.profile_code("Place objects in collections")
|
||||||
self.add_project_to_scene()
|
self.add_project_to_scene()
|
||||||
@@ -326,6 +326,7 @@ class IfcImporter:
|
|||||||
self.elements = [e for e in self.elements if e.Representation and not e.is_a("IfcFeatureElement")]
|
self.elements = [e for e in self.elements if e.Representation and not e.is_a("IfcFeatureElement")]
|
||||||
|
|
||||||
self.elements = set(self.elements[offset:offset_limit])
|
self.elements = set(self.elements[offset:offset_limit])
|
||||||
|
self.element_types = set([ifcopenshell.util.element.get_type(e) for e in self.elements])
|
||||||
|
|
||||||
if self.ifc_import_settings.has_filter and self.ifc_import_settings.should_filter_spatial_elements:
|
if self.ifc_import_settings.has_filter and self.ifc_import_settings.should_filter_spatial_elements:
|
||||||
self.spatial_elements = self.get_spatial_elements_filtered_by_elements(self.elements)
|
self.spatial_elements = self.get_spatial_elements_filtered_by_elements(self.elements)
|
||||||
@@ -576,19 +577,13 @@ class IfcImporter:
|
|||||||
self.set_matrix_world(obj, grid_obj.matrix_world)
|
self.set_matrix_world(obj, grid_obj.matrix_world)
|
||||||
grid_collection.objects.link(obj)
|
grid_collection.objects.link(obj)
|
||||||
|
|
||||||
def create_type_products(self):
|
def create_element_types(self):
|
||||||
# TODO allow filtering of spatial elements too
|
for element_type in self.element_types:
|
||||||
if self.ifc_import_settings.has_filter:
|
if not element_type:
|
||||||
type_products = set([ifcopenshell.util.element.get_type(e) for e in self.elements])
|
|
||||||
else:
|
|
||||||
type_products = self.file.by_type("IfcTypeProduct")
|
|
||||||
|
|
||||||
for type_product in type_products:
|
|
||||||
if not type_product:
|
|
||||||
continue
|
continue
|
||||||
self.create_type_product(type_product)
|
self.create_element_type(element_type)
|
||||||
|
|
||||||
def create_type_product(self, element):
|
def create_element_type(self, element):
|
||||||
self.ifc_import_settings.logger.info("Creating object %s", element)
|
self.ifc_import_settings.logger.info("Creating object %s", element)
|
||||||
representation_map = self.get_type_product_body_representation_map(element)
|
representation_map = self.get_type_product_body_representation_map(element)
|
||||||
mesh = None
|
mesh = None
|
||||||
|
|||||||
@@ -768,6 +768,8 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
|||||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||||
props = context.scene.BCFProperties
|
props = context.scene.BCFProperties
|
||||||
blender_topic = props.active_topic
|
blender_topic = props.active_topic
|
||||||
|
if blender_topic is None:
|
||||||
|
return False
|
||||||
topic = bcfxml.topics[blender_topic.name]
|
topic = bcfxml.topics[blender_topic.name]
|
||||||
return topic.viewpoints
|
return topic.viewpoints
|
||||||
|
|
||||||
@@ -880,7 +882,7 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
|||||||
# Operators with context overrides are used because they are
|
# Operators with context overrides are used because they are
|
||||||
# significantly faster than looping through all objects
|
# significantly faster than looping through all objects
|
||||||
|
|
||||||
exception_global_ids = [v.ifc_guid for v in viewpoint.components.visibility.exceptions]
|
exception_global_ids = {v.ifc_guid for v in viewpoint.components.visibility.exceptions}
|
||||||
|
|
||||||
if viewpoint.components.visibility.default_visibility:
|
if viewpoint.components.visibility.default_visibility:
|
||||||
old = context.area.type
|
old = context.area.type
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ def getBcfViewpoints(self, context, force_update=False):
|
|||||||
props = context.scene.BCFProperties
|
props = context.scene.BCFProperties
|
||||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||||
topic = props.active_topic
|
topic = props.active_topic
|
||||||
viewpoints = bcfxml.get_viewpoints(topic.name)
|
viewpoints = bcfxml.get_viewpoints(topic.name) if topic else {}
|
||||||
bcfviewpoints_enum.extend([(v, f"Viewpoint {i+1}", "") for i, v in enumerate(viewpoints.keys())])
|
bcfviewpoints_enum.extend([(v, f"Viewpoint {i+1}", "") for i, v in enumerate(viewpoints.keys())])
|
||||||
return bcfviewpoints_enum
|
return bcfviewpoints_enum
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
import blenderbim.tool as tool
|
import blenderbim.tool as tool
|
||||||
from bpy.types import Panel, UIList
|
from bpy.types import Panel, UIList
|
||||||
|
from blenderbim.bim.helper import prop_with_search
|
||||||
from blenderbim.bim.module.brick.data import BrickschemaData, BrickschemaReferencesData
|
from blenderbim.bim.module.brick.data import BrickschemaData, BrickschemaReferencesData
|
||||||
|
|
||||||
|
|
||||||
@@ -48,7 +49,8 @@ class BIM_PT_brickschema(Panel):
|
|||||||
row.operator("bim.close_brick_project", text="", icon="CANCEL")
|
row.operator("bim.close_brick_project", text="", icon="CANCEL")
|
||||||
|
|
||||||
row = self.layout.row(align=True)
|
row = self.layout.row(align=True)
|
||||||
row.prop(self.props, "namespace", text="")
|
prop_with_search(row, self.props, "namespace", text="")
|
||||||
|
prop_with_search(row, self.props, "brick_equipment_class", text="")
|
||||||
row.prop(self.props, "brick_equipment_class", text="")
|
row.prop(self.props, "brick_equipment_class", text="")
|
||||||
row.operator("bim.add_brick", text="", icon="ADD")
|
row.operator("bim.add_brick", text="", icon="ADD")
|
||||||
|
|
||||||
@@ -101,7 +103,7 @@ class BIM_PT_ifc_brickschema_references(Panel):
|
|||||||
return
|
return
|
||||||
|
|
||||||
row = self.layout.row(align=True)
|
row = self.layout.row(align=True)
|
||||||
row.prop(self.props, "libraries")
|
prop_with_search(row, self.props, "libraries")
|
||||||
row.operator("bim.convert_brick_project", text="", icon="ADD")
|
row.operator("bim.convert_brick_project", text="", icon="ADD")
|
||||||
|
|
||||||
row = self.layout.row(align=True)
|
row = self.layout.row(align=True)
|
||||||
|
|||||||
@@ -64,8 +64,8 @@ class BIM_PT_context(bpy.types.Panel):
|
|||||||
row.operator("bim.remove_context", icon="X", text="").context = ifc_context["id"]
|
row.operator("bim.remove_context", icon="X", text="").context = ifc_context["id"]
|
||||||
|
|
||||||
row = box.row(align=True)
|
row = box.row(align=True)
|
||||||
row.prop(props, "subcontexts", text="")
|
blenderbim.bim.helper.prop_with_search(row, props, "subcontexts", text="")
|
||||||
row.prop(props, "target_views", text="")
|
blenderbim.bim.helper.prop_with_search(row, props, "target_views", text="")
|
||||||
op = row.operator("bim.add_context", icon="ADD", text="")
|
op = row.operator("bim.add_context", icon="ADD", text="")
|
||||||
op.context_type = ifc_context["context_type"]
|
op.context_type = ifc_context["context_type"]
|
||||||
op.context_identifier = props.subcontexts
|
op.context_identifier = props.subcontexts
|
||||||
|
|||||||
@@ -112,6 +112,7 @@ class CreateAllShapes(bpy.types.Operator):
|
|||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
self.file = IfcStore.get_file()
|
self.file = IfcStore.get_file()
|
||||||
elements = self.file.by_type("IfcElement") + self.file.by_type("IfcSpace")
|
elements = self.file.by_type("IfcElement") + self.file.by_type("IfcSpace")
|
||||||
|
|
||||||
total = len(elements)
|
total = len(elements)
|
||||||
settings = ifcopenshell.geom.settings()
|
settings = ifcopenshell.geom.settings()
|
||||||
failures = []
|
failures = []
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ classes = (
|
|||||||
operator.SelectDiffOldFile,
|
operator.SelectDiffOldFile,
|
||||||
operator.SelectDiffNewFile,
|
operator.SelectDiffNewFile,
|
||||||
operator.ExecuteIfcDiff,
|
operator.ExecuteIfcDiff,
|
||||||
|
prop.Relationships,
|
||||||
prop.DiffProperties,
|
prop.DiffProperties,
|
||||||
ui.BIM_PT_diff,
|
ui.BIM_PT_diff,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||||
|
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
|
||||||
|
#
|
||||||
|
# This file is part of BlenderBIM Add-on.
|
||||||
|
#
|
||||||
|
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
import json
|
||||||
|
import blenderbim.tool as tool
|
||||||
|
|
||||||
|
|
||||||
|
def refresh():
|
||||||
|
DiffData.is_loaded = False
|
||||||
|
|
||||||
|
|
||||||
|
class DiffData:
|
||||||
|
data = {}
|
||||||
|
is_loaded = False
|
||||||
|
diff_json_file = None
|
||||||
|
diff = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def load(cls):
|
||||||
|
cls.data = {"diff_json": cls.diff_json(), "changes": cls.changes()}
|
||||||
|
cls.is_loaded = True
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def diff_json(cls):
|
||||||
|
props = bpy.context.scene.DiffProperties
|
||||||
|
if not props.diff_json_file:
|
||||||
|
cls.diff = None
|
||||||
|
return
|
||||||
|
if props.diff_json_file != cls.diff_json_file:
|
||||||
|
cls.diff_json_file = props.diff_json_file
|
||||||
|
with open(props.diff_json_file, "r") as file:
|
||||||
|
cls.diff = json.load(file)
|
||||||
|
return cls.diff
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def changes(cls):
|
||||||
|
diff = cls.diff_json()
|
||||||
|
if not diff:
|
||||||
|
return {}
|
||||||
|
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||||
|
if not element or not hasattr(element, "GlobalId"):
|
||||||
|
return {}
|
||||||
|
return {k.upper().replace("_", " "): str(v) for k, v in diff["changed"].get(element.GlobalId, {}).items()}
|
||||||
@@ -20,6 +20,7 @@ import bpy
|
|||||||
import ifccsv
|
import ifccsv
|
||||||
import ifcopenshell
|
import ifcopenshell
|
||||||
import json
|
import json
|
||||||
|
import blenderbim.tool as tool
|
||||||
from blenderbim.bim.ifc import IfcStore
|
from blenderbim.bim.ifc import IfcStore
|
||||||
|
|
||||||
|
|
||||||
@@ -44,21 +45,23 @@ class VisualiseDiff(bpy.types.Operator):
|
|||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
# ifc_file = IfcStore.get_file() # In case we get from Store
|
ifc_file = tool.Ifc.get()
|
||||||
ifc_file = ifcopenshell.open(context.scene.DiffProperties.diff_new_file) # for Now refer to the new file
|
|
||||||
with open(context.scene.DiffProperties.diff_json_file, "r") as file:
|
with open(context.scene.DiffProperties.diff_json_file, "r") as file:
|
||||||
diff = json.load(file)
|
diff = json.load(file)
|
||||||
for obj in context.visible_objects:
|
for obj in context.visible_objects:
|
||||||
obj.color = (1.0, 1.0, 1.0, 0.2)
|
obj.color = (1.0, 1.0, 1.0, 1.0)
|
||||||
global_id = ifc_file.by_id(obj.BIMObjectProperties.ifc_definition_id).GlobalId
|
element = tool.Ifc.get_entity(obj)
|
||||||
|
if not element:
|
||||||
|
continue
|
||||||
|
global_id = getattr(element, "GlobalId", None)
|
||||||
if not global_id:
|
if not global_id:
|
||||||
continue
|
continue
|
||||||
if global_id in diff["deleted"]:
|
if global_id in diff["deleted"]:
|
||||||
obj.color = (1.0, 0.0, 0.0, 0.2)
|
obj.color = (1.0, 0.0, 0.0, 1.0)
|
||||||
elif global_id in diff["added"]:
|
elif global_id in diff["added"]:
|
||||||
obj.color = (0.0, 1.0, 0.0, 0.2)
|
obj.color = (0.0, 1.0, 0.0, 1.0)
|
||||||
elif global_id in diff["changed"]:
|
elif global_id in diff["changed"]:
|
||||||
obj.color = (0.0, 0.0, 1.0, 0.2)
|
obj.color = (0.0, 0.0, 1.0, 1.0)
|
||||||
area = next(area for area in context.screen.areas if area.type == "VIEW_3D")
|
area = next(area for area in context.screen.areas if area.type == "VIEW_3D")
|
||||||
area.spaces[0].shading.color_type = "OBJECT"
|
area.spaces[0].shading.color_type = "OBJECT"
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
@@ -113,9 +116,11 @@ class ExecuteIfcDiff(bpy.types.Operator):
|
|||||||
context.scene.DiffProperties.diff_old_file,
|
context.scene.DiffProperties.diff_old_file,
|
||||||
context.scene.DiffProperties.diff_new_file,
|
context.scene.DiffProperties.diff_new_file,
|
||||||
self.filepath,
|
self.filepath,
|
||||||
context.scene.DiffProperties.diff_relationships.split(),
|
[r.relationship for r in context.scene.DiffProperties.diff_relationships],
|
||||||
|
context.scene.DiffProperties.diff_filter_elements,
|
||||||
)
|
)
|
||||||
ifc_diff.diff()
|
diff = ifc_diff.diff()
|
||||||
ifc_diff.export()
|
ifc_diff.export()
|
||||||
context.scene.DiffProperties.diff_json_file = self.filepath
|
context.scene.DiffProperties.diff_json_file = self.filepath
|
||||||
|
context.scene.DiffProperties.diff_result = diff
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
from blenderbim.bim.prop import StrProperty
|
from blenderbim.bim.prop import StrProperty
|
||||||
|
from blenderbim.bim.module.diff.data import DiffData
|
||||||
from bpy.types import PropertyGroup
|
from bpy.types import PropertyGroup
|
||||||
from bpy.props import (
|
from bpy.props import (
|
||||||
PointerProperty,
|
PointerProperty,
|
||||||
@@ -31,8 +32,21 @@ from bpy.props import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def update_diff_json_file(self, context):
|
||||||
|
DiffData.data["diff_json"] = DiffData.diff_json()
|
||||||
|
|
||||||
|
|
||||||
|
class Relationships(PropertyGroup):
|
||||||
|
relationship: EnumProperty(
|
||||||
|
name="Relationship",
|
||||||
|
items=[(r, r.capitalize(), r) for r in ["type", "property", "container", "aggregate", "classification"]],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class DiffProperties(PropertyGroup):
|
class DiffProperties(PropertyGroup):
|
||||||
diff_json_file: StringProperty(default="", name="Diff JSON File")
|
diff_json_file: StringProperty(default="", name="Diff JSON File", update=update_diff_json_file)
|
||||||
diff_old_file: StringProperty(default="", name="Diff Old IFC File")
|
diff_old_file: StringProperty(default="", name="Diff Old IFC File")
|
||||||
diff_new_file: StringProperty(default="", name="Diff New IFC File")
|
diff_new_file: StringProperty(default="", name="Diff New IFC File")
|
||||||
diff_relationships: StringProperty(default="", name="Diff Relationships")
|
diff_relationships: CollectionProperty(type=Relationships, name="Diff Relationships")
|
||||||
|
diff_filter_elements: StringProperty(default="", name="Diff Filter")
|
||||||
|
diff_result: StringProperty(default="", name="Diff Result")
|
||||||
|
|||||||
@@ -18,6 +18,9 @@
|
|||||||
|
|
||||||
from bpy.types import Panel
|
from bpy.types import Panel
|
||||||
from blenderbim.bim.ifc import IfcStore
|
from blenderbim.bim.ifc import IfcStore
|
||||||
|
from blenderbim.bim.module.diff.data import DiffData
|
||||||
|
import blenderbim.tool as tool
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
class BIM_PT_diff(Panel):
|
class BIM_PT_diff(Panel):
|
||||||
@@ -30,6 +33,9 @@ class BIM_PT_diff(Panel):
|
|||||||
bl_parent_id = "BIM_PT_quality_control"
|
bl_parent_id = "BIM_PT_quality_control"
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
|
if not DiffData.is_loaded:
|
||||||
|
DiffData.load()
|
||||||
|
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
layout.use_property_split = True
|
layout.use_property_split = True
|
||||||
|
|
||||||
@@ -48,12 +54,41 @@ class BIM_PT_diff(Panel):
|
|||||||
|
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
row.prop(bim_properties, "diff_relationships")
|
row.prop(bim_properties, "diff_relationships")
|
||||||
|
row.context_pointer_set("bim_prop_group", bim_properties)
|
||||||
|
add = row.operator("bim.edit_blender_collection", icon="ADD", text="")
|
||||||
|
add.option = "add"
|
||||||
|
add.collection = "diff_relationships"
|
||||||
|
|
||||||
|
for index, r in enumerate(bim_properties.diff_relationships):
|
||||||
|
row = layout.row(align=True)
|
||||||
|
row.context_pointer_set("bim_prop_group", bim_properties)
|
||||||
|
row.prop(r, "relationship", text=" ")
|
||||||
|
remove = row.operator("bim.edit_blender_collection", icon="REMOVE", text="")
|
||||||
|
remove.option = "remove"
|
||||||
|
remove.collection = "diff_relationships"
|
||||||
|
remove.index = index
|
||||||
|
|
||||||
|
row = layout.row(align=True)
|
||||||
|
row.prop(bim_properties, "diff_filter_elements")
|
||||||
|
row.operator("bim.ifc_selector", icon="FILTER", text="")
|
||||||
|
|
||||||
row = layout.row()
|
row = layout.row()
|
||||||
row.operator("bim.execute_ifc_diff")
|
row.operator("bim.execute_ifc_diff")
|
||||||
|
if bim_properties.diff_result:
|
||||||
|
row = layout.row()
|
||||||
|
row.alignment = "CENTER"
|
||||||
|
row.label(text=bim_properties.diff_result)
|
||||||
|
|
||||||
# TODO: show if there ifc diff operation is sucessful
|
# TODO: show if there ifc diff operation is sucessful
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
row.prop(bim_properties, "diff_json_file")
|
row.prop(bim_properties, "diff_json_file")
|
||||||
row.operator("bim.select_diff_json_file", icon="FILE_FOLDER", text="")
|
row.operator("bim.select_diff_json_file", icon="FILE_FOLDER", text="")
|
||||||
row.operator("bim.visualise_diff", icon="HIDE_OFF", text="")
|
row.operator("bim.visualise_diff", icon="HIDE_OFF", text="")
|
||||||
|
|
||||||
|
if DiffData.data["changes"]:
|
||||||
|
row = layout.row()
|
||||||
|
row.label(text="Diff Results:")
|
||||||
|
for key, value in DiffData.data["changes"].items():
|
||||||
|
row = layout.row()
|
||||||
|
row.label(text=key)
|
||||||
|
row.label(text=value)
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ import blenderbim.bim.module.drawing.helper as helper
|
|||||||
import blenderbim.bim.export_ifc
|
import blenderbim.bim.export_ifc
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
from mathutils import Vector
|
from mathutils import Vector
|
||||||
|
from timeit import default_timer as timer
|
||||||
from blenderbim.bim.module.drawing.prop import RasterStyleProperty
|
from blenderbim.bim.module.drawing.prop import RasterStyleProperty
|
||||||
from blenderbim.bim.ifc import IfcStore
|
from blenderbim.bim.ifc import IfcStore
|
||||||
from ifcopenshell.api.group.data import Data as GroupData
|
from ifcopenshell.api.group.data import Data as GroupData
|
||||||
@@ -49,6 +50,21 @@ from ifcopenshell.api.pset.data import Data as PsetData
|
|||||||
cwd = os.path.dirname(os.path.realpath(__file__))
|
cwd = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
|
||||||
|
|
||||||
|
class profile:
|
||||||
|
"""
|
||||||
|
A python context manager timing utility
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, task):
|
||||||
|
self.task = task
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
self.start = timer()
|
||||||
|
|
||||||
|
def __exit__(self, *args):
|
||||||
|
print(self.task, timer() - self.start)
|
||||||
|
|
||||||
|
|
||||||
def open_with_user_command(user_command, path):
|
def open_with_user_command(user_command, path):
|
||||||
if user_command:
|
if user_command:
|
||||||
commands = eval(user_command)
|
commands = eval(user_command)
|
||||||
@@ -115,34 +131,39 @@ class CreateDrawing(bpy.types.Operator):
|
|||||||
self.camera = context.scene.camera
|
self.camera = context.scene.camera
|
||||||
self.camera_element = tool.Ifc.get_entity(self.camera)
|
self.camera_element = tool.Ifc.get_entity(self.camera)
|
||||||
self.file = IfcStore.get_file()
|
self.file = IfcStore.get_file()
|
||||||
self.time = None
|
|
||||||
start = time.time()
|
|
||||||
self.profile_code("Start drawing generation process")
|
|
||||||
self.props = context.scene.DocProperties
|
|
||||||
self.cprops = self.camera.data.BIMCameraProperties
|
|
||||||
self.drawing_name = self.file.by_id(self.camera.BIMObjectProperties.ifc_definition_id).Name
|
|
||||||
self.get_scale()
|
|
||||||
if self.cprops.update_representation(self.camera):
|
|
||||||
bpy.ops.bim.update_representation(obj=self.camera.name, ifc_representation_class="")
|
|
||||||
|
|
||||||
self.svg_writer = svgwriter.SvgWriter()
|
with profile("Drawing generation process"):
|
||||||
self.svg_writer.human_scale = self.human_scale
|
|
||||||
self.svg_writer.scale = self.scale
|
with profile("Initialize drawing generation process"):
|
||||||
self.svg_writer.data_dir = context.scene.BIMProperties.data_dir
|
self.props = context.scene.DocProperties
|
||||||
self.svg_writer.camera = self.camera
|
self.cprops = self.camera.data.BIMCameraProperties
|
||||||
self.svg_writer.camera_width, self.svg_writer.camera_height = self.get_camera_dimensions()
|
self.drawing_name = self.file.by_id(self.camera.BIMObjectProperties.ifc_definition_id).Name
|
||||||
self.svg_writer.camera_projection = tuple(self.camera.matrix_world.to_quaternion() @ Vector((0, 0, -1)))
|
self.get_scale()
|
||||||
|
if self.cprops.update_representation(self.camera):
|
||||||
|
bpy.ops.bim.update_representation(obj=self.camera.name, ifc_representation_class="")
|
||||||
|
|
||||||
|
self.svg_writer = svgwriter.SvgWriter()
|
||||||
|
self.svg_writer.human_scale = self.human_scale
|
||||||
|
self.svg_writer.scale = self.scale
|
||||||
|
self.svg_writer.data_dir = context.scene.BIMProperties.data_dir
|
||||||
|
self.svg_writer.camera = self.camera
|
||||||
|
self.svg_writer.camera_width, self.svg_writer.camera_height = self.get_camera_dimensions()
|
||||||
|
self.svg_writer.camera_projection = tuple(self.camera.matrix_world.to_quaternion() @ Vector((0, 0, -1)))
|
||||||
|
|
||||||
|
with profile("Generate underlay"):
|
||||||
|
underlay_svg = self.generate_underlay(context)
|
||||||
|
|
||||||
|
with profile("Generate linework"):
|
||||||
|
linework_svg = self.generate_linework(context)
|
||||||
|
|
||||||
|
with profile("Generate annotation"):
|
||||||
|
annotation_svg = self.generate_annotation(context)
|
||||||
|
|
||||||
|
with profile("Combine SVG layers"):
|
||||||
|
svg_path = self.combine_svgs(context, underlay_svg, linework_svg, annotation_svg)
|
||||||
|
|
||||||
underlay_svg = self.generate_underlay(context)
|
|
||||||
self.profile_code("Generate underlay")
|
|
||||||
linework_svg = self.generate_linework(context)
|
|
||||||
self.profile_code("Generate linework")
|
|
||||||
annotation_svg = self.generate_annotation(context)
|
|
||||||
self.profile_code("Generate annotation")
|
|
||||||
svg_path = self.combine_svgs(context, underlay_svg, linework_svg, annotation_svg)
|
|
||||||
self.profile_code("Combine SVG layers")
|
|
||||||
open_with_user_command(context.preferences.addons["blenderbim"].preferences.svg_command, svg_path)
|
open_with_user_command(context.preferences.addons["blenderbim"].preferences.svg_command, svg_path)
|
||||||
print("Total Time: {:.2f}".format(time.time() - start))
|
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
def get_camera_dimensions(self):
|
def get_camera_dimensions(self):
|
||||||
@@ -155,12 +176,6 @@ class CreateDrawing(bpy.types.Operator):
|
|||||||
width = height / render.resolution_y * render.resolution_x
|
width = height / render.resolution_y * render.resolution_x
|
||||||
return width, height
|
return width, height
|
||||||
|
|
||||||
def profile_code(self, message):
|
|
||||||
if not self.time:
|
|
||||||
self.time = time.time()
|
|
||||||
print("{} :: {:.2f}".format(message, time.time() - self.time))
|
|
||||||
self.time = time.time()
|
|
||||||
|
|
||||||
def combine_svgs(self, context, underlay, linework, annotation):
|
def combine_svgs(self, context, underlay, linework, annotation):
|
||||||
# Hacky :)
|
# Hacky :)
|
||||||
svg_path = os.path.join(context.scene.BIMProperties.data_dir, "diagrams", self.drawing_name + ".svg")
|
svg_path = os.path.join(context.scene.BIMProperties.data_dir, "diagrams", self.drawing_name + ".svg")
|
||||||
@@ -255,13 +270,14 @@ class CreateDrawing(bpy.types.Operator):
|
|||||||
if os.path.isfile(svg_path) and self.props.should_use_linework_cache:
|
if os.path.isfile(svg_path) and self.props.should_use_linework_cache:
|
||||||
return svg_path
|
return svg_path
|
||||||
|
|
||||||
# All very hackish whilst prototyping
|
with profile("sync"):
|
||||||
exporter = blenderbim.bim.export_ifc.IfcExporter(None)
|
# All very hackish whilst prototyping
|
||||||
exporter.file = tool.Ifc.get()
|
exporter = blenderbim.bim.export_ifc.IfcExporter(None)
|
||||||
invalidated_guids = exporter.sync_deletions()
|
exporter.file = tool.Ifc.get()
|
||||||
invalidated_elements = exporter.sync_all_objects()
|
invalidated_guids = exporter.sync_deletions()
|
||||||
invalidated_elements += exporter.sync_edited_objects()
|
invalidated_elements = exporter.sync_all_objects()
|
||||||
[invalidated_guids.append(e.GlobalId) for e in invalidated_elements if hasattr(e, "GlobalId")]
|
invalidated_elements += exporter.sync_edited_objects()
|
||||||
|
[invalidated_guids.append(e.GlobalId) for e in invalidated_elements if hasattr(e, "GlobalId")]
|
||||||
|
|
||||||
# If we have already calculated it in the SVG in the past, don't recalculate
|
# If we have already calculated it in the SVG in the past, don't recalculate
|
||||||
edited_guids = set()
|
edited_guids = set()
|
||||||
@@ -312,40 +328,46 @@ class CreateDrawing(bpy.types.Operator):
|
|||||||
cache = IfcStore.get_cache()
|
cache = IfcStore.get_cache()
|
||||||
[cache.remove(guid) for guid in invalidated_guids]
|
[cache.remove(guid) for guid in invalidated_guids]
|
||||||
|
|
||||||
if target_view_contexts and elements:
|
with profile("Processing target view context"):
|
||||||
geom_settings = ifcopenshell.geom.settings(
|
if target_view_contexts and elements:
|
||||||
DISABLE_TRIANGULATION=True, STRICT_TOLERANCE=True, INCLUDE_CURVES=True
|
geom_settings = ifcopenshell.geom.settings(
|
||||||
)
|
DISABLE_TRIANGULATION=True, STRICT_TOLERANCE=True, INCLUDE_CURVES=True
|
||||||
geom_settings.set_context_ids(target_view_contexts)
|
)
|
||||||
it = ifcopenshell.geom.iterator(geom_settings, ifc, multiprocessing.cpu_count(), include=elements)
|
geom_settings.set_context_ids(target_view_contexts)
|
||||||
it.set_cache(cache)
|
it = ifcopenshell.geom.iterator(geom_settings, ifc, multiprocessing.cpu_count(), include=elements)
|
||||||
processed = set()
|
it.set_cache(cache)
|
||||||
for elem in self.yield_from_iterator(it):
|
processed = set()
|
||||||
processed.add(ifc.by_id(elem.id))
|
for elem in self.yield_from_iterator(it):
|
||||||
self.serialiser.write(elem)
|
processed.add(ifc.by_id(elem.id))
|
||||||
elements -= processed
|
self.serialiser.write(elem)
|
||||||
|
elements -= processed
|
||||||
|
|
||||||
if body_contexts and elements:
|
with profile("Processing body context"):
|
||||||
|
if body_contexts and elements:
|
||||||
|
geom_settings = ifcopenshell.geom.settings(DISABLE_TRIANGULATION=True, STRICT_TOLERANCE=True)
|
||||||
|
geom_settings.set_context_ids(body_contexts)
|
||||||
|
it = ifcopenshell.geom.iterator(geom_settings, ifc, multiprocessing.cpu_count(), include=elements)
|
||||||
|
it.set_cache(cache)
|
||||||
|
for elem in self.yield_from_iterator(it):
|
||||||
|
self.serialiser.write(elem)
|
||||||
|
|
||||||
|
with profile("Camera element"):
|
||||||
|
# @todo tfk: is this needed?
|
||||||
geom_settings = ifcopenshell.geom.settings(DISABLE_TRIANGULATION=True, STRICT_TOLERANCE=True)
|
geom_settings = ifcopenshell.geom.settings(DISABLE_TRIANGULATION=True, STRICT_TOLERANCE=True)
|
||||||
geom_settings.set_context_ids(body_contexts)
|
it = ifcopenshell.geom.iterator(geom_settings, ifc, include=[self.camera_element])
|
||||||
it = ifcopenshell.geom.iterator(geom_settings, ifc, multiprocessing.cpu_count(), include=elements)
|
|
||||||
it.set_cache(cache)
|
|
||||||
for elem in self.yield_from_iterator(it):
|
for elem in self.yield_from_iterator(it):
|
||||||
self.serialiser.write(elem)
|
self.serialiser.write(elem)
|
||||||
|
|
||||||
geom_settings = ifcopenshell.geom.settings(DISABLE_TRIANGULATION=True, STRICT_TOLERANCE=True)
|
with profile("Finalizing"):
|
||||||
it = ifcopenshell.geom.iterator(geom_settings, ifc, include=[self.camera_element])
|
self.serialiser.finalize()
|
||||||
for elem in self.yield_from_iterator(it):
|
|
||||||
self.serialiser.write(elem)
|
|
||||||
|
|
||||||
self.serialiser.finalize()
|
|
||||||
results = self.svg_buffer.get_value()
|
results = self.svg_buffer.get_value()
|
||||||
|
|
||||||
root = etree.fromstring(results)
|
with profile("Post processing"):
|
||||||
self.enrich_linework_with_metadata(root)
|
root = etree.fromstring(results)
|
||||||
self.move_projection_to_bottom(root)
|
self.enrich_linework_with_metadata(root)
|
||||||
with open(svg_path, "wb") as svg:
|
self.move_projection_to_bottom(root)
|
||||||
svg.write(etree.tostring(root))
|
with open(svg_path, "wb") as svg:
|
||||||
|
svg.write(etree.tostring(root))
|
||||||
|
|
||||||
return svg_path
|
return svg_path
|
||||||
|
|
||||||
@@ -487,8 +509,11 @@ class AddAnnotation(bpy.types.Operator, Operator):
|
|||||||
def _execute(self, context):
|
def _execute(self, context):
|
||||||
drawing = tool.Ifc.get_entity(context.scene.camera)
|
drawing = tool.Ifc.get_entity(context.scene.camera)
|
||||||
if not drawing:
|
if not drawing:
|
||||||
|
self.report({"WARNING"}, "Not a BIM camera")
|
||||||
return
|
return
|
||||||
core.add_annotation(tool.Ifc, tool.Collector, tool.Drawing, drawing=drawing, object_type=self.object_type)
|
r = core.add_annotation(tool.Ifc, tool.Collector, tool.Drawing, drawing=drawing, object_type=self.object_type)
|
||||||
|
if isinstance(r, str):
|
||||||
|
self.report({"WARNING"}, r)
|
||||||
|
|
||||||
|
|
||||||
class AddSheet(bpy.types.Operator, Operator):
|
class AddSheet(bpy.types.Operator, Operator):
|
||||||
|
|||||||
@@ -392,6 +392,7 @@ class BIMTextProperties(PropertyGroup):
|
|||||||
name="Font Size",
|
name="Font Size",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class BIMAssignedProductProperties(PropertyGroup):
|
class BIMAssignedProductProperties(PropertyGroup):
|
||||||
is_editing_product: BoolProperty(name="Is Editing Product", default=False)
|
is_editing_product: BoolProperty(name="Is Editing Product", default=False)
|
||||||
relating_product: PointerProperty(name="Relating Product", type=bpy.types.Object)
|
relating_product: PointerProperty(name="Relating Product", type=bpy.types.Object)
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ class SheetBuilder:
|
|||||||
sheet_tree = ET.parse(sheet_path)
|
sheet_tree = ET.parse(sheet_path)
|
||||||
sheet_root = sheet_tree.getroot()
|
sheet_root = sheet_tree.getroot()
|
||||||
|
|
||||||
for g in sheet_root.findall('{http://www.w3.org/2000/svg}g'):
|
for g in sheet_root.findall("{http://www.w3.org/2000/svg}g"):
|
||||||
if g.attrib.get("data-id") == str(reference.id()):
|
if g.attrib.get("data-id") == str(reference.id()):
|
||||||
sheet_root.remove(g)
|
sheet_root.remove(g)
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ class SvgWriter:
|
|||||||
viewBox=("0 0 {} {}".format(self.width, self.height)),
|
viewBox=("0 0 {} {}".format(self.width, self.height)),
|
||||||
id="root",
|
id="root",
|
||||||
data_scale=self.human_scale,
|
data_scale=self.human_scale,
|
||||||
debug=False, # Disable validation so that we can insert the IFC namespace
|
debug=False, # Disable validation so that we can insert the IFC namespace
|
||||||
)
|
)
|
||||||
self.svg.attribs["xmlns:ifc"] = "http://www.ifcopenshell.org/ns"
|
self.svg.attribs["xmlns:ifc"] = "http://www.ifcopenshell.org/ns"
|
||||||
return self
|
return self
|
||||||
@@ -81,7 +81,9 @@ class SvgWriter:
|
|||||||
def draw_underlay(self, image):
|
def draw_underlay(self, image):
|
||||||
self.svg.add(
|
self.svg.add(
|
||||||
self.svg.image(
|
self.svg.image(
|
||||||
os.path.join("..", "diagrams", os.path.basename(image)), width=self.width, height=self.height,
|
os.path.join("..", "diagrams", os.path.basename(image)),
|
||||||
|
width=self.width,
|
||||||
|
height=self.height,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return self
|
return self
|
||||||
@@ -252,7 +254,9 @@ class SvgWriter:
|
|||||||
end = Vector(((x_offset + v1.x), (y_offset - v1.y)))
|
end = Vector(((x_offset + v1.x), (y_offset - v1.y)))
|
||||||
vector = end - start
|
vector = end - start
|
||||||
line = self.svg.add(
|
line = self.svg.add(
|
||||||
self.svg.line(start=tuple(start * self.svg_scale), end=tuple(end * self.svg_scale), class_=" ".join(classes))
|
self.svg.line(
|
||||||
|
start=tuple(start * self.svg_scale), end=tuple(end * self.svg_scale), class_=" ".join(classes)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
line["stroke-dasharray"] = "12.5, 3, 3, 3"
|
line["stroke-dasharray"] = "12.5, 3, 3, 3"
|
||||||
axis_tag = tool.Ifc.get_entity(obj).Name
|
axis_tag = tool.Ifc.get_entity(obj).Name
|
||||||
@@ -361,7 +365,9 @@ class SvgWriter:
|
|||||||
end = Vector(((x_offset + v1.x), (y_offset - v1.y)))
|
end = Vector(((x_offset + v1.x), (y_offset - v1.y)))
|
||||||
vector = end - start
|
vector = end - start
|
||||||
line = self.svg.add(
|
line = self.svg.add(
|
||||||
self.svg.line(start=tuple(start * self.svg_scale), end=tuple(end * self.svg_scale), class_=" ".join(classes))
|
self.svg.line(
|
||||||
|
start=tuple(start * self.svg_scale), end=tuple(end * self.svg_scale), class_=" ".join(classes)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def draw_leader_annotation(self, obj):
|
def draw_leader_annotation(self, obj):
|
||||||
@@ -427,7 +433,9 @@ class SvgWriter:
|
|||||||
(symbol_position * self.svg_scale)[1],
|
(symbol_position * self.svg_scale)[1],
|
||||||
)
|
)
|
||||||
|
|
||||||
self.svg.add(self.svg.use("#elevation-arrow", insert=tuple(symbol_position * self.svg_scale), transform=transform))
|
self.svg.add(
|
||||||
|
self.svg.use("#elevation-arrow", insert=tuple(symbol_position * self.svg_scale), transform=transform)
|
||||||
|
)
|
||||||
self.svg.add(self.svg.use("#elevation-tag", insert=tuple(symbol_position * self.svg_scale)))
|
self.svg.add(self.svg.use("#elevation-tag", insert=tuple(symbol_position * self.svg_scale)))
|
||||||
|
|
||||||
reference_id, sheet_id = self.get_reference_and_sheet_id_from_annotation(tool.Ifc.get_entity(obj))
|
reference_id, sheet_id = self.get_reference_and_sheet_id_from_annotation(tool.Ifc.get_entity(obj))
|
||||||
@@ -666,7 +674,7 @@ class SvgWriter:
|
|||||||
dir2 = ((arc_matrix.inverted() @ arc_end_pts[1]) - (arc_matrix.inverted() @ center)).normalized()
|
dir2 = ((arc_matrix.inverted() @ arc_end_pts[1]) - (arc_matrix.inverted() @ center)).normalized()
|
||||||
angle = -dir1.xy.angle_signed(dir2.xy)
|
angle = -dir1.xy.angle_signed(dir2.xy)
|
||||||
|
|
||||||
#if is_reflex:
|
# if is_reflex:
|
||||||
# angle = angle % (math.pi * 2)
|
# angle = angle % (math.pi * 2)
|
||||||
|
|
||||||
# Center of gravity of all vertices, used to help position the text
|
# Center of gravity of all vertices, used to help position the text
|
||||||
@@ -680,7 +688,9 @@ class SvgWriter:
|
|||||||
arc_midpoint = center + ((cog - center).normalized() * radius)
|
arc_midpoint = center + ((cog - center).normalized() * radius)
|
||||||
|
|
||||||
text_position = self.project_point_onto_camera(arc_midpoint)
|
text_position = self.project_point_onto_camera(arc_midpoint)
|
||||||
text_position = Vector(((x_offset + text_position.x) * self.svg_scale, (y_offset - text_position.y) * self.svg_scale))
|
text_position = Vector(
|
||||||
|
((x_offset + text_position.x) * self.svg_scale, (y_offset - text_position.y) * self.svg_scale)
|
||||||
|
)
|
||||||
|
|
||||||
center_projected = self.project_point_onto_camera(center)
|
center_projected = self.project_point_onto_camera(center)
|
||||||
center_position = Vector(
|
center_position = Vector(
|
||||||
@@ -704,8 +714,12 @@ class SvgWriter:
|
|||||||
|
|
||||||
# Draw SVG arc, see for details: http://xahlee.info/js/svg_circle_arc.html
|
# Draw SVG arc, see for details: http://xahlee.info/js/svg_circle_arc.html
|
||||||
arc_proj_end_pts = [self.project_point_onto_camera(v) for v in arc_end_pts]
|
arc_proj_end_pts = [self.project_point_onto_camera(v) for v in arc_end_pts]
|
||||||
p1 = Vector(((x_offset + arc_proj_end_pts[0].x) * self.svg_scale, (y_offset - arc_proj_end_pts[0].y) * self.svg_scale))
|
p1 = Vector(
|
||||||
p2 = Vector(((x_offset + arc_proj_end_pts[1].x) * self.svg_scale, (y_offset - arc_proj_end_pts[1].y) * self.svg_scale))
|
((x_offset + arc_proj_end_pts[0].x) * self.svg_scale, (y_offset - arc_proj_end_pts[0].y) * self.svg_scale)
|
||||||
|
)
|
||||||
|
p2 = Vector(
|
||||||
|
((x_offset + arc_proj_end_pts[1].x) * self.svg_scale, (y_offset - arc_proj_end_pts[1].y) * self.svg_scale)
|
||||||
|
)
|
||||||
r = radius * self.svg_scale
|
r = radius * self.svg_scale
|
||||||
# reflex = 1 if angle > math.pi else 0
|
# reflex = 1 if angle > math.pi else 0
|
||||||
reflex = is_reflex
|
reflex = is_reflex
|
||||||
@@ -735,10 +749,16 @@ class SvgWriter:
|
|||||||
path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes)))
|
path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes)))
|
||||||
|
|
||||||
p0 = Vector(
|
p0 = Vector(
|
||||||
((x_offset + projected_points[0].x) * self.svg_scale, (y_offset - projected_points[0].y) * self.svg_scale)
|
(
|
||||||
|
(x_offset + projected_points[0].x) * self.svg_scale,
|
||||||
|
(y_offset - projected_points[0].y) * self.svg_scale,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
p1 = Vector(
|
p1 = Vector(
|
||||||
((x_offset + projected_points[1].x) * self.svg_scale, (y_offset - projected_points[1].y) * self.svg_scale)
|
(
|
||||||
|
(x_offset + projected_points[1].x) * self.svg_scale,
|
||||||
|
(y_offset - projected_points[1].y) * self.svg_scale,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
text_offset = (p0 - p1).xy.normalized() * 5
|
text_offset = (p0 - p1).xy.normalized() * 5
|
||||||
text_position = projected_points[0]
|
text_position = projected_points[0]
|
||||||
@@ -823,7 +843,9 @@ class SvgWriter:
|
|||||||
text_position = (mid * self.svg_scale) + perpendicular
|
text_position = (mid * self.svg_scale) + perpendicular
|
||||||
rotation = math.degrees(vector.angle_signed(Vector((1, 0))))
|
rotation = math.degrees(vector.angle_signed(Vector((1, 0))))
|
||||||
line = self.svg.add(
|
line = self.svg.add(
|
||||||
self.svg.line(start=tuple(start * self.svg_scale), end=tuple(end * self.svg_scale), class_=" ".join(classes))
|
self.svg.line(
|
||||||
|
start=tuple(start * self.svg_scale), end=tuple(end * self.svg_scale), class_=" ".join(classes)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
if text_override is not None:
|
if text_override is not None:
|
||||||
text = text_override
|
text = text_override
|
||||||
|
|||||||
@@ -130,10 +130,6 @@ class UpdateRepresentation(bpy.types.Operator):
|
|||||||
obj: bpy.props.StringProperty()
|
obj: bpy.props.StringProperty()
|
||||||
ifc_representation_class: bpy.props.StringProperty()
|
ifc_representation_class: bpy.props.StringProperty()
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def poll(cls, context):
|
|
||||||
return context.active_object.mode == "OBJECT"
|
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
return IfcStore.execute_ifc_operator(self, context)
|
return IfcStore.execute_ifc_operator(self, context)
|
||||||
|
|
||||||
@@ -141,6 +137,10 @@ class UpdateRepresentation(bpy.types.Operator):
|
|||||||
if not ContextData.is_loaded:
|
if not ContextData.is_loaded:
|
||||||
ContextData.load(IfcStore.get_file())
|
ContextData.load(IfcStore.get_file())
|
||||||
|
|
||||||
|
if context.active_object and context.active_object.mode != "OBJECT":
|
||||||
|
# Ensure mode is object to prevent invalid mesh data causing CTD
|
||||||
|
bpy.ops.object.mode_set(mode="OBJECT", toggle=False)
|
||||||
|
|
||||||
objs = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects
|
objs = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects
|
||||||
self.file = IfcStore.get_file()
|
self.file = IfcStore.get_file()
|
||||||
|
|
||||||
@@ -207,7 +207,10 @@ class UpdateRepresentation(bpy.types.Operator):
|
|||||||
new_representation = ifcopenshell.api.run("geometry.add_representation", self.file, **representation_data)
|
new_representation = ifcopenshell.api.run("geometry.add_representation", self.file, **representation_data)
|
||||||
|
|
||||||
if tool.Geometry.is_body_representation(new_representation):
|
if tool.Geometry.is_body_representation(new_representation):
|
||||||
[tool.Geometry.run_style_add_style(obj=mat) for mat in tool.Geometry.get_object_materials_without_styles(obj)]
|
[
|
||||||
|
tool.Geometry.run_style_add_style(obj=mat)
|
||||||
|
for mat in tool.Geometry.get_object_materials_without_styles(obj)
|
||||||
|
]
|
||||||
ifcopenshell.api.run(
|
ifcopenshell.api.run(
|
||||||
"style.assign_representation_styles",
|
"style.assign_representation_styles",
|
||||||
self.file,
|
self.file,
|
||||||
@@ -237,7 +240,7 @@ class UpdateParametricRepresentation(bpy.types.Operator):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
return context.active_object.mode == "OBJECT"
|
return context.active_object and context.active_object.mode == "OBJECT"
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
self.file = IfcStore.get_file()
|
self.file = IfcStore.get_file()
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
import bpy
|
import bpy
|
||||||
from bpy.types import Panel
|
from bpy.types import Panel
|
||||||
from blenderbim.bim.ifc import IfcStore
|
from blenderbim.bim.ifc import IfcStore
|
||||||
|
from blenderbim.bim.helper import prop_with_search
|
||||||
from blenderbim.bim.module.geometry.data import RepresentationsData, DerivedPlacementsData
|
from blenderbim.bim.module.geometry.data import RepresentationsData, DerivedPlacementsData
|
||||||
|
|
||||||
|
|
||||||
@@ -50,7 +51,7 @@ class BIM_PT_representations(Panel):
|
|||||||
layout.label(text="No representations found")
|
layout.label(text="No representations found")
|
||||||
|
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
row.prop(context.scene.BIMRootProperties, "contexts", text="")
|
prop_with_search(row, context.scene.BIMRootProperties, "contexts", text="")
|
||||||
row.operator("bim.add_representation", icon="ADD", text="")
|
row.operator("bim.add_representation", icon="ADD", text="")
|
||||||
|
|
||||||
for representation in RepresentationsData.data["representations"]:
|
for representation in RepresentationsData.data["representations"]:
|
||||||
|
|||||||
@@ -21,8 +21,10 @@ from . import ui, prop, operator
|
|||||||
|
|
||||||
classes = (
|
classes = (
|
||||||
operator.LoadGroups,
|
operator.LoadGroups,
|
||||||
|
operator.ToggleGroup,
|
||||||
operator.DisableGroupEditingUI,
|
operator.DisableGroupEditingUI,
|
||||||
operator.AddGroup,
|
operator.AddGroup,
|
||||||
|
operator.AddGroupToGroup,
|
||||||
operator.EditGroup,
|
operator.EditGroup,
|
||||||
operator.RemoveGroup,
|
operator.RemoveGroup,
|
||||||
operator.ToggleAssigningGroup,
|
operator.ToggleAssigningGroup,
|
||||||
@@ -31,6 +33,8 @@ classes = (
|
|||||||
operator.EnableEditingGroup,
|
operator.EnableEditingGroup,
|
||||||
operator.DisableEditingGroup,
|
operator.DisableEditingGroup,
|
||||||
operator.SelectGroupProducts,
|
operator.SelectGroupProducts,
|
||||||
|
operator.UpdateGroup,
|
||||||
|
prop.ExpandedGroups,
|
||||||
prop.Group,
|
prop.Group,
|
||||||
prop.BIMGroupProperties,
|
prop.BIMGroupProperties,
|
||||||
ui.BIM_PT_groups,
|
ui.BIM_PT_groups,
|
||||||
@@ -42,7 +46,9 @@ classes = (
|
|||||||
|
|
||||||
def register():
|
def register():
|
||||||
bpy.types.Scene.BIMGroupProperties = bpy.props.PointerProperty(type=prop.BIMGroupProperties)
|
bpy.types.Scene.BIMGroupProperties = bpy.props.PointerProperty(type=prop.BIMGroupProperties)
|
||||||
|
bpy.types.Scene.ExpandedGroups = bpy.props.PointerProperty(type=prop.ExpandedGroups)
|
||||||
|
|
||||||
|
|
||||||
def unregister():
|
def unregister():
|
||||||
del bpy.types.Scene.BIMGroupProperties
|
del bpy.types.Scene.BIMGroupProperties
|
||||||
|
del bpy.types.Scene.ExpandedGroups
|
||||||
|
|||||||
@@ -22,22 +22,86 @@ import ifcopenshell.api
|
|||||||
import blenderbim.bim.helper
|
import blenderbim.bim.helper
|
||||||
from blenderbim.bim.ifc import IfcStore
|
from blenderbim.bim.ifc import IfcStore
|
||||||
from ifcopenshell.api.group.data import Data
|
from ifcopenshell.api.group.data import Data
|
||||||
|
from ifcopenshell.util.selector import Selector
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
class LoadGroups(bpy.types.Operator):
|
class LoadGroups(bpy.types.Operator):
|
||||||
bl_idname = "bim.load_groups"
|
bl_idname = "bim.load_groups"
|
||||||
bl_label = "Load Groups"
|
bl_label = "Load Groups"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
is_refresh: bpy.props.BoolProperty(default=False)
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
props = context.scene.BIMGroupProperties
|
self.props = context.scene.BIMGroupProperties
|
||||||
props.groups.clear()
|
self.expanded_groups = json.loads(context.scene.ExpandedGroups.json_string)
|
||||||
|
self.props.groups.clear()
|
||||||
|
self.ifc = IfcStore.get_file()
|
||||||
|
if not self.is_refresh:
|
||||||
|
context.scene.ExpandedGroups.json_string = "{}"
|
||||||
|
|
||||||
for ifc_definition_id, group in Data.groups.items():
|
for ifc_definition_id, group in Data.groups.items():
|
||||||
new = props.groups.add()
|
if not group["HasAssignments"]:
|
||||||
new.ifc_definition_id = ifc_definition_id
|
new = self.props.groups.add()
|
||||||
new.name = group["Name"]
|
new.ifc_definition_id = ifc_definition_id
|
||||||
props.is_editing = True
|
new.name = group["Name"]
|
||||||
|
new.selection_query = group["Description"].split("*selector*")[1] if group["Description"] else ""
|
||||||
|
new.tree_depth = 0
|
||||||
|
|
||||||
|
if group["IsGroupedBy"]:
|
||||||
|
# assumes 1:1 cardinality, will need to be updated to reflect IFC4 changes
|
||||||
|
# where the cardinality is 0:? - vulevukusej
|
||||||
|
sub_groups = [g for g in group["IsGroupedBy"][0].RelatedObjects if g.is_a("IfcGroup")]
|
||||||
|
new.has_children = True if len(sub_groups) != 0 else False
|
||||||
|
|
||||||
|
if self.is_refresh:
|
||||||
|
self.recursively_load_sub_groups(ifc_definition_id, new)
|
||||||
|
|
||||||
|
self.props.is_editing = True
|
||||||
bpy.ops.bim.disable_editing_group()
|
bpy.ops.bim.disable_editing_group()
|
||||||
|
Data.load(IfcStore.get_file())
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
def recursively_load_sub_groups(self, ifc_definition_id, parent):
|
||||||
|
sub_groups = [
|
||||||
|
k for k, v in Data.products.items() if self.ifc.by_id(k).is_a("IfcGroup") and ifc_definition_id in v
|
||||||
|
]
|
||||||
|
if str(ifc_definition_id) not in self.expanded_groups or sub_groups == []:
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
parent.is_expanded = True
|
||||||
|
parent.has_children = True
|
||||||
|
for group in sub_groups:
|
||||||
|
new = self.props.groups.add()
|
||||||
|
new.ifc_definition_id = group
|
||||||
|
new.name = Data.groups[group]["Name"]
|
||||||
|
new.selection_query = (
|
||||||
|
Data.groups[group]["Description"].split("*selector*")[1]
|
||||||
|
if Data.groups[group]["Description"]
|
||||||
|
else ""
|
||||||
|
)
|
||||||
|
new.has_children = True if len(Data.groups[group]["IsGroupedBy"]) != 0 else False
|
||||||
|
new.tree_depth = parent.tree_depth + 1
|
||||||
|
self.recursively_load_sub_groups(new.ifc_definition_id, new)
|
||||||
|
|
||||||
|
|
||||||
|
class ToggleGroup(bpy.types.Operator):
|
||||||
|
bl_idname = "bim.toggle_group"
|
||||||
|
bl_label = "Toggle Group"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
ifc_definition_id: bpy.props.IntProperty()
|
||||||
|
index: bpy.props.IntProperty()
|
||||||
|
option: bpy.props.StringProperty(name="Expand or Collapse")
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
context.scene.BIMGroupProperties.groups[self.index].is_expanded = True if self.option == "Expand" else False
|
||||||
|
json_string = json.loads(context.scene.ExpandedGroups.json_string)
|
||||||
|
if self.option == "Expand":
|
||||||
|
json_string.setdefault(str(self.ifc_definition_id), None)
|
||||||
|
else:
|
||||||
|
json_string.pop(str(self.ifc_definition_id), None)
|
||||||
|
context.scene.ExpandedGroups.json_string = json.dumps(json_string)
|
||||||
|
bpy.ops.bim.load_groups(is_refresh=True)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
@@ -54,7 +118,7 @@ class DisableGroupEditingUI(bpy.types.Operator):
|
|||||||
|
|
||||||
class AddGroup(bpy.types.Operator):
|
class AddGroup(bpy.types.Operator):
|
||||||
bl_idname = "bim.add_group"
|
bl_idname = "bim.add_group"
|
||||||
bl_label = "Add Group"
|
bl_label = "Add New Group"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
@@ -63,15 +127,38 @@ class AddGroup(bpy.types.Operator):
|
|||||||
def _execute(self, context):
|
def _execute(self, context):
|
||||||
result = ifcopenshell.api.run("group.add_group", IfcStore.get_file())
|
result = ifcopenshell.api.run("group.add_group", IfcStore.get_file())
|
||||||
Data.load(IfcStore.get_file())
|
Data.load(IfcStore.get_file())
|
||||||
bpy.ops.bim.load_groups()
|
|
||||||
|
bpy.ops.bim.load_groups(is_refresh=True)
|
||||||
bpy.ops.bim.enable_editing_group(group=result.id())
|
bpy.ops.bim.enable_editing_group(group=result.id())
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
class AddGroupToGroup(bpy.types.Operator):
|
||||||
|
bl_idname = "bim.add_group_to_group"
|
||||||
|
bl_label = "Add Group to Group"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
group: bpy.props.IntProperty(name="Group ID")
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
return IfcStore.execute_ifc_operator(self, context)
|
||||||
|
|
||||||
|
def _execute(self, context):
|
||||||
|
self.file = IfcStore.get_file()
|
||||||
|
result = ifcopenshell.api.run("group.add_group", self.file)
|
||||||
|
ifcopenshell.api.run(
|
||||||
|
"group.assign_group", IfcStore.get_file(), **{"product": [result], "group": self.file.by_id(self.group)}
|
||||||
|
)
|
||||||
|
Data.load(IfcStore.get_file())
|
||||||
|
bpy.ops.bim.load_groups(is_refresh=True)
|
||||||
|
bpy.ops.bim.disable_group_editing_ui()
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
class EditGroup(bpy.types.Operator):
|
class EditGroup(bpy.types.Operator):
|
||||||
bl_idname = "bim.edit_group"
|
bl_idname = "bim.edit_group"
|
||||||
bl_label = "Edit Group"
|
bl_label = "Edit Group"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
copy_from_selector: bpy.props.BoolProperty(name="Copy from Selector", default=False)
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
return IfcStore.execute_ifc_operator(self, context)
|
return IfcStore.execute_ifc_operator(self, context)
|
||||||
@@ -84,12 +171,16 @@ class EditGroup(bpy.types.Operator):
|
|||||||
attributes[attribute.name] = None
|
attributes[attribute.name] = None
|
||||||
else:
|
else:
|
||||||
attributes[attribute.name] = attribute.string_value
|
attributes[attribute.name] = attribute.string_value
|
||||||
|
|
||||||
|
if self.copy_from_selector:
|
||||||
|
attributes["Description"] = context.scene.IFCSelector.selection_query
|
||||||
|
|
||||||
self.file = IfcStore.get_file()
|
self.file = IfcStore.get_file()
|
||||||
ifcopenshell.api.run(
|
ifcopenshell.api.run(
|
||||||
"group.edit_group", self.file, **{"group": self.file.by_id(props.active_group_id), "attributes": attributes}
|
"group.edit_group", self.file, **{"group": self.file.by_id(props.active_group_id), "attributes": attributes}
|
||||||
)
|
)
|
||||||
Data.load(IfcStore.get_file())
|
Data.load(IfcStore.get_file())
|
||||||
bpy.ops.bim.load_groups()
|
bpy.ops.bim.load_groups(is_refresh=True)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
@@ -107,7 +198,7 @@ class RemoveGroup(bpy.types.Operator):
|
|||||||
self.file = IfcStore.get_file()
|
self.file = IfcStore.get_file()
|
||||||
ifcopenshell.api.run("group.remove_group", self.file, **{"group": self.file.by_id(self.group)})
|
ifcopenshell.api.run("group.remove_group", self.file, **{"group": self.file.by_id(self.group)})
|
||||||
Data.load(IfcStore.get_file())
|
Data.load(IfcStore.get_file())
|
||||||
bpy.ops.bim.load_groups()
|
bpy.ops.bim.load_groups(is_refresh=True)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
@@ -120,9 +211,7 @@ class EnableEditingGroup(bpy.types.Operator):
|
|||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
props = context.scene.BIMGroupProperties
|
props = context.scene.BIMGroupProperties
|
||||||
props.group_attributes.clear()
|
props.group_attributes.clear()
|
||||||
|
|
||||||
blenderbim.bim.helper.import_attributes("IfcGroup", props.group_attributes, Data.groups[self.group])
|
blenderbim.bim.helper.import_attributes("IfcGroup", props.group_attributes, Data.groups[self.group])
|
||||||
|
|
||||||
props.active_group_id = self.group
|
props.active_group_id = self.group
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
@@ -167,7 +256,7 @@ class AssignGroup(bpy.types.Operator):
|
|||||||
"group.assign_group",
|
"group.assign_group",
|
||||||
self.file,
|
self.file,
|
||||||
**{
|
**{
|
||||||
"product": self.file.by_id(product.BIMObjectProperties.ifc_definition_id),
|
"product": [self.file.by_id(product.BIMObjectProperties.ifc_definition_id)],
|
||||||
"group": self.file.by_id(self.group),
|
"group": self.file.by_id(self.group),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -219,3 +308,33 @@ class SelectGroupProducts(bpy.types.Operator):
|
|||||||
if self.group in product_groups:
|
if self.group in product_groups:
|
||||||
obj.select_set(True)
|
obj.select_set(True)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateGroup(bpy.types.Operator):
|
||||||
|
bl_idname = "bim.update_group"
|
||||||
|
bl_label = "Update Group"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
query: bpy.props.StringProperty()
|
||||||
|
group_id: bpy.props.IntProperty()
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
return IfcStore.execute_ifc_operator(self, context)
|
||||||
|
|
||||||
|
def _execute(self, context):
|
||||||
|
self.file = IfcStore.get_file()
|
||||||
|
group = self.file.by_id(self.group_id)
|
||||||
|
query = self.query
|
||||||
|
|
||||||
|
new_products = Selector.parse(self.file, query)
|
||||||
|
ifcopenshell.api.run(
|
||||||
|
"group.update_group_products",
|
||||||
|
self.file,
|
||||||
|
**{
|
||||||
|
"group": group,
|
||||||
|
"products": new_products,
|
||||||
|
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Data.load(IfcStore.get_file())
|
||||||
|
bpy.ops.bim.load_groups(is_refresh=True)
|
||||||
|
return {"FINISHED"}
|
||||||
|
|||||||
@@ -18,7 +18,10 @@
|
|||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
from blenderbim.bim.prop import StrProperty, Attribute
|
from blenderbim.bim.prop import StrProperty, Attribute
|
||||||
|
from blenderbim.bim.helper import import_attributes
|
||||||
|
from ifcopenshell.api.group.data import Data
|
||||||
from bpy.types import PropertyGroup
|
from bpy.types import PropertyGroup
|
||||||
|
import json
|
||||||
from bpy.props import (
|
from bpy.props import (
|
||||||
PointerProperty,
|
PointerProperty,
|
||||||
StringProperty,
|
StringProperty,
|
||||||
@@ -31,9 +34,17 @@ from bpy.props import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ExpandedGroups(StrProperty):
|
||||||
|
json_string: StringProperty(name="JSON String", default="{}")
|
||||||
|
|
||||||
|
|
||||||
class Group(PropertyGroup):
|
class Group(PropertyGroup):
|
||||||
name: StringProperty(name="Name")
|
name: StringProperty(name="Name")
|
||||||
ifc_definition_id: IntProperty(name="IFC Definition ID")
|
ifc_definition_id: IntProperty(name="IFC Definition ID")
|
||||||
|
selection_query: StringProperty(name="Selection Query")
|
||||||
|
is_expanded: BoolProperty(name="Is Expanded", default=False)
|
||||||
|
has_children: BoolProperty(name="Has Children", default=False)
|
||||||
|
tree_depth: IntProperty(name="Tree Depth")
|
||||||
|
|
||||||
|
|
||||||
class BIMGroupProperties(PropertyGroup):
|
class BIMGroupProperties(PropertyGroup):
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class BIM_PT_groups(Panel):
|
|||||||
self.props = context.scene.BIMGroupProperties
|
self.props = context.scene.BIMGroupProperties
|
||||||
|
|
||||||
row = self.layout.row(align=True)
|
row = self.layout.row(align=True)
|
||||||
row.label(text="{} Groups Found".format(len(Data.groups)), icon="OUTLINER")
|
row.label(text=f"{len(Data.groups)} Groups Found", icon="OUTLINER")
|
||||||
if self.props.is_editing:
|
if self.props.is_editing:
|
||||||
row.operator("bim.add_group", text="", icon="ADD")
|
row.operator("bim.add_group", text="", icon="ADD")
|
||||||
row.operator("bim.disable_group_editing_ui", text="", icon="CANCEL")
|
row.operator("bim.disable_group_editing_ui", text="", icon="CANCEL")
|
||||||
@@ -115,10 +115,22 @@ class BIM_PT_object_groups(Panel):
|
|||||||
|
|
||||||
|
|
||||||
class BIM_UL_groups(UIList):
|
class BIM_UL_groups(UIList):
|
||||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
|
||||||
if item:
|
if item:
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
row.label(text=item.name)
|
for i in range(0, item.tree_depth):
|
||||||
|
row.label(text="", icon="BLANK1")
|
||||||
|
if item.has_children:
|
||||||
|
op = row.operator(
|
||||||
|
"bim.toggle_group", icon="TRIA_DOWN" if item.is_expanded else "TRIA_RIGHT", text="", emboss=False
|
||||||
|
)
|
||||||
|
op.ifc_definition_id = item.ifc_definition_id
|
||||||
|
op.index = index
|
||||||
|
op.option = "Collapse" if item.is_expanded else "Expand"
|
||||||
|
else:
|
||||||
|
row.label(text="", icon="BLANK1")
|
||||||
|
|
||||||
|
row.label(text=f"*{item.name}") if item.selection_query != "" else row.label(text=item.name)
|
||||||
group_id = item.ifc_definition_id
|
group_id = item.ifc_definition_id
|
||||||
if context.scene.BIMGroupProperties.active_group_id == group_id:
|
if context.scene.BIMGroupProperties.active_group_id == group_id:
|
||||||
op = row.operator("bim.select_group_products", text="", icon="RESTRICT_SELECT_OFF")
|
op = row.operator("bim.select_group_products", text="", icon="RESTRICT_SELECT_OFF")
|
||||||
@@ -128,21 +140,46 @@ class BIM_UL_groups(UIList):
|
|||||||
elif context.scene.BIMGroupProperties.active_group_id:
|
elif context.scene.BIMGroupProperties.active_group_id:
|
||||||
op = row.operator("bim.select_group_products", text="", icon="RESTRICT_SELECT_OFF")
|
op = row.operator("bim.select_group_products", text="", icon="RESTRICT_SELECT_OFF")
|
||||||
op.group = group_id
|
op.group = group_id
|
||||||
|
op = row.operator("bim.add_group_to_group", text="", icon="ADD")
|
||||||
|
op.group = group_id
|
||||||
op = row.operator("bim.remove_group", text="", icon="X")
|
op = row.operator("bim.remove_group", text="", icon="X")
|
||||||
op.group = group_id
|
op.group = group_id
|
||||||
|
if item.selection_query != "":
|
||||||
|
op = row.operator("bim.update_group", text="", icon="FILE_REFRESH")
|
||||||
|
op.group_id = item.ifc_definition_id
|
||||||
|
op.query = item.selection_query
|
||||||
else:
|
else:
|
||||||
op = row.operator("bim.select_group_products", text="", icon="RESTRICT_SELECT_OFF")
|
op = row.operator("bim.select_group_products", text="", icon="RESTRICT_SELECT_OFF")
|
||||||
op.group = group_id
|
op.group = group_id
|
||||||
op = row.operator("bim.enable_editing_group", text="", icon="GREASEPENCIL")
|
op = row.operator("bim.enable_editing_group", text="", icon="GREASEPENCIL")
|
||||||
op.group = group_id
|
op.group = group_id
|
||||||
|
op = row.operator("bim.add_group_to_group", text="", icon="ADD")
|
||||||
|
op.group = group_id
|
||||||
op = row.operator("bim.remove_group", text="", icon="X")
|
op = row.operator("bim.remove_group", text="", icon="X")
|
||||||
op.group = group_id
|
op.group = group_id
|
||||||
|
if item.selection_query != "":
|
||||||
|
op = row.operator("bim.update_group", text="", icon="FILE_REFRESH")
|
||||||
|
op.group_id = item.ifc_definition_id
|
||||||
|
op.query = item.selection_query
|
||||||
|
|
||||||
|
|
||||||
class BIM_UL_object_groups(UIList):
|
class BIM_UL_object_groups(UIList):
|
||||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
|
||||||
if item:
|
if item:
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
|
for i in range(0, item.tree_depth):
|
||||||
|
row.label(text="", icon="BLANK1")
|
||||||
|
if item.has_children:
|
||||||
|
op = row.operator(
|
||||||
|
"bim.toggle_group", icon="TRIA_DOWN" if item.is_expanded else "TRIA_RIGHT", text="", emboss=False
|
||||||
|
)
|
||||||
|
op.ifc_definition_id = item.ifc_definition_id
|
||||||
|
op.index = index
|
||||||
|
op.option = "Collapse" if item.is_expanded else "Expand"
|
||||||
|
else:
|
||||||
|
row.label(text="", icon="BLANK1")
|
||||||
row.label(text=item.name)
|
row.label(text=item.name)
|
||||||
|
op = row.operator("bim.remove_group", text="", icon="X")
|
||||||
|
op.group = item.ifc_definition_id
|
||||||
op = row.operator("bim.assign_group", text="", icon="ADD")
|
op = row.operator("bim.assign_group", text="", icon="ADD")
|
||||||
op.group = item.ifc_definition_id
|
op.group = item.ifc_definition_id
|
||||||
|
|||||||
@@ -84,7 +84,9 @@ class ObjectMaterialData:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def materials(cls):
|
def materials(cls):
|
||||||
return sorted([(str(m.id()), m.Name or "Unnamed", "") for m in tool.Ifc.get().by_type("IfcMaterial")], key=lambda x: x[1])
|
return sorted(
|
||||||
|
[(str(m.id()), m.Name or "Unnamed", "") for m in tool.Ifc.get().by_type("IfcMaterial")], key=lambda x: x[1]
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def type_material(cls):
|
def type_material(cls):
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ from ifcopenshell.api.material.data import Data
|
|||||||
from ifcopenshell.api.profile.data import Data as ProfileData
|
from ifcopenshell.api.profile.data import Data as ProfileData
|
||||||
from blenderbim.bim.ifc import IfcStore
|
from blenderbim.bim.ifc import IfcStore
|
||||||
from blenderbim.bim.helper import draw_attributes
|
from blenderbim.bim.helper import draw_attributes
|
||||||
|
from blenderbim.bim.helper import prop_with_search
|
||||||
from blenderbim.bim.module.material.data import MaterialsData, ObjectMaterialData
|
from blenderbim.bim.module.material.data import MaterialsData, ObjectMaterialData
|
||||||
|
|
||||||
|
|
||||||
@@ -50,7 +51,7 @@ class BIM_PT_materials(Panel):
|
|||||||
row.operator("bim.disable_editing_materials", text="", icon="CANCEL")
|
row.operator("bim.disable_editing_materials", text="", icon="CANCEL")
|
||||||
else:
|
else:
|
||||||
row = self.layout.row(align=True)
|
row = self.layout.row(align=True)
|
||||||
row.prop(self.props, "material_type", text="")
|
prop_with_search(row, self.props, "material_type", text="")
|
||||||
row.operator("bim.load_materials", text="", icon="IMPORT")
|
row.operator("bim.load_materials", text="", icon="IMPORT")
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -188,9 +189,9 @@ class BIM_PT_object_material(Panel):
|
|||||||
return self.draw_material_ui()
|
return self.draw_material_ui()
|
||||||
|
|
||||||
row = self.layout.row(align=True)
|
row = self.layout.row(align=True)
|
||||||
row.prop(self.props, "material_type", text="")
|
prop_with_search(row, self.props, "material_type", text="")
|
||||||
if self.props.material_type == "IfcMaterial" or self.props.material_type == "IfcMaterialList":
|
if self.props.material_type == "IfcMaterial" or self.props.material_type == "IfcMaterialList":
|
||||||
row.prop(self.props, "material", text="")
|
prop_with_search(row, self.props, "material", text="")
|
||||||
row.operator("bim.assign_material", icon="ADD", text="")
|
row.operator("bim.assign_material", icon="ADD", text="")
|
||||||
|
|
||||||
def draw_material_ui(self):
|
def draw_material_ui(self):
|
||||||
@@ -220,8 +221,7 @@ class BIM_PT_object_material(Panel):
|
|||||||
return self.draw_read_only_single_ui()
|
return self.draw_read_only_single_ui()
|
||||||
|
|
||||||
def draw_editable_single_ui(self):
|
def draw_editable_single_ui(self):
|
||||||
row = self.layout.row(align=True)
|
prop_with_search(self.layout, self.props, "material", text="")
|
||||||
row.prop(self.props, "material", text="")
|
|
||||||
|
|
||||||
def draw_read_only_single_ui(self):
|
def draw_read_only_single_ui(self):
|
||||||
material = Data.materials[self.product_data["id"]]
|
material = Data.materials[self.product_data["id"]]
|
||||||
@@ -242,7 +242,7 @@ class BIM_PT_object_material(Panel):
|
|||||||
row.prop(attribute, "string_value", text=attribute.name)
|
row.prop(attribute, "string_value", text=attribute.name)
|
||||||
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
|
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
|
||||||
row = self.layout.row(align=True)
|
row = self.layout.row(align=True)
|
||||||
row.prop(self.props, "material", text="")
|
prop_with_search(row, self.props, "material", text="")
|
||||||
|
|
||||||
op = row.operator(f"bim.add_{self.set_item_name}", icon="ADD", text="")
|
op = row.operator(f"bim.add_{self.set_item_name}", icon="ADD", text="")
|
||||||
setattr(op, f"{self.set_item_name}_set", self.material_set_id)
|
setattr(op, f"{self.set_item_name}_set", self.material_set_id)
|
||||||
|
|||||||
@@ -17,11 +17,12 @@
|
|||||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
from . import handler, prop, ui, grid, product, wall, slab, stair, opening, pie, workspace
|
from . import handler, prop, ui, grid, product, wall, slab, stair, opening, pie, workspace, profile
|
||||||
|
|
||||||
classes = (
|
classes = (
|
||||||
product.AddEmptyType,
|
product.AddEmptyType,
|
||||||
product.AddTypeInstance,
|
product.AddConstrTypeInstance,
|
||||||
|
product.DisplayConstrTypes,
|
||||||
product.AlignProduct,
|
product.AlignProduct,
|
||||||
product.DynamicallyVoidProduct,
|
product.DynamicallyVoidProduct,
|
||||||
workspace.Hotkey,
|
workspace.Hotkey,
|
||||||
@@ -32,8 +33,10 @@ classes = (
|
|||||||
opening.AddElementOpening,
|
opening.AddElementOpening,
|
||||||
profile.ExtendProfile,
|
profile.ExtendProfile,
|
||||||
prop.BIMModelProperties,
|
prop.BIMModelProperties,
|
||||||
|
prop.ConstrTypeInfo,
|
||||||
ui.BIM_PT_authoring,
|
ui.BIM_PT_authoring,
|
||||||
ui.BIM_PT_authoring_architectural,
|
ui.DisplayConstrTypesUI,
|
||||||
|
ui.HelpConstrTypes,
|
||||||
grid.BIM_OT_add_object,
|
grid.BIM_OT_add_object,
|
||||||
stair.BIM_OT_add_object,
|
stair.BIM_OT_add_object,
|
||||||
opening.BIM_OT_add_object,
|
opening.BIM_OT_add_object,
|
||||||
@@ -51,6 +54,7 @@ def register():
|
|||||||
if not bpy.app.background:
|
if not bpy.app.background:
|
||||||
bpy.utils.register_tool(workspace.BimTool, after={"builtin.scale_cage"}, separator=True, group=True)
|
bpy.utils.register_tool(workspace.BimTool, after={"builtin.scale_cage"}, separator=True, group=True)
|
||||||
bpy.types.Scene.BIMModelProperties = bpy.props.PointerProperty(type=prop.BIMModelProperties)
|
bpy.types.Scene.BIMModelProperties = bpy.props.PointerProperty(type=prop.BIMModelProperties)
|
||||||
|
bpy.types.Scene.ConstrTypeInfo = bpy.props.CollectionProperty(type=prop.ConstrTypeInfo)
|
||||||
bpy.types.VIEW3D_MT_mesh_add.append(grid.add_object_button)
|
bpy.types.VIEW3D_MT_mesh_add.append(grid.add_object_button)
|
||||||
bpy.types.VIEW3D_MT_mesh_add.append(stair.add_object_button)
|
bpy.types.VIEW3D_MT_mesh_add.append(stair.add_object_button)
|
||||||
bpy.types.VIEW3D_MT_mesh_add.append(opening.add_object_button)
|
bpy.types.VIEW3D_MT_mesh_add.append(opening.add_object_button)
|
||||||
@@ -68,6 +72,7 @@ def unregister():
|
|||||||
if not bpy.app.background:
|
if not bpy.app.background:
|
||||||
bpy.utils.unregister_tool(workspace.BimTool)
|
bpy.utils.unregister_tool(workspace.BimTool)
|
||||||
del bpy.types.Scene.BIMModelProperties
|
del bpy.types.Scene.BIMModelProperties
|
||||||
|
del bpy.types.Scene.ConstrTypeInfo
|
||||||
bpy.app.handlers.load_post.remove(handler.load_post)
|
bpy.app.handlers.load_post.remove(handler.load_post)
|
||||||
bpy.types.VIEW3D_MT_mesh_add.remove(grid.add_object_button)
|
bpy.types.VIEW3D_MT_mesh_add.remove(grid.add_object_button)
|
||||||
bpy.types.VIEW3D_MT_mesh_add.remove(stair.add_object_button)
|
bpy.types.VIEW3D_MT_mesh_add.remove(stair.add_object_button)
|
||||||
|
|||||||
@@ -16,8 +16,14 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import functools
|
||||||
import bpy
|
import bpy
|
||||||
import blenderbim.tool as tool
|
import blenderbim.tool as tool
|
||||||
|
from blenderbim.bim.ifc import IfcStore
|
||||||
|
|
||||||
|
|
||||||
|
preview_icon_ids = {}
|
||||||
|
attempts = 0
|
||||||
|
|
||||||
|
|
||||||
def refresh():
|
def refresh():
|
||||||
@@ -31,10 +37,29 @@ class AuthoringData:
|
|||||||
@classmethod
|
@classmethod
|
||||||
def load(cls):
|
def load(cls):
|
||||||
cls.is_loaded = True
|
cls.is_loaded = True
|
||||||
cls.data = {
|
if not hasattr(cls, "data"):
|
||||||
"ifc_classes": cls.ifc_classes(),
|
cls.data = {}
|
||||||
"relating_types": cls.relating_types(),
|
cls.props = bpy.context.scene.BIMModelProperties
|
||||||
}
|
cls.load_ifc_classes()
|
||||||
|
cls.load_relating_types()
|
||||||
|
cls.load_relating_types_browser()
|
||||||
|
cls.load_preview_constr_types()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def load_ifc_classes(cls):
|
||||||
|
cls.data["ifc_classes"] = cls.ifc_classes()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def load_relating_types(cls):
|
||||||
|
cls.data["relating_types_ids"] = cls.relating_types()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def load_relating_types_browser(cls):
|
||||||
|
cls.data["relating_types_browser_ids"] = cls.relating_types_browser()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def load_preview_constr_types(cls):
|
||||||
|
cls.data["preview_constr_types"] = preview_icon_ids
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def ifc_classes(cls):
|
def ifc_classes(cls):
|
||||||
@@ -49,16 +74,123 @@ class AuthoringData:
|
|||||||
return results
|
return results
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def relating_types(cls):
|
def constr_class_entities(cls, ifc_class=None):
|
||||||
ifc_classes = cls.ifc_classes()
|
ifc_classes = cls.data["ifc_classes"]
|
||||||
if not ifc_classes:
|
if not ifc_classes:
|
||||||
return []
|
return []
|
||||||
results = []
|
results = []
|
||||||
ifc_class = bpy.context.scene.BIMModelProperties.ifc_class
|
if ifc_class is None:
|
||||||
|
ifc_class = cls.props.ifc_class
|
||||||
if not ifc_class and ifc_classes:
|
if not ifc_class and ifc_classes:
|
||||||
ifc_class = ifc_classes[0][0]
|
ifc_class = ifc_classes[0][0]
|
||||||
if ifc_class:
|
if ifc_class:
|
||||||
elements = [(str(e.id()), e.Name, e.Description or "") for e in tool.Ifc.get().by_type(ifc_class)]
|
elements = sorted(tool.Ifc.get().by_type(ifc_class), key=lambda s: s.Name)
|
||||||
results.extend(sorted(elements, key=lambda s: s[1]))
|
results.extend(elements)
|
||||||
return results
|
return results
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def relating_types(cls, ifc_class=None):
|
||||||
|
return [(str(e.id()), e.Name, e.Description or "") for e in cls.constr_class_entities(ifc_class=ifc_class)]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def relating_types_browser(cls):
|
||||||
|
return cls.relating_types(ifc_class=cls.props.ifc_class_browser)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def new_relating_type_info(ifc_class):
|
||||||
|
relating_type_info = bpy.context.scene.ConstrTypeInfo.add()
|
||||||
|
relating_type_info.name = ifc_class
|
||||||
|
return relating_type_info
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def assetize_constr_class(cls, ifc_class=None):
|
||||||
|
if ifc_class is None:
|
||||||
|
ifc_class = cls.props.ifc_class
|
||||||
|
relating_type_info = cls.relating_type_info(ifc_class)
|
||||||
|
_ = cls.new_relating_type_info(ifc_class) if relating_type_info is None else relating_type_info
|
||||||
|
constr_class_occurrences = cls.constr_class_entities(ifc_class)
|
||||||
|
preview_constr_types = cls.data["preview_constr_types"]
|
||||||
|
for constr_class_entity in constr_class_occurrences:
|
||||||
|
|
||||||
|
### handle asset regeneration when library entity is updated ¿?
|
||||||
|
if (
|
||||||
|
ifc_class not in preview_constr_types
|
||||||
|
or str(constr_class_entity.id()) not in preview_constr_types[ifc_class]
|
||||||
|
):
|
||||||
|
obj = tool.Ifc.get_object(constr_class_entity)
|
||||||
|
cls.assetize_object(obj, ifc_class, constr_class_entity)
|
||||||
|
relating_type_info = cls.relating_type_info(ifc_class)
|
||||||
|
relating_type_info.fully_loaded = True
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def assetize_object(cls, obj, ifc_class, ifc_class_entity, from_selection=False):
|
||||||
|
relating_type_id = ifc_class_entity.id()
|
||||||
|
to_be_deleted = False
|
||||||
|
if obj.type == "EMPTY":
|
||||||
|
kwargs = {}
|
||||||
|
if not from_selection:
|
||||||
|
kwargs.update({"ifc_class": ifc_class, "relating_type_id": relating_type_id})
|
||||||
|
new_obj = cls.new_relating_type(**kwargs)
|
||||||
|
if new_obj is not None:
|
||||||
|
to_be_deleted = True
|
||||||
|
obj = new_obj
|
||||||
|
obj.asset_mark()
|
||||||
|
obj.asset_generate_preview()
|
||||||
|
icon_id = obj.preview.icon_id
|
||||||
|
if ifc_class not in cls.data["preview_constr_types"]:
|
||||||
|
cls.data["preview_constr_types"][ifc_class] = {}
|
||||||
|
cls.data["preview_constr_types"][ifc_class][str(relating_type_id)] = {"icon_id": icon_id, "object": obj}
|
||||||
|
if to_be_deleted:
|
||||||
|
for col in obj.users_collection:
|
||||||
|
col.objects.unlink(obj)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def assetize_relating_type_from_selection(cls, browser=False):
|
||||||
|
ifc_class = cls.props.ifc_class_browser if browser else cls.props.ifc_class
|
||||||
|
relating_type_id = cls.props.relating_type_id_browser if browser else cls.props.relating_type_id
|
||||||
|
constr_class_occurrences = cls.constr_class_entities(ifc_class=ifc_class)
|
||||||
|
constr_class_occurrences = [
|
||||||
|
entity for entity in constr_class_occurrences if entity.id() == int(relating_type_id)
|
||||||
|
]
|
||||||
|
if len(constr_class_occurrences) == 0:
|
||||||
|
return False
|
||||||
|
constr_class_entity = constr_class_occurrences[0]
|
||||||
|
obj = tool.Ifc.get_object(constr_class_entity)
|
||||||
|
if obj is None:
|
||||||
|
return False
|
||||||
|
cls.assetize_object(obj, ifc_class, constr_class_entity, from_selection=True)
|
||||||
|
return True
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def relating_type_info(ifc_class):
|
||||||
|
relating_type_infos = [element for element in bpy.context.scene.ConstrTypeInfo if element.name == ifc_class]
|
||||||
|
return None if len(relating_type_infos) == 0 else relating_type_infos[0]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def new_relating_type(cls, ifc_class=None, relating_type_id=None):
|
||||||
|
if ifc_class is None:
|
||||||
|
bpy.ops.bim.add_constr_type_instance(
|
||||||
|
ifc_class=cls.props.ifc_class, relating_type_id=int(cls.props.relating_type_id)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
cls.props.updating = True
|
||||||
|
cls.props.ifc_class = ifc_class
|
||||||
|
cls.props.relating_type_id = str(relating_type_id)
|
||||||
|
cls.props.updating = False
|
||||||
|
bpy.ops.bim.add_constr_type_instance()
|
||||||
|
return bpy.context.selected_objects[-1]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def relating_type_name_by_id(ifc_class, relating_type_id):
|
||||||
|
file = IfcStore.get_file()
|
||||||
|
try:
|
||||||
|
constr_class_entity = file.by_id(int(relating_type_id))
|
||||||
|
except (RuntimeError, ValueError):
|
||||||
|
return None
|
||||||
|
return constr_class_entity.Name if constr_class_entity.is_a() == ifc_class else None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def relating_type_id_by_name(cls, ifc_class, relating_type):
|
||||||
|
relating_types = [ct[0] for ct in cls.relating_types(ifc_class=ifc_class) if ct[1] == relating_type]
|
||||||
|
return None if len(relating_types) == 0 else relating_types[0]
|
||||||
|
|||||||
@@ -65,14 +65,14 @@ class MepGenerator:
|
|||||||
|
|
||||||
def create_rectangle_segment(self):
|
def create_rectangle_segment(self):
|
||||||
verts = [
|
verts = [
|
||||||
Vector((- self.width / 2, self.height / 2, 0)),
|
Vector((-self.width / 2, self.height / 2, 0)),
|
||||||
Vector((- self.width / 2, - self.height / 2, 0)),
|
Vector((-self.width / 2, -self.height / 2, 0)),
|
||||||
Vector((- self.width / 2, self.height / 2, self.length)),
|
Vector((-self.width / 2, self.height / 2, self.length)),
|
||||||
Vector((- self.width / 2, - self.height / 2, self.length)),
|
Vector((-self.width / 2, -self.height / 2, self.length)),
|
||||||
Vector((self.width / 2, self.height / 2, 0)),
|
Vector((self.width / 2, self.height / 2, 0)),
|
||||||
Vector((self.width / 2, - self.height / 2, 0)),
|
Vector((self.width / 2, -self.height / 2, 0)),
|
||||||
Vector((self.width / 2, self.height / 2, self.length)),
|
Vector((self.width / 2, self.height / 2, self.length)),
|
||||||
Vector((self.width / 2, - self.height / 2, self.length)),
|
Vector((self.width / 2, -self.height / 2, self.length)),
|
||||||
]
|
]
|
||||||
faces = [
|
faces = [
|
||||||
[1, 3, 2, 0],
|
[1, 3, 2, 0],
|
||||||
@@ -114,5 +114,13 @@ class MepGenerator:
|
|||||||
tool.Ifc.run("system.assign_port", element=element, port=port)
|
tool.Ifc.run("system.assign_port", element=element, port=port)
|
||||||
tool.Ifc.run("geometry.edit_object_placement", product=port, matrix=obj.matrix_world @ mat, is_si=True)
|
tool.Ifc.run("geometry.edit_object_placement", product=port, matrix=obj.matrix_world @ mat, is_si=True)
|
||||||
|
|
||||||
obj.select_set(True)
|
try:
|
||||||
|
obj.select_set(True)
|
||||||
|
except RuntimeError:
|
||||||
|
def msg(self, context):
|
||||||
|
txt = "The created object could not be assigned to a collection. "
|
||||||
|
txt += "Has any IfcSpatialElement been deleted?"
|
||||||
|
self.layout.label(text=txt)
|
||||||
|
|
||||||
|
bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
|
||||||
return obj
|
return obj
|
||||||
|
|||||||
@@ -29,9 +29,11 @@ import blenderbim.core.type
|
|||||||
import blenderbim.core.geometry
|
import blenderbim.core.geometry
|
||||||
from . import wall, slab, profile, mep
|
from . import wall, slab, profile, mep
|
||||||
from blenderbim.bim.ifc import IfcStore
|
from blenderbim.bim.ifc import IfcStore
|
||||||
|
from blenderbim.bim.module.model.data import AuthoringData
|
||||||
from ifcopenshell.api.pset.data import Data as PsetData
|
from ifcopenshell.api.pset.data import Data as PsetData
|
||||||
from mathutils import Vector, Matrix
|
from mathutils import Vector, Matrix
|
||||||
from bpy_extras.object_utils import AddObjectHelper
|
from bpy_extras.object_utils import AddObjectHelper
|
||||||
|
from . import prop
|
||||||
|
|
||||||
|
|
||||||
class AddEmptyType(bpy.types.Operator, AddObjectHelper):
|
class AddEmptyType(bpy.types.Operator, AddObjectHelper):
|
||||||
@@ -53,13 +55,17 @@ def add_empty_type_button(self, context):
|
|||||||
self.layout.operator(AddEmptyType.bl_idname, icon="FILE_3D")
|
self.layout.operator(AddEmptyType.bl_idname, icon="FILE_3D")
|
||||||
|
|
||||||
|
|
||||||
class AddTypeInstance(bpy.types.Operator):
|
class AddConstrTypeInstance(bpy.types.Operator):
|
||||||
bl_idname = "bim.add_type_instance"
|
bl_idname = "bim.add_constr_type_instance"
|
||||||
bl_label = "Add Type Instance"
|
bl_label = "Add"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
bl_description = "Add the selected Type Instance to the model"
|
bl_description = "Add Type Instance to the model"
|
||||||
ifc_class: bpy.props.StringProperty()
|
ifc_class: bpy.props.StringProperty()
|
||||||
relating_type: bpy.props.IntProperty()
|
relating_type_id: bpy.props.IntProperty()
|
||||||
|
from_invoke: bpy.props.BoolProperty(default=False)
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
return self.execute(context)
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
return IfcStore.execute_ifc_operator(self, context)
|
return IfcStore.execute_ifc_operator(self, context)
|
||||||
@@ -67,11 +73,15 @@ class AddTypeInstance(bpy.types.Operator):
|
|||||||
def _execute(self, context):
|
def _execute(self, context):
|
||||||
props = context.scene.BIMModelProperties
|
props = context.scene.BIMModelProperties
|
||||||
ifc_class = self.ifc_class or props.ifc_class
|
ifc_class = self.ifc_class or props.ifc_class
|
||||||
relating_type_id = self.relating_type or props.relating_type
|
relating_type_id = self.relating_type_id or props.relating_type_id
|
||||||
|
|
||||||
if not ifc_class or not relating_type_id:
|
if not ifc_class or not relating_type_id:
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
if self.from_invoke:
|
||||||
|
props.ifc_class = self.ifc_class
|
||||||
|
props.relating_type_id = str(self.relating_type_id)
|
||||||
|
|
||||||
self.file = IfcStore.get_file()
|
self.file = IfcStore.get_file()
|
||||||
instance_class = ifcopenshell.util.type.get_applicable_entities(ifc_class, self.file.schema)[0]
|
instance_class = ifcopenshell.util.type.get_applicable_entities(ifc_class, self.file.schema)[0]
|
||||||
relating_type = self.file.by_id(int(relating_type_id))
|
relating_type = self.file.by_id(int(relating_type_id))
|
||||||
@@ -120,9 +130,7 @@ class AddTypeInstance(bpy.types.Operator):
|
|||||||
collection_obj = bpy.data.objects.get(collection.name)
|
collection_obj = bpy.data.objects.get(collection.name)
|
||||||
bpy.ops.bim.assign_class(obj=obj.name, ifc_class=instance_class)
|
bpy.ops.bim.assign_class(obj=obj.name, ifc_class=instance_class)
|
||||||
element = tool.Ifc.get_entity(obj)
|
element = tool.Ifc.get_entity(obj)
|
||||||
blenderbim.core.type.assign_type(
|
blenderbim.core.type.assign_type(tool.Ifc, tool.Type, element=element, type=relating_type)
|
||||||
tool.Ifc, tool.Type, element=element, type=relating_type
|
|
||||||
)
|
|
||||||
|
|
||||||
if building_obj:
|
if building_obj:
|
||||||
if instance_class in ["IfcWindow", "IfcDoor"]:
|
if instance_class in ["IfcWindow", "IfcDoor"]:
|
||||||
@@ -152,7 +160,8 @@ class AddTypeInstance(bpy.types.Operator):
|
|||||||
context.view_layer.objects.active = obj
|
context.view_layer.objects.active = obj
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
def generate_layered_element(self, ifc_class, relating_type):
|
@staticmethod
|
||||||
|
def generate_layered_element(ifc_class, relating_type):
|
||||||
layer_set_direction = None
|
layer_set_direction = None
|
||||||
|
|
||||||
parametric = ifcopenshell.util.element.get_psets(relating_type).get("EPset_Parametric")
|
parametric = ifcopenshell.util.element.get_psets(relating_type).get("EPset_Parametric")
|
||||||
@@ -174,6 +183,24 @@ class AddTypeInstance(bpy.types.Operator):
|
|||||||
pass # Dumb block generator? Eh? :)
|
pass # Dumb block generator? Eh? :)
|
||||||
|
|
||||||
|
|
||||||
|
class DisplayConstrTypes(bpy.types.Operator):
|
||||||
|
bl_idname = "bim.display_constr_types"
|
||||||
|
bl_label = "Browse Construction Types"
|
||||||
|
bl_options = {"REGISTER"}
|
||||||
|
bl_description = "Display all available Construction Types to add new instances"
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
if not AuthoringData.is_loaded:
|
||||||
|
AuthoringData.load()
|
||||||
|
props = context.scene.BIMModelProperties
|
||||||
|
ifc_class = props.ifc_class
|
||||||
|
relating_type_info = AuthoringData.relating_type_info(ifc_class)
|
||||||
|
if relating_type_info is None or not relating_type_info.fully_loaded:
|
||||||
|
AuthoringData.assetize_constr_class(ifc_class)
|
||||||
|
bpy.ops.bim.display_constr_types_ui("INVOKE_DEFAULT")
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
class AlignProduct(bpy.types.Operator):
|
class AlignProduct(bpy.types.Operator):
|
||||||
bl_idname = "bim.align_product"
|
bl_idname = "bim.align_product"
|
||||||
bl_label = "Align Product"
|
bl_label = "Align Product"
|
||||||
@@ -342,10 +369,13 @@ def ensure_material_assigned(usecase_path, ifc_file, settings):
|
|||||||
if om is not None and om.BIMObjectProperties.ifc_definition_id
|
if om is not None and om.BIMObjectProperties.ifc_definition_id
|
||||||
]
|
]
|
||||||
|
|
||||||
if material[0].id() in object_material_ids:
|
if material and material[0].id() in object_material_ids:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if len(obj.data.materials) == 1:
|
if len(obj.data.materials) == 1:
|
||||||
obj.data.materials.clear()
|
obj.data.materials.clear()
|
||||||
|
|
||||||
|
if not material:
|
||||||
|
continue
|
||||||
|
|
||||||
obj.data.materials.append(IfcStore.get_element(material[0].id()))
|
obj.data.materials.append(IfcStore.get_element(material[0].id()))
|
||||||
|
|||||||
@@ -162,7 +162,16 @@ class DumbProfileGenerator:
|
|||||||
pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="EPset_Parametric")
|
pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="EPset_Parametric")
|
||||||
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Engine": "BlenderBIM.DumbProfile"})
|
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Engine": "BlenderBIM.DumbProfile"})
|
||||||
MaterialData.load(self.file)
|
MaterialData.load(self.file)
|
||||||
obj.select_set(True)
|
try:
|
||||||
|
obj.select_set(True)
|
||||||
|
except RuntimeError:
|
||||||
|
|
||||||
|
def msg(self, context):
|
||||||
|
txt = "The created object could not be assigned to a collection. "
|
||||||
|
txt += "Has any IfcSpatialElement been deleted?"
|
||||||
|
self.layout.label(text=txt)
|
||||||
|
|
||||||
|
bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -17,21 +17,8 @@
|
|||||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
import ifcopenshell.util.type
|
|
||||||
from blenderbim.bim.module.model.data import AuthoringData
|
from blenderbim.bim.module.model.data import AuthoringData
|
||||||
from blenderbim.bim.prop import StrProperty, Attribute
|
|
||||||
from blenderbim.bim.ifc import IfcStore
|
|
||||||
from bpy.types import PropertyGroup
|
from bpy.types import PropertyGroup
|
||||||
from bpy.props import (
|
|
||||||
PointerProperty,
|
|
||||||
StringProperty,
|
|
||||||
EnumProperty,
|
|
||||||
BoolProperty,
|
|
||||||
IntProperty,
|
|
||||||
FloatProperty,
|
|
||||||
FloatVectorProperty,
|
|
||||||
CollectionProperty,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def get_ifc_class(self, context):
|
def get_ifc_class(self, context):
|
||||||
@@ -43,18 +30,104 @@ def get_ifc_class(self, context):
|
|||||||
def get_relating_type(self, context):
|
def get_relating_type(self, context):
|
||||||
if not AuthoringData.is_loaded:
|
if not AuthoringData.is_loaded:
|
||||||
AuthoringData.load()
|
AuthoringData.load()
|
||||||
return AuthoringData.data["relating_types"]
|
return AuthoringData.data["relating_types_ids"]
|
||||||
|
|
||||||
|
|
||||||
|
def get_relating_type_browser(self, context):
|
||||||
|
if not AuthoringData.is_loaded:
|
||||||
|
AuthoringData.load()
|
||||||
|
return AuthoringData.data["relating_types_browser_ids"]
|
||||||
|
|
||||||
|
|
||||||
|
def update_icon_id(self, context, browser=False):
|
||||||
|
ifc_class = self.ifc_class_browser if browser else self.ifc_class
|
||||||
|
relating_type_id = self.relating_type_id_browser if browser else self.relating_type_id
|
||||||
|
relating_type = AuthoringData.relating_type_name_by_id(ifc_class, relating_type_id)
|
||||||
|
if (
|
||||||
|
ifc_class not in AuthoringData.data["preview_constr_types"]
|
||||||
|
or relating_type_id not in AuthoringData.data["preview_constr_types"][ifc_class]
|
||||||
|
) and relating_type is not None:
|
||||||
|
if not AuthoringData.assetize_relating_type_from_selection(browser=browser):
|
||||||
|
return
|
||||||
|
if ifc_class not in AuthoringData.data["preview_constr_types"]:
|
||||||
|
pass
|
||||||
|
self.icon_id = AuthoringData.data["preview_constr_types"][ifc_class][relating_type_id]["icon_id"]
|
||||||
|
|
||||||
|
|
||||||
def update_ifc_class(self, context):
|
def update_ifc_class(self, context):
|
||||||
AuthoringData.is_loaded = False
|
AuthoringData.load_ifc_classes()
|
||||||
|
AuthoringData.load_relating_types()
|
||||||
|
if self.updating:
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def update_ifc_class_browser(self, context):
|
||||||
|
AuthoringData.load_ifc_classes()
|
||||||
|
AuthoringData.load_relating_types_browser()
|
||||||
|
if self.updating:
|
||||||
|
return
|
||||||
|
ifc_class = self.ifc_class_browser
|
||||||
|
relating_type_info = AuthoringData.relating_type_info(ifc_class)
|
||||||
|
if relating_type_info is None or not relating_type_info.fully_loaded:
|
||||||
|
AuthoringData.assetize_constr_class(ifc_class)
|
||||||
|
|
||||||
|
|
||||||
|
def update_relating_type(self, context):
|
||||||
|
AuthoringData.load_relating_types()
|
||||||
|
if not self.updating:
|
||||||
|
update_icon_id(self, context)
|
||||||
|
|
||||||
|
|
||||||
|
def update_relating_type_browser(self, context):
|
||||||
|
AuthoringData.load_relating_types_browser()
|
||||||
|
if not self.updating:
|
||||||
|
update_icon_id(self, context, browser=True)
|
||||||
|
|
||||||
|
|
||||||
|
def update_relating_type_by_name(self, context):
|
||||||
|
AuthoringData.load_relating_types()
|
||||||
|
relating_type_id = AuthoringData.relating_type_id_by_name(self.ifc_class, self.relating_type)
|
||||||
|
if relating_type_id is not None:
|
||||||
|
self.relating_type_id = relating_type_id
|
||||||
|
|
||||||
|
|
||||||
|
def update_preview_multiple(self, context):
|
||||||
|
if self.preview_multiple_constr_types:
|
||||||
|
ifc_class = self.ifc_class
|
||||||
|
relating_type_info = AuthoringData.relating_type_info(ifc_class)
|
||||||
|
if relating_type_info is None or not relating_type_info.fully_loaded:
|
||||||
|
AuthoringData.assetize_constr_class(ifc_class)
|
||||||
|
else:
|
||||||
|
update_relating_type(self, context)
|
||||||
|
|
||||||
|
|
||||||
class BIMModelProperties(PropertyGroup):
|
class BIMModelProperties(PropertyGroup):
|
||||||
ifc_class: bpy.props.EnumProperty(items=get_ifc_class, name="IFC Class", update=update_ifc_class)
|
ifc_class: bpy.props.EnumProperty(items=get_ifc_class, name="Construction Class", update=update_ifc_class)
|
||||||
relating_type: bpy.props.EnumProperty(items=get_relating_type, name="Relating Type")
|
ifc_class_browser: bpy.props.EnumProperty(items=get_ifc_class, name="Construction Class",
|
||||||
|
update=update_ifc_class_browser)
|
||||||
|
relating_type: bpy.props.StringProperty(update=update_relating_type_by_name)
|
||||||
|
relating_type_id: bpy.props.EnumProperty(
|
||||||
|
items=get_relating_type, name="Construction Type", update=update_relating_type
|
||||||
|
)
|
||||||
|
relating_type_id_browser: bpy.props.EnumProperty(
|
||||||
|
items=get_relating_type_browser, name="Construction Type", update=update_relating_type_browser
|
||||||
|
)
|
||||||
|
icon_id: bpy.props.IntProperty()
|
||||||
|
preview_multiple_constr_types: bpy.props.BoolProperty(default=False, update=update_preview_multiple)
|
||||||
|
updating: bpy.props.BoolProperty(default=False)
|
||||||
occurrence_name_style: bpy.props.EnumProperty(
|
occurrence_name_style: bpy.props.EnumProperty(
|
||||||
items=[("CLASS", "By Class", ""), ("TYPE", "By Type", ""), ("CUSTOM", "Custom", "")],
|
items=[("CLASS", "By Class", ""), ("TYPE", "By Type", ""), ("CUSTOM", "Custom", "")],
|
||||||
name="Occurrence Name Style",
|
name="Occurrence Name Style",
|
||||||
)
|
)
|
||||||
occurrence_name_function: bpy.props.StringProperty(name="Occurrence Name Function")
|
occurrence_name_function: bpy.props.StringProperty(name="Occurrence Name Function")
|
||||||
|
getter_enum = {"ifc_class": get_ifc_class, "relating_type": get_relating_type}
|
||||||
|
|
||||||
|
|
||||||
|
def get_relating_type_info(self, context):
|
||||||
|
return AuthoringData.relating_types(ifc_class=self.name)
|
||||||
|
|
||||||
|
|
||||||
|
class ConstrTypeInfo(PropertyGroup):
|
||||||
|
name: bpy.props.StringProperty(name="Construction class")
|
||||||
|
relating_type: bpy.props.EnumProperty(name="Construction type", items=get_relating_type_info)
|
||||||
|
fully_loaded: bpy.props.BoolProperty(default=False)
|
||||||
|
|||||||
@@ -166,7 +166,7 @@ def calculate_quantities(usecase_path, ifc_file, settings):
|
|||||||
obj = settings["blender_object"]
|
obj = settings["blender_object"]
|
||||||
product = ifc_file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
product = ifc_file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||||
parametric = ifcopenshell.util.element.get_psets(product).get("EPset_Parametric")
|
parametric = ifcopenshell.util.element.get_psets(product).get("EPset_Parametric")
|
||||||
if not parametric or parametric["Engine"] != "BlenderBIM.DumbLayer3":
|
if not parametric or "Engine" not in parametric or parametric["Engine"] != "BlenderBIM.DumbLayer3":
|
||||||
return
|
return
|
||||||
qto = ifcopenshell.api.run(
|
qto = ifcopenshell.api.run(
|
||||||
"pset.add_qto", ifc_file, should_run_listeners=False, product=product, name="Qto_SlabBaseQuantities"
|
"pset.add_qto", ifc_file, should_run_listeners=False, product=product, name="Qto_SlabBaseQuantities"
|
||||||
@@ -306,7 +306,16 @@ class DumbSlabGenerator:
|
|||||||
pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="EPset_Parametric")
|
pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="EPset_Parametric")
|
||||||
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Engine": "BlenderBIM.DumbLayer3"})
|
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Engine": "BlenderBIM.DumbLayer3"})
|
||||||
MaterialData.load(self.file)
|
MaterialData.load(self.file)
|
||||||
obj.select_set(True)
|
try:
|
||||||
|
obj.select_set(True)
|
||||||
|
except RuntimeError:
|
||||||
|
|
||||||
|
def msg(self, context):
|
||||||
|
txt = "The created object could not be assigned to a collection. "
|
||||||
|
txt += "Has any IfcSpatialElement been deleted?"
|
||||||
|
self.layout.label(text=txt)
|
||||||
|
|
||||||
|
bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
|
||||||
@@ -342,6 +351,23 @@ class DumbSlabPlaner:
|
|||||||
return
|
return
|
||||||
new_thickness = sum([l.LayerThickness for l in new_material.MaterialLayers])
|
new_thickness = sum([l.LayerThickness for l in new_material.MaterialLayers])
|
||||||
material = ifcopenshell.util.element.get_material(settings["related_object"])
|
material = ifcopenshell.util.element.get_material(settings["related_object"])
|
||||||
|
|
||||||
|
relating_type = settings["relating_type"]
|
||||||
|
if hasattr(relating_type, "HasPropertySets"):
|
||||||
|
psets = relating_type.HasPropertySets
|
||||||
|
if psets is not None:
|
||||||
|
for pset in psets:
|
||||||
|
if hasattr(pset, "HasProperties"):
|
||||||
|
pset_props = pset.HasProperties
|
||||||
|
if pset_props is not None:
|
||||||
|
for prop in pset_props:
|
||||||
|
if prop.Name == "LayerSetDirection":
|
||||||
|
if hasattr(prop, "NominalValue"):
|
||||||
|
nominal_value = prop.NominalValue
|
||||||
|
if hasattr(nominal_value, "wrappedValue"):
|
||||||
|
if nominal_value.wrappedValue == "AXIS2":
|
||||||
|
return
|
||||||
|
|
||||||
if material and material.is_a("IfcMaterialLayerSetUsage") and material.LayerSetDirection == "AXIS3":
|
if material and material.is_a("IfcMaterialLayerSetUsage") and material.LayerSetDirection == "AXIS3":
|
||||||
self.change_thickness(settings["related_object"], new_thickness)
|
self.change_thickness(settings["related_object"], new_thickness)
|
||||||
|
|
||||||
|
|||||||
@@ -16,54 +16,17 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import blenderbim.bim.module.type.prop as type_prop
|
from bpy.types import Panel, Operator
|
||||||
from bpy.types import Panel
|
|
||||||
from blenderbim.bim.ifc import IfcStore
|
|
||||||
from blenderbim.bim.module.model.data import AuthoringData
|
from blenderbim.bim.module.model.data import AuthoringData
|
||||||
|
from blenderbim.bim.helper import prop_with_search, close_operator_panel
|
||||||
|
|
||||||
|
|
||||||
class BIM_PT_authoring(Panel):
|
class BIM_PT_authoring(Panel):
|
||||||
bl_idname = "BIM_PT_authoring"
|
|
||||||
bl_label = "Authoring"
|
|
||||||
bl_space_type = "VIEW_3D"
|
|
||||||
bl_region_type = "UI"
|
|
||||||
bl_category = "BlenderBIM"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def poll(cls, context):
|
|
||||||
return IfcStore.get_file()
|
|
||||||
|
|
||||||
def draw(self, context):
|
|
||||||
if not AuthoringData.is_loaded:
|
|
||||||
AuthoringData.load()
|
|
||||||
|
|
||||||
props = context.scene.BIMModelProperties
|
|
||||||
col = self.layout.column(align=True)
|
|
||||||
enabled = True
|
|
||||||
|
|
||||||
if AuthoringData.data["ifc_classes"]:
|
|
||||||
col.prop(props, "ifc_class", text="", icon="FILE_VOLUME")
|
|
||||||
else:
|
|
||||||
col.label(text="No IFC Class", icon="FILE_VOLUME")
|
|
||||||
enabled = False
|
|
||||||
if AuthoringData.data["relating_types"]:
|
|
||||||
col.prop(props, "relating_type", text="", icon="FILE_3D")
|
|
||||||
else:
|
|
||||||
col.label(text="No Relating Type", icon="FILE_3D")
|
|
||||||
enabled = False
|
|
||||||
row = col.row()
|
|
||||||
row.operator("bim.add_type_instance", icon="ADD")
|
|
||||||
row.enabled = enabled
|
|
||||||
|
|
||||||
|
|
||||||
class BIM_PT_authoring_architectural(Panel):
|
|
||||||
bl_label = "Architectural"
|
bl_label = "Architectural"
|
||||||
bl_idname = "BIM_PT_authoring_architectural"
|
bl_idname = "BIM_PT_authoring"
|
||||||
bl_options = {"DEFAULT_CLOSED"}
|
|
||||||
bl_space_type = "VIEW_3D"
|
bl_space_type = "VIEW_3D"
|
||||||
bl_region_type = "UI"
|
bl_region_type = "UI"
|
||||||
bl_category = "BlenderBIM"
|
bl_category = "BlenderBIM"
|
||||||
bl_parent_id = "BIM_PT_authoring"
|
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
row = self.layout.row(align=True)
|
row = self.layout.row(align=True)
|
||||||
@@ -78,3 +41,108 @@ class BIM_PT_authoring_architectural(Panel):
|
|||||||
row = self.layout.row(align=True)
|
row = self.layout.row(align=True)
|
||||||
row.operator("bim.flip_wall", icon="ORIENTATION_NORMAL", text="Flip")
|
row.operator("bim.flip_wall", icon="ORIENTATION_NORMAL", text="Flip")
|
||||||
row.operator("bim.split_wall", icon="MOD_PHYSICS", text="Split")
|
row.operator("bim.split_wall", icon="MOD_PHYSICS", text="Split")
|
||||||
|
|
||||||
|
|
||||||
|
class DisplayConstrTypesUI(Operator):
|
||||||
|
bl_idname = "bim.display_constr_types_ui"
|
||||||
|
bl_label = "Browse Construction Types"
|
||||||
|
bl_options = {"REGISTER"}
|
||||||
|
bl_description = "Display all available Construction Types to add new instances"
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
return context.window_manager.invoke_popup(self, width=550)
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
props = context.scene.BIMModelProperties
|
||||||
|
if AuthoringData.data["ifc_classes"]:
|
||||||
|
row = self.layout.row()
|
||||||
|
row.label(text="", icon="FILE_VOLUME")
|
||||||
|
prop_with_search(row, props, "ifc_class_browser", text="")
|
||||||
|
ifc_class = props.ifc_class_browser
|
||||||
|
num_cols = 3
|
||||||
|
self.layout.row().separator(factor=0.25)
|
||||||
|
flow = self.layout.grid_flow(row_major=True, columns=num_cols, even_columns=True, even_rows=True, align=True)
|
||||||
|
relating_types = AuthoringData.relating_types_browser()
|
||||||
|
num_types = len(relating_types)
|
||||||
|
for idx, (relating_type_id, name, desc) in enumerate(relating_types):
|
||||||
|
outer_col = flow.column()
|
||||||
|
box = outer_col.box()
|
||||||
|
row = box.row()
|
||||||
|
row.label(text=name, icon="FILE_3D")
|
||||||
|
row.alignment = "CENTER"
|
||||||
|
row = box.row()
|
||||||
|
preview_constr_types = AuthoringData.data["preview_constr_types"]
|
||||||
|
if ifc_class in preview_constr_types:
|
||||||
|
preview_ifc_class = preview_constr_types[ifc_class]
|
||||||
|
if relating_type_id in preview_ifc_class:
|
||||||
|
icon_id = preview_ifc_class[relating_type_id]["icon_id"]
|
||||||
|
row.template_icon(icon_value=icon_id, scale=6.0)
|
||||||
|
row = box.row()
|
||||||
|
op = row.operator("bim.add_constr_type_instance", icon="ADD")
|
||||||
|
op.from_invoke = True
|
||||||
|
op.ifc_class = ifc_class
|
||||||
|
if relating_type_id.isnumeric():
|
||||||
|
op.relating_type_id = int(relating_type_id)
|
||||||
|
last_row_cols = num_types % num_cols
|
||||||
|
if last_row_cols != 0:
|
||||||
|
for _ in range(num_cols - last_row_cols):
|
||||||
|
flow.column()
|
||||||
|
row = self.layout.row()
|
||||||
|
row.alignment = "RIGHT"
|
||||||
|
row.operator("bim.help_relating_types", text="", icon="QUESTION")
|
||||||
|
|
||||||
|
|
||||||
|
class HelpConstrTypes(Operator):
|
||||||
|
bl_idname = "bim.help_relating_types"
|
||||||
|
bl_label = "Construction Types Help"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
bl_description = "Click to read some contextual help"
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
return context.window_manager.invoke_popup(self, width=510)
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
layout.row().separator(factor=0.5)
|
||||||
|
row = layout.row()
|
||||||
|
row.alignment = "CENTER"
|
||||||
|
row.label(text="BlenderBIM Help", icon="BLENDER")
|
||||||
|
layout.row().separator(factor=0.5)
|
||||||
|
|
||||||
|
row = layout.row().row()
|
||||||
|
row.label(text="Overview:", icon="KEYTYPE_MOVING_HOLD_VEC")
|
||||||
|
self.draw_lines(layout, self.message_summary)
|
||||||
|
layout.row().separator()
|
||||||
|
|
||||||
|
row = layout.row().row()
|
||||||
|
row.label(text="Further support:", icon="KEYTYPE_MOVING_HOLD_VEC")
|
||||||
|
layout.row().separator(factor=0.5)
|
||||||
|
row = layout.row()
|
||||||
|
op = row.operator("bim.open_upstream", text="Homepage", icon="HOME")
|
||||||
|
op.page = "home"
|
||||||
|
op = row.operator("bim.open_upstream", text="Docs", icon="DOCUMENTS")
|
||||||
|
op.page = "docs"
|
||||||
|
op = row.operator("bim.open_upstream", text="Wiki", icon="CURRENT_FILE")
|
||||||
|
op.page = "wiki"
|
||||||
|
op = row.operator("bim.open_upstream", text="Community", icon="COMMUNITY")
|
||||||
|
op.page = "community"
|
||||||
|
layout.row().separator()
|
||||||
|
|
||||||
|
def draw_lines(self, layout, lines):
|
||||||
|
box = layout.box()
|
||||||
|
for line in lines:
|
||||||
|
row = box.row()
|
||||||
|
row.label(text=f" {line}")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def message_summary(self):
|
||||||
|
return [
|
||||||
|
"The Construction Type Browser allows to preview and add new instances to the model.",
|
||||||
|
"For further support, please click on the Documentation link below.",
|
||||||
|
]
|
||||||
|
|||||||
@@ -817,7 +817,16 @@ class DumbWallGenerator:
|
|||||||
pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="EPset_Parametric")
|
pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="EPset_Parametric")
|
||||||
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Engine": "BlenderBIM.DumbLayer2"})
|
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Engine": "BlenderBIM.DumbLayer2"})
|
||||||
MaterialData.load(self.file)
|
MaterialData.load(self.file)
|
||||||
obj.select_set(True)
|
try:
|
||||||
|
obj.select_set(True)
|
||||||
|
except RuntimeError:
|
||||||
|
|
||||||
|
def msg(self, context):
|
||||||
|
txt = "The created object could not be assigned to a collection. "
|
||||||
|
txt += "Has any IfcSpatialElement been deleted?"
|
||||||
|
self.layout.label(text=txt)
|
||||||
|
|
||||||
|
bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
|
||||||
@@ -861,7 +870,7 @@ def calculate_quantities(usecase_path, ifc_file, settings):
|
|||||||
obj = settings["blender_object"]
|
obj = settings["blender_object"]
|
||||||
product = ifc_file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
product = ifc_file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||||
parametric = ifcopenshell.util.element.get_psets(product).get("EPset_Parametric")
|
parametric = ifcopenshell.util.element.get_psets(product).get("EPset_Parametric")
|
||||||
if not parametric or parametric["Engine"] != "BlenderBIM.DumbLayer2":
|
if not parametric or "Engine" not in parametric or parametric["Engine"] != "BlenderBIM.DumbLayer2":
|
||||||
return
|
return
|
||||||
qto = ifcopenshell.api.run(
|
qto = ifcopenshell.api.run(
|
||||||
"pset.add_qto", ifc_file, should_run_listeners=False, product=product, name="Qto_WallBaseQuantities"
|
"pset.add_qto", ifc_file, should_run_listeners=False, product=product, name="Qto_WallBaseQuantities"
|
||||||
@@ -941,6 +950,23 @@ class DumbWallPlaner:
|
|||||||
return
|
return
|
||||||
new_thickness = sum([l.LayerThickness for l in new_material.MaterialLayers])
|
new_thickness = sum([l.LayerThickness for l in new_material.MaterialLayers])
|
||||||
material = ifcopenshell.util.element.get_material(settings["related_object"])
|
material = ifcopenshell.util.element.get_material(settings["related_object"])
|
||||||
|
|
||||||
|
relating_type = settings["relating_type"]
|
||||||
|
if hasattr(relating_type, "HasPropertySets"):
|
||||||
|
psets = relating_type.HasPropertySets
|
||||||
|
if psets is not None:
|
||||||
|
for pset in psets:
|
||||||
|
if hasattr(pset, "HasProperties"):
|
||||||
|
pset_props = pset.HasProperties
|
||||||
|
if pset_props is not None:
|
||||||
|
for prop in pset_props:
|
||||||
|
if prop.Name == "LayerSetDirection":
|
||||||
|
if hasattr(prop, "NominalValue"):
|
||||||
|
nominal_value = prop.NominalValue
|
||||||
|
if hasattr(nominal_value, "wrappedValue"):
|
||||||
|
if nominal_value.wrappedValue == "AXIS3":
|
||||||
|
return
|
||||||
|
|
||||||
if material and material.is_a("IfcMaterialLayerSetUsage") and material.LayerSetDirection == "AXIS2":
|
if material and material.is_a("IfcMaterialLayerSetUsage") and material.LayerSetDirection == "AXIS2":
|
||||||
self.change_thickness(settings["related_object"], new_thickness)
|
self.change_thickness(settings["related_object"], new_thickness)
|
||||||
|
|
||||||
|
|||||||
@@ -19,9 +19,11 @@
|
|||||||
import os
|
import os
|
||||||
import bpy
|
import bpy
|
||||||
import blenderbim.bim.module.type.prop as type_prop
|
import blenderbim.bim.module.type.prop as type_prop
|
||||||
|
from blenderbim.bim.helper import prop_with_search, close_operator_panel
|
||||||
from bpy.types import WorkSpaceTool
|
from bpy.types import WorkSpaceTool
|
||||||
from blenderbim.bim.ifc import IfcStore
|
from blenderbim.bim.ifc import IfcStore
|
||||||
from blenderbim.bim.module.model.data import AuthoringData
|
from blenderbim.bim.module.model.data import AuthoringData
|
||||||
|
from blenderbim.bim.module.model import prop
|
||||||
|
|
||||||
|
|
||||||
class BimTool(WorkSpaceTool):
|
class BimTool(WorkSpaceTool):
|
||||||
@@ -52,31 +54,81 @@ class BimTool(WorkSpaceTool):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def draw_settings(context, layout, tool):
|
def draw_settings(context, layout, tool):
|
||||||
if not AuthoringData.is_loaded and IfcStore.get_file():
|
props = context.scene.BIMModelProperties
|
||||||
AuthoringData.load()
|
is_tool_header = context.region.type == "TOOL_HEADER"
|
||||||
|
is_sidebar = context.region.type == "UI"
|
||||||
|
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
if not IfcStore.get_file():
|
if not IfcStore.get_file():
|
||||||
row.label(text="No IFC Project", icon="ERROR")
|
row.label(text="No IFC Project", icon="ERROR")
|
||||||
return
|
return
|
||||||
props = context.scene.BIMModelProperties
|
|
||||||
if AuthoringData.data["ifc_classes"]:
|
if not AuthoringData.is_loaded:
|
||||||
row.prop(props, "ifc_class", text="")
|
AuthoringData.load()
|
||||||
|
|
||||||
|
ifc_classes = AuthoringData.data[
|
||||||
|
"ifc_classes"] if "ifc_classes" in AuthoringData.data else False
|
||||||
|
relating_types_ids = AuthoringData.data[
|
||||||
|
"relating_types_ids"] if "relating_types_ids" in AuthoringData.data else False
|
||||||
|
|
||||||
|
if ifc_classes and relating_types_ids and not props.icon_id:
|
||||||
|
# hack Dion won't like to show a preview also on the first time the sidebar is shown
|
||||||
|
bpy.app.timers.register(lambda: prop.update_relating_type(props, context))
|
||||||
|
|
||||||
|
ifc_class = props.ifc_class
|
||||||
|
relating_type_id = props.relating_type_id
|
||||||
|
|
||||||
|
if is_tool_header:
|
||||||
|
row = layout.row(align=True)
|
||||||
|
if ifc_classes:
|
||||||
|
row.label(text="", icon="FILE_VOLUME")
|
||||||
|
row.prop(data=props, property="ifc_class", text="")
|
||||||
|
else:
|
||||||
|
row.label(text="No Construction Class", icon="FILE_VOLUME")
|
||||||
|
row = layout.row(align=True)
|
||||||
|
if relating_types_ids:
|
||||||
|
row.label(text="", icon="FILE_3D")
|
||||||
|
row.prop(data=props, property="relating_type_id", text="")
|
||||||
|
else:
|
||||||
|
row.label(text="No Construction Type", icon="FILE_3D")
|
||||||
|
if ifc_classes:
|
||||||
|
row = layout.row()
|
||||||
|
row.operator("bim.display_constr_types", icon="TRIA_DOWN", text="")
|
||||||
|
row = layout.row(align=True)
|
||||||
|
row.label(text="", icon="EVENT_SHIFT")
|
||||||
|
row.label(text="", icon="EVENT_A")
|
||||||
|
row.label(text=f" Add")
|
||||||
else:
|
else:
|
||||||
row.label(text="No IFC Class")
|
row = layout.row(align=True)
|
||||||
if AuthoringData.data["relating_types"]:
|
if ifc_classes:
|
||||||
row.prop(props, "relating_type", text="")
|
row.label(text="", icon="FILE_VOLUME")
|
||||||
else:
|
prop_with_search(row, props, "ifc_class", text="")
|
||||||
row.label(text="No Relating Type")
|
else:
|
||||||
|
row.label(text="No Construction Class", icon="FILE_VOLUME")
|
||||||
|
row = layout.row(align=True)
|
||||||
|
if relating_types_ids:
|
||||||
|
row.label(text="", icon="FILE_3D")
|
||||||
|
prop_with_search(row, props, "relating_type_id", text="")
|
||||||
|
else:
|
||||||
|
row.label(text="No Construction Type", icon="FILE_3D")
|
||||||
|
if is_sidebar and ifc_classes and relating_types_ids:
|
||||||
|
box = layout.box()
|
||||||
|
box.template_icon(icon_value=props.icon_id, scale=8)
|
||||||
|
row = layout.row()
|
||||||
|
op = row.operator("bim.add_constr_type_instance", icon="ADD")
|
||||||
|
op.from_invoke = True
|
||||||
|
op.ifc_class = ifc_class
|
||||||
|
if relating_type_id.isnumeric():
|
||||||
|
op.relating_type_id = int(relating_type_id)
|
||||||
|
|
||||||
row.label(text="", icon="BLANK1")
|
if ifc_classes:
|
||||||
|
row = layout.row(align=True)
|
||||||
|
row.label(text="", icon="EVENT_SHIFT")
|
||||||
|
row.label(text="", icon="EVENT_A")
|
||||||
|
row.label(text=f" Add Type Instance")
|
||||||
|
|
||||||
row = layout.row(align=True)
|
if ifc_classes:
|
||||||
row.label(text="", icon="EVENT_SHIFT")
|
if ifc_class == "IfcWallType":
|
||||||
row.label(text="Add Type Instance", icon="EVENT_A")
|
|
||||||
|
|
||||||
if AuthoringData.data["ifc_classes"]:
|
|
||||||
if props.ifc_class == "IfcWallType":
|
|
||||||
row = layout.row()
|
row = layout.row()
|
||||||
row.label(text="Join")
|
row.label(text="Join")
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
@@ -98,13 +150,23 @@ class BimTool(WorkSpaceTool):
|
|||||||
row.label(text="", icon="EVENT_SHIFT")
|
row.label(text="", icon="EVENT_SHIFT")
|
||||||
row.label(text="Split", icon="EVENT_S")
|
row.label(text="Split", icon="EVENT_S")
|
||||||
|
|
||||||
if props.ifc_class in ("IfcColumnType", "IfcBeamType", "IfcMemberType"):
|
if ifc_class in ("IfcColumnType", "IfcBeamType", "IfcMemberType"):
|
||||||
row = layout.row()
|
row = layout.row()
|
||||||
row.label(text="Join")
|
row.label(text="Join")
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
row.label(text="", icon="EVENT_SHIFT")
|
row.label(text="", icon="EVENT_SHIFT")
|
||||||
row.label(text="Extend", icon="EVENT_E")
|
row.label(text="Extend", icon="EVENT_E")
|
||||||
|
|
||||||
|
if props.ifc_class in (
|
||||||
|
"IfcCableCarrierSegmentType",
|
||||||
|
"IfcCableSegmentType",
|
||||||
|
"IfcDuctSegmentType",
|
||||||
|
"IfcPipeSegmentType",
|
||||||
|
):
|
||||||
|
row = layout.row(align=True)
|
||||||
|
row.label(text="", icon="EVENT_SHIFT")
|
||||||
|
row.label(text="Extend", icon="EVENT_E")
|
||||||
|
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
row.label(text="", icon="EVENT_SHIFT")
|
row.label(text="", icon="EVENT_SHIFT")
|
||||||
row.label(text="Opening", icon="EVENT_O")
|
row.label(text="Opening", icon="EVENT_O")
|
||||||
@@ -156,7 +218,7 @@ class Hotkey(bpy.types.Operator):
|
|||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
def hotkey_S_A(self):
|
def hotkey_S_A(self):
|
||||||
bpy.ops.bim.add_type_instance()
|
bpy.ops.bim.add_constr_type_instance()
|
||||||
|
|
||||||
def hotkey_S_C(self):
|
def hotkey_S_C(self):
|
||||||
if self.has_ifc_class and self.props.ifc_class == "IfcWallType":
|
if self.has_ifc_class and self.props.ifc_class == "IfcWallType":
|
||||||
|
|||||||
@@ -253,10 +253,7 @@ class ObjectActorData:
|
|||||||
@classmethod
|
@classmethod
|
||||||
def load(cls):
|
def load(cls):
|
||||||
cls.is_loaded = True
|
cls.is_loaded = True
|
||||||
cls.data = {
|
cls.data = {"actor": cls.actor(), "actors": cls.actors()}
|
||||||
"actor": cls.actor(),
|
|
||||||
"actors": cls.actors()
|
|
||||||
}
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def actor(cls):
|
def actor(cls):
|
||||||
@@ -279,9 +276,9 @@ class ObjectActorData:
|
|||||||
roles = cls.get_roles(actor.TheActor.ThePerson)
|
roles = cls.get_roles(actor.TheActor.ThePerson)
|
||||||
roles.extend(cls.get_roles(actor.TheActor.TheOrganization))
|
roles.extend(cls.get_roles(actor.TheActor.TheOrganization))
|
||||||
role = ", ".join(roles)
|
role = ", ".join(roles)
|
||||||
results.append({
|
results.append(
|
||||||
"id": actor.id(), "name": actor.Name or "Unnamed", "role": role, "ifc_class": actor.is_a()
|
{"id": actor.id(), "name": actor.Name or "Unnamed", "role": role, "ifc_class": actor.is_a()}
|
||||||
})
|
)
|
||||||
return results
|
return results
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from blenderbim.bim.helper import prop_with_search
|
||||||
import bpy
|
import bpy
|
||||||
from blenderbim.bim.helper import draw_attributes
|
from blenderbim.bim.helper import draw_attributes
|
||||||
|
|
||||||
@@ -37,7 +38,7 @@ class BIM_PT_patch(bpy.types.Panel):
|
|||||||
scene = context.scene
|
scene = context.scene
|
||||||
props = scene.BIMPatchProperties
|
props = scene.BIMPatchProperties
|
||||||
row = layout.row()
|
row = layout.row()
|
||||||
row.prop(props, "ifc_patch_recipes")
|
prop_with_search(row, props, "ifc_patch_recipes")
|
||||||
|
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
row.prop(props, "ifc_patch_input")
|
row.prop(props, "ifc_patch_input")
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ classes = (
|
|||||||
operator.RefreshLibrary,
|
operator.RefreshLibrary,
|
||||||
operator.RewindLibrary,
|
operator.RewindLibrary,
|
||||||
operator.SaveLibraryFile,
|
operator.SaveLibraryFile,
|
||||||
|
operator.AppendEntireLibrary,
|
||||||
operator.SelectLibraryFile,
|
operator.SelectLibraryFile,
|
||||||
operator.ToggleFilterCategories,
|
operator.ToggleFilterCategories,
|
||||||
operator.ToggleLinkVisibility,
|
operator.ToggleLinkVisibility,
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ class SelectLibraryFile(bpy.types.Operator, IFCFileSelector):
|
|||||||
bl_description = "Select an IFC file that can be used as a library"
|
bl_description = "Select an IFC file that can be used as a library"
|
||||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||||
filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"})
|
filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"})
|
||||||
|
append_all: bpy.props.BoolProperty(default=False)
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
IfcStore.begin_transaction(self)
|
IfcStore.begin_transaction(self)
|
||||||
@@ -87,6 +88,8 @@ class SelectLibraryFile(bpy.types.Operator, IFCFileSelector):
|
|||||||
bpy.ops.bim.refresh_library()
|
bpy.ops.bim.refresh_library()
|
||||||
if context.area:
|
if context.area:
|
||||||
context.area.tag_redraw()
|
context.area.tag_redraw()
|
||||||
|
if self.append_all:
|
||||||
|
bpy.ops.bim.append_entire_library()
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
@@ -105,6 +108,9 @@ class SelectLibraryFile(bpy.types.Operator, IFCFileSelector):
|
|||||||
IfcStore.library_path = data["filepath"]
|
IfcStore.library_path = data["filepath"]
|
||||||
IfcStore.library_file = ifcopenshell.open(data["filepath"])
|
IfcStore.library_file = ifcopenshell.open(data["filepath"])
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
self.layout.prop(self, "append_all", text="Append Entire Library")
|
||||||
|
|
||||||
|
|
||||||
class RefreshLibrary(bpy.types.Operator):
|
class RefreshLibrary(bpy.types.Operator):
|
||||||
bl_idname = "bim.refresh_library"
|
bl_idname = "bim.refresh_library"
|
||||||
@@ -282,16 +288,43 @@ class SaveLibraryFile(bpy.types.Operator):
|
|||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
class AppendEntireLibrary(bpy.types.Operator):
|
||||||
|
bl_idname = "bim.append_entire_library"
|
||||||
|
bl_label = "Append Entiry Library"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return IfcStore.get_file()
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
return IfcStore.execute_ifc_operator(self, context)
|
||||||
|
|
||||||
|
def _execute(self, context):
|
||||||
|
self.file = IfcStore.get_file()
|
||||||
|
self.library = IfcStore.library_file
|
||||||
|
|
||||||
|
lib_elements = ifcopenshell.util.selector.Selector().parse(
|
||||||
|
self.library, ".IfcTypeProduct | .IfcMaterial | .IfcCostSchedule| .IfcProfileDef"
|
||||||
|
)
|
||||||
|
for element in lib_elements:
|
||||||
|
bpy.ops.bim.append_library_element(definition=element.id())
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
class AppendLibraryElement(bpy.types.Operator):
|
class AppendLibraryElement(bpy.types.Operator):
|
||||||
bl_idname = "bim.append_library_element"
|
bl_idname = "bim.append_library_element"
|
||||||
bl_label = "Append Library Element"
|
bl_label = "Append Library Element"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
bl_description = "Append element to the current project"
|
||||||
definition: bpy.props.IntProperty()
|
definition: bpy.props.IntProperty()
|
||||||
prop_index: bpy.props.IntProperty()
|
prop_index: bpy.props.IntProperty()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
return IfcStore.get_file()
|
poll = bool(IfcStore.get_file())
|
||||||
|
if bpy.app.version > (3, 0, 0) and not poll:
|
||||||
|
cls.poll_message_set("Please create or load a project first.")
|
||||||
|
return poll
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
return IfcStore.execute_ifc_operator(self, context)
|
return IfcStore.execute_ifc_operator(self, context)
|
||||||
@@ -347,7 +380,7 @@ class AppendLibraryElement(bpy.types.Operator):
|
|||||||
ifc_importer.material_creator.load_existing_materials()
|
ifc_importer.material_creator.load_existing_materials()
|
||||||
self.import_type_materials(element, ifc_importer)
|
self.import_type_materials(element, ifc_importer)
|
||||||
self.import_type_styles(element, ifc_importer)
|
self.import_type_styles(element, ifc_importer)
|
||||||
ifc_importer.create_type_product(element)
|
ifc_importer.create_element_type(element)
|
||||||
ifc_importer.place_objects_in_collections()
|
ifc_importer.place_objects_in_collections()
|
||||||
|
|
||||||
def import_type_materials(self, element, ifc_importer):
|
def import_type_materials(self, element, ifc_importer):
|
||||||
@@ -633,16 +666,20 @@ class LinkIfc(bpy.types.Operator):
|
|||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
bl_description = "Link a Blender file"
|
bl_description = "Link a Blender file"
|
||||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||||
|
files: bpy.props.CollectionProperty(name="Files", type=bpy.types.OperatorFileListElement)
|
||||||
|
directory: bpy.props.StringProperty(subtype="DIR_PATH")
|
||||||
filter_glob: bpy.props.StringProperty(default="*.blend;*.blend1", options={"HIDDEN"})
|
filter_glob: bpy.props.StringProperty(default="*.blend;*.blend1", options={"HIDDEN"})
|
||||||
use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=False)
|
use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=False)
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
new = context.scene.BIMProjectProperties.links.add()
|
files = [self.filepath] if self.filepath else [f.name for f in self.files]
|
||||||
filepath = self.filepath
|
for filename in files:
|
||||||
if self.use_relative_path:
|
filepath = os.path.join(self.directory, filename)
|
||||||
filepath = os.path.relpath(filepath, bpy.path.abspath("//"))
|
new = context.scene.BIMProjectProperties.links.add()
|
||||||
new.name = filepath
|
if self.use_relative_path:
|
||||||
bpy.ops.bim.load_link(filepath=self.filepath)
|
filepath = os.path.relpath(filepath, bpy.path.abspath("//"))
|
||||||
|
new.name = filepath
|
||||||
|
bpy.ops.bim.load_link(filepath=filepath)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
@@ -708,7 +745,6 @@ class LoadLink(bpy.types.Operator):
|
|||||||
continue
|
continue
|
||||||
bpy.data.scenes[0].collection.children.link(child)
|
bpy.data.scenes[0].collection.children.link(child)
|
||||||
link = context.scene.BIMProjectProperties.links.get(filepath)
|
link = context.scene.BIMProjectProperties.links.get(filepath)
|
||||||
link.collection = child
|
|
||||||
link.is_loaded = True
|
link.is_loaded = True
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
@@ -731,23 +767,25 @@ class ToggleLinkVisibility(bpy.types.Operator):
|
|||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
def toggle_wireframe(self, link):
|
def toggle_wireframe(self, link):
|
||||||
objs = filter(lambda obj: "IfcOpeningElement" not in obj.name, link.collection.all_objects)
|
for collection in self.get_linked_collections():
|
||||||
for i,obj in enumerate(objs):
|
objs = filter(lambda obj: "IfcOpeningElement" not in obj.name, collection.all_objects)
|
||||||
if i == 0:
|
for i, obj in enumerate(objs):
|
||||||
if obj.display_type == "WIRE":
|
if i == 0:
|
||||||
display_type = "TEXTURED"
|
if obj.display_type == "WIRE":
|
||||||
else:
|
display_type = "TEXTURED"
|
||||||
display_type = "WIRE"
|
else:
|
||||||
obj.display_type = display_type
|
display_type = "WIRE"
|
||||||
link.is_wireframe = display_type == "WIRE"
|
obj.display_type = display_type
|
||||||
|
link.is_wireframe = display_type == "WIRE"
|
||||||
|
|
||||||
def toggle_visibility(self, link):
|
def toggle_visibility(self, link):
|
||||||
|
linked_collections = self.get_linked_collections()
|
||||||
queue = [bpy.context.view_layer.layer_collection]
|
queue = [bpy.context.view_layer.layer_collection]
|
||||||
layer_collection = None
|
layer_collection = None
|
||||||
|
|
||||||
while queue:
|
while queue:
|
||||||
layer = queue.pop()
|
layer = queue.pop()
|
||||||
if layer.collection == link.collection:
|
if layer.collection in linked_collections:
|
||||||
layer_collection = layer
|
layer_collection = layer
|
||||||
break
|
break
|
||||||
queue.extend(list(layer.children))
|
queue.extend(list(layer.children))
|
||||||
@@ -756,6 +794,11 @@ class ToggleLinkVisibility(bpy.types.Operator):
|
|||||||
layer_collection.exclude = not layer_collection.exclude
|
layer_collection.exclude = not layer_collection.exclude
|
||||||
link.is_hidden = layer_collection.exclude
|
link.is_hidden = layer_collection.exclude
|
||||||
|
|
||||||
|
def get_linked_collections(self):
|
||||||
|
return [
|
||||||
|
c for c in bpy.data.collections if "IfcProject" in c.name and c.library and c.library.filepath == self.link
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class ExportIFC(bpy.types.Operator):
|
class ExportIFC(bpy.types.Operator):
|
||||||
bl_idname = "export_ifc.bim"
|
bl_idname = "export_ifc.bim"
|
||||||
|
|||||||
@@ -92,7 +92,6 @@ class FilterCategory(PropertyGroup):
|
|||||||
|
|
||||||
class Link(PropertyGroup):
|
class Link(PropertyGroup):
|
||||||
name: StringProperty(name="Name")
|
name: StringProperty(name="Name")
|
||||||
collection: PointerProperty(name="Collection", type=bpy.types.Collection)
|
|
||||||
is_loaded: BoolProperty(name="Is Loaded", default=False)
|
is_loaded: BoolProperty(name="Is Loaded", default=False)
|
||||||
is_wireframe: BoolProperty(name="Is Wireframe", default=False)
|
is_wireframe: BoolProperty(name="Is Wireframe", default=False)
|
||||||
is_hidden: BoolProperty(name="Is Hidden", default=False)
|
is_hidden: BoolProperty(name="Is Hidden", default=False)
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
from blenderbim.bim.helper import prop_with_search
|
||||||
from bpy.types import Panel, UIList
|
from bpy.types import Panel, UIList
|
||||||
from blenderbim.bim.ifc import IfcStore
|
from blenderbim.bim.ifc import IfcStore
|
||||||
from blenderbim.bim.module.project.data import ProjectData
|
from blenderbim.bim.module.project.data import ProjectData
|
||||||
@@ -48,10 +49,8 @@ class BIM_PT_project(Panel):
|
|||||||
|
|
||||||
def draw_load_ui(self, context):
|
def draw_load_ui(self, context):
|
||||||
pprops = context.scene.BIMProjectProperties
|
pprops = context.scene.BIMProjectProperties
|
||||||
row = self.layout.row()
|
prop_with_search(self.layout, pprops, "collection_mode")
|
||||||
row.prop(pprops, "collection_mode")
|
prop_with_search(self.layout, pprops, "filter_mode")
|
||||||
row = self.layout.row()
|
|
||||||
row.prop(pprops, "filter_mode")
|
|
||||||
if pprops.filter_mode in ["DECOMPOSITION", "IFC_CLASS", "IFC_TYPE"]:
|
if pprops.filter_mode in ["DECOMPOSITION", "IFC_CLASS", "IFC_TYPE"]:
|
||||||
row = self.layout.row(align=True)
|
row = self.layout.row(align=True)
|
||||||
row.label(text=f"Total: {pprops.total_elements}")
|
row.label(text=f"Total: {pprops.total_elements}")
|
||||||
@@ -84,8 +83,7 @@ class BIM_PT_project(Panel):
|
|||||||
row = self.layout.row()
|
row = self.layout.row()
|
||||||
row.prop(pprops, "is_coordinating")
|
row.prop(pprops, "is_coordinating")
|
||||||
if pprops.is_coordinating:
|
if pprops.is_coordinating:
|
||||||
row = self.layout.row()
|
prop_with_search(self.layout, pprops, "merge_mode")
|
||||||
row.prop(pprops, "merge_mode")
|
|
||||||
row = self.layout.row()
|
row = self.layout.row()
|
||||||
row.prop(pprops, "deflection_tolerance")
|
row.prop(pprops, "deflection_tolerance")
|
||||||
row = self.layout.row()
|
row = self.layout.row()
|
||||||
@@ -170,8 +168,7 @@ class BIM_PT_project(Panel):
|
|||||||
def draw_create_project_ui(self, context):
|
def draw_create_project_ui(self, context):
|
||||||
props = context.scene.BIMProperties
|
props = context.scene.BIMProperties
|
||||||
pprops = context.scene.BIMProjectProperties
|
pprops = context.scene.BIMProjectProperties
|
||||||
row = self.layout.row()
|
prop_with_search(self.layout, pprops, "export_schema")
|
||||||
row.prop(pprops, "export_schema")
|
|
||||||
row = self.layout.row()
|
row = self.layout.row()
|
||||||
row.prop(context.scene.unit_settings, "system")
|
row.prop(context.scene.unit_settings, "system")
|
||||||
row = self.layout.row()
|
row = self.layout.row()
|
||||||
@@ -180,8 +177,7 @@ class BIM_PT_project(Panel):
|
|||||||
row.prop(props, "area_unit", text="Area Unit")
|
row.prop(props, "area_unit", text="Area Unit")
|
||||||
row = self.layout.row()
|
row = self.layout.row()
|
||||||
row.prop(props, "volume_unit", text="Volume Unit")
|
row.prop(props, "volume_unit", text="Volume Unit")
|
||||||
row = self.layout.row()
|
prop_with_search(self.layout, pprops, "template_file", text="Template")
|
||||||
row.prop(pprops, "template_file", text="Template")
|
|
||||||
|
|
||||||
row = self.layout.row(align=True)
|
row = self.layout.row(align=True)
|
||||||
row.operator("bim.create_project")
|
row.operator("bim.create_project")
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ classes = (
|
|||||||
operator.BIM_OT_rename_parameters,
|
operator.BIM_OT_rename_parameters,
|
||||||
operator.BIM_OT_add_edit_custom_property,
|
operator.BIM_OT_add_edit_custom_property,
|
||||||
operator.BIM_OT_bulk_remove_psets,
|
operator.BIM_OT_bulk_remove_psets,
|
||||||
|
prop.IfcPropertyEnumeratedValue,
|
||||||
|
prop.IfcProperty,
|
||||||
prop.PsetProperties,
|
prop.PsetProperties,
|
||||||
prop.MaterialPsetProperties,
|
prop.MaterialPsetProperties,
|
||||||
prop.TaskPsetProperties,
|
prop.TaskPsetProperties,
|
||||||
|
|||||||
@@ -122,58 +122,100 @@ class EnablePsetEditing(bpy.types.Operator):
|
|||||||
for prop_template in pset_template.HasPropertyTemplates:
|
for prop_template in pset_template.HasPropertyTemplates:
|
||||||
if not prop_template.is_a("IfcSimplePropertyTemplate"):
|
if not prop_template.is_a("IfcSimplePropertyTemplate"):
|
||||||
continue # Other types not yet supported
|
continue # Other types not yet supported
|
||||||
|
|
||||||
if prop_template.TemplateType == "P_SINGLEVALUE":
|
if prop_template.TemplateType == "P_SINGLEVALUE":
|
||||||
try:
|
self.load_single_value(prop_template, data)
|
||||||
data_type = ifcopenshell.util.attribute.get_primitive_type(
|
elif prop_template.TemplateType.startswith("Q_"):
|
||||||
IfcStore.get_schema().declaration_by_name(prop_template.PrimaryMeasureType or "IfcLabel")
|
self.load_single_value(prop_template, data)
|
||||||
)
|
|
||||||
except:
|
|
||||||
# TODO: Occurs if the data type is something that exists in
|
|
||||||
# IFC4 and not in IFC2X3. To fully fix this we need to
|
|
||||||
# generate the IFC2X3 pset template definitions.
|
|
||||||
continue
|
|
||||||
elif prop_template.TemplateType == "P_ENUMERATEDVALUE":
|
elif prop_template.TemplateType == "P_ENUMERATEDVALUE":
|
||||||
data_type = "enum"
|
self.load_enumerated_value(prop_template, data)
|
||||||
enum_items = [v.wrappedValue for v in prop_template.Enumerators.EnumerationValues]
|
|
||||||
elif prop_template.TemplateType in ["Q_LENGTH", "Q_AREA", "Q_VOLUME", "Q_WEIGHT", "Q_TIME"]:
|
|
||||||
data_type = "float"
|
|
||||||
elif prop_template.TemplateType == "Q_COUNT":
|
|
||||||
data_type = "integer"
|
|
||||||
else:
|
|
||||||
continue # Other types not yet supported
|
|
||||||
|
|
||||||
new = self.props.properties.add()
|
def load_single_value(self, prop_template, data):
|
||||||
new.name = prop_template.Name
|
prop = self.props.properties.add()
|
||||||
new.is_null = data.get(prop_template.Name, None) is None
|
prop.name = prop_template.Name
|
||||||
new.is_optional = True
|
prop.value_type = "IfcPropertySingleValue"
|
||||||
new.data_type = data_type
|
metadata = prop.metadata
|
||||||
new.is_uri = prop_template.PrimaryMeasureType == "IfcURIReference"
|
metadata.name = prop_template.Name
|
||||||
|
metadata.is_null = data.get(prop_template.Name, None) is None
|
||||||
|
metadata.is_optional = True
|
||||||
|
metadata.is_uri = prop_template.PrimaryMeasureType == "IfcURIReference"
|
||||||
|
metadata.data_type = self.get_data_type(prop_template)
|
||||||
|
|
||||||
if data_type == "string":
|
if metadata.data_type == "string":
|
||||||
new.string_value = "" if new.is_null else data[prop_template.Name]
|
metadata.string_value = "" if metadata.is_null else data[prop_template.Name]
|
||||||
elif data_type == "integer":
|
elif metadata.data_type == "integer":
|
||||||
new.int_value = 0 if new.is_null else data[prop_template.Name]
|
metadata.int_value = 0 if metadata.is_null else data[prop_template.Name]
|
||||||
elif data_type == "float":
|
elif metadata.data_type == "float":
|
||||||
new.float_value = 0.0 if new.is_null else data[prop_template.Name]
|
metadata.float_value = 0.0 if metadata.is_null else data[prop_template.Name]
|
||||||
elif data_type == "boolean":
|
elif metadata.data_type == "boolean":
|
||||||
new.bool_value = False if new.is_null else data[prop_template.Name]
|
metadata.bool_value = False if metadata.is_null else data[prop_template.Name]
|
||||||
elif data_type == "enum":
|
|
||||||
new.enum_items = json.dumps(enum_items)
|
def get_data_type(self, prop_template):
|
||||||
if data.get(prop_template.Name):
|
if prop_template.TemplateType in ["Q_LENGTH", "Q_AREA", "Q_VOLUME", "Q_WEIGHT", "Q_TIME"]:
|
||||||
new.enum_value = str(data[prop_template.Name])
|
return "float"
|
||||||
|
elif prop_template.TemplateType == "Q_COUNT":
|
||||||
|
return "integer"
|
||||||
|
try:
|
||||||
|
return ifcopenshell.util.attribute.get_primitive_type(
|
||||||
|
IfcStore.get_schema().declaration_by_name(prop_template.PrimaryMeasureType or "IfcLabel")
|
||||||
|
)
|
||||||
|
except:
|
||||||
|
# TODO: Occurs if the data type is something that exists in
|
||||||
|
# IFC4 and not in IFC2X3. To fully fix this we need to
|
||||||
|
# generate the IFC2X3 pset template definitions.
|
||||||
|
pass
|
||||||
|
|
||||||
|
def load_enumerated_value(self, prop_template, data):
|
||||||
|
enum_items = [v.wrappedValue for v in prop_template.Enumerators.EnumerationValues]
|
||||||
|
selected_enum_items = data.get(prop_template.Name, [])
|
||||||
|
|
||||||
|
prop = self.props.properties.add()
|
||||||
|
prop.name = prop_template.Name
|
||||||
|
prop.value_type = "IfcPropertyEnumeratedValue"
|
||||||
|
metadata = prop.metadata
|
||||||
|
metadata.name = prop_template.Name
|
||||||
|
metadata.is_null = data.get(prop_template.Name, None) is None
|
||||||
|
metadata.is_optional = True
|
||||||
|
metadata.is_uri = prop_template.PrimaryMeasureType == "IfcURIReference"
|
||||||
|
|
||||||
|
# Cute hack to abuse the metadata to find the Blender data_type
|
||||||
|
metadata.set_value(enum_items[0])
|
||||||
|
data_type = metadata.get_value_name()
|
||||||
|
|
||||||
|
for enum in enum_items:
|
||||||
|
new = prop.enumerated_value.enumerated_values.add()
|
||||||
|
setattr(new, data_type, enum)
|
||||||
|
new.is_selected = enum in selected_enum_items
|
||||||
|
|
||||||
def load_from_pset_data(self, pset_data):
|
def load_from_pset_data(self, pset_data):
|
||||||
for prop_id in pset_data["Properties"]:
|
for prop_id in pset_data["Properties"]:
|
||||||
prop = Data.properties[prop_id]
|
prop = Data.properties[prop_id]
|
||||||
|
|
||||||
value = prop["NominalValue"]
|
if prop["type"] == "IfcPropertyEnumeratedValue":
|
||||||
new = self.props.properties.add()
|
simple_prop = self.props.properties.add()
|
||||||
new.set_value(value)
|
simple_prop.value_type = "IfcPropertyEnumeratedValue"
|
||||||
new.name = prop["Name"]
|
metadata = simple_prop.metadata
|
||||||
new.is_null = value is None
|
metadata.name = prop["Name"]
|
||||||
new.is_optional = True
|
metadata.is_null = len(simple_prop.enumerated_value.enumerated_values) == 0
|
||||||
new.set_value(new.get_value_default() if new.is_null else value)
|
metadata.is_optional = True
|
||||||
|
metadata.set_value(prop["EnumerationReference"].EnumerationValues[0].wrappedValue)
|
||||||
|
|
||||||
|
enum_items = [v.wrappedValue for v in prop["EnumerationReference"].EnumerationValues]
|
||||||
|
selected_enum_items = [v.wrappedValue for v in prop["EnumerationValues"]]
|
||||||
|
data_type = metadata.get_value_name()
|
||||||
|
|
||||||
|
for enum in enum_items:
|
||||||
|
new = simple_prop.enumerated_value.enumerated_values.add()
|
||||||
|
setattr(new, data_type, enum)
|
||||||
|
new.is_selected = enum in selected_enum_items
|
||||||
|
else:
|
||||||
|
value = prop["NominalValue"]
|
||||||
|
new_prop = self.props.properties.add()
|
||||||
|
metadata = new_prop.metadata
|
||||||
|
metadata.set_value(value)
|
||||||
|
metadata.name = prop["Name"]
|
||||||
|
metadata.is_null = value is None
|
||||||
|
metadata.is_optional = True
|
||||||
|
metadata.set_value(metadata.get_value_default() if metadata.is_null else value)
|
||||||
|
|
||||||
|
|
||||||
class DisablePsetEditing(bpy.types.Operator, Operator):
|
class DisablePsetEditing(bpy.types.Operator, Operator):
|
||||||
@@ -209,7 +251,13 @@ class EditPset(bpy.types.Operator, Operator):
|
|||||||
else:
|
else:
|
||||||
data = Data.psets if pset_id in Data.psets else Data.qtos
|
data = Data.psets if pset_id in Data.psets else Data.qtos
|
||||||
for prop in props.properties:
|
for prop in props.properties:
|
||||||
properties[prop.name] = prop.get_value()
|
if prop.value_type == "IfcPropertySingleValue":
|
||||||
|
properties[prop.metadata.name] = prop.metadata.get_value()
|
||||||
|
elif prop.value_type == "IfcPropertyEnumeratedValue":
|
||||||
|
value_name = prop.metadata.get_value_name()
|
||||||
|
properties[prop.metadata.name] = [
|
||||||
|
e[value_name] for e in prop.enumerated_value.enumerated_values if e.is_selected
|
||||||
|
]
|
||||||
|
|
||||||
if pset_id in Data.psets:
|
if pset_id in Data.psets:
|
||||||
ifcopenshell.api.run(
|
ifcopenshell.api.run(
|
||||||
@@ -319,7 +367,7 @@ class GuessQuantity(bpy.types.Operator):
|
|||||||
self.qto_calculator = QtoCalculator()
|
self.qto_calculator = QtoCalculator()
|
||||||
obj = context.active_object
|
obj = context.active_object
|
||||||
prop = obj.PsetProperties.properties.get(self.prop)
|
prop = obj.PsetProperties.properties.get(self.prop)
|
||||||
prop.float_value = self.guess_quantity(obj, context)
|
prop.metadata.float_value = self.guess_quantity(obj, context)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
def guess_quantity(self, obj, context):
|
def guess_quantity(self, obj, context):
|
||||||
@@ -363,7 +411,7 @@ class CopyPropertyToSelection(bpy.types.Operator, Operator):
|
|||||||
def _execute(self, context):
|
def _execute(self, context):
|
||||||
is_pset = tool.Ifc.get().by_id(context.active_object.PsetProperties.active_pset_id).is_a("IfcPropertySet")
|
is_pset = tool.Ifc.get().by_id(context.active_object.PsetProperties.active_pset_id).is_a("IfcPropertySet")
|
||||||
pset_name = context.active_object.PsetProperties.active_pset_name
|
pset_name = context.active_object.PsetProperties.active_pset_name
|
||||||
prop_value = context.active_object.PsetProperties.properties.get(self.name).get_value()
|
prop_value = context.active_object.PsetProperties.properties.get(self.name).metadata.get_value()
|
||||||
for obj in context.selected_objects:
|
for obj in context.selected_objects:
|
||||||
core.copy_property_to_selection(
|
core.copy_property_to_selection(
|
||||||
tool.Ifc,
|
tool.Ifc,
|
||||||
@@ -381,9 +429,13 @@ class BIM_OT_add_property_to_edit(bpy.types.Operator):
|
|||||||
bl_idname = "bim.add_property_to_edit"
|
bl_idname = "bim.add_property_to_edit"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
option: bpy.props.StringProperty()
|
option: bpy.props.StringProperty()
|
||||||
|
index: bpy.props.IntProperty(default=-1)
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
getattr(context.scene, self.option).add()
|
if self.index == -1:
|
||||||
|
getattr(context.scene, self.option).add()
|
||||||
|
else:
|
||||||
|
getattr(context.scene, self.option)[self.index].enum_values.add()
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
@@ -392,10 +444,14 @@ class BIM_OT_remove_property_to_edit(bpy.types.Operator):
|
|||||||
bl_idname = "bim.remove_property_to_edit"
|
bl_idname = "bim.remove_property_to_edit"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
index: bpy.props.IntProperty()
|
index: bpy.props.IntProperty()
|
||||||
|
index2: bpy.props.IntProperty(default=-1)
|
||||||
option: bpy.props.StringProperty()
|
option: bpy.props.StringProperty()
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
getattr(context.scene, self.option).remove(self.index)
|
if self.index2 == -1:
|
||||||
|
getattr(context.scene, self.option).remove(self.index)
|
||||||
|
else:
|
||||||
|
getattr(context.scene, self.option)[self.index].enum_values.remove(self.index2)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
@@ -471,16 +527,41 @@ class BIM_OT_add_edit_custom_property(bpy.types.Operator):
|
|||||||
for prop in props:
|
for prop in props:
|
||||||
value = getattr(prop, prop.get_value_name())
|
value = getattr(prop, prop.get_value_name())
|
||||||
primary_measure_type = prop.primary_measure_type
|
primary_measure_type = prop.primary_measure_type
|
||||||
value_ifc_entity = getattr(self.file, f"create{primary_measure_type}")(value)
|
|
||||||
|
if prop.template_type == "IfcPropertyEnumeratedValue":
|
||||||
|
value_ifc_entity = self.generate_enum_entity(prop)
|
||||||
|
elif prop.template_type == "IfcPropertySingleValue":
|
||||||
|
value_ifc_entity = getattr(self.file, f"create{primary_measure_type}")(value)
|
||||||
|
|
||||||
new_pset = ifcopenshell.api.run("pset.add_pset", self.file, product=ifc_element, name=prop.pset_name)
|
new_pset = ifcopenshell.api.run("pset.add_pset", self.file, product=ifc_element, name=prop.pset_name)
|
||||||
ifcopenshell.api.run(
|
ifcopenshell.api.run(
|
||||||
"pset.edit_pset", self.file, pset=new_pset, properties={prop.property_name: value_ifc_entity}
|
"pset.edit_pset", self.file, pset=new_pset, properties={prop.property_name: value_ifc_entity}
|
||||||
)
|
)
|
||||||
|
Data.load(IfcStore.get_file(), ifc_definition_id)
|
||||||
self.report({"INFO"}, "Finished applying changes")
|
self.report({"INFO"}, "Finished applying changes")
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
def generate_enum_entity(self, prop):
|
||||||
|
prop_type = prop.get_value_name()
|
||||||
|
prop_enum = self.file.create_entity(
|
||||||
|
"IFCPROPERTYENUMERATION",
|
||||||
|
Name=prop.property_name,
|
||||||
|
EnumerationValues=tuple(
|
||||||
|
self.file.create_entity(prop.primary_measure_type, ev[prop_type]) for ev in prop.enum_values
|
||||||
|
),
|
||||||
|
)
|
||||||
|
prop_enum_value = self.file.create_entity(
|
||||||
|
"IFCPROPERTYENUMERATEDVALUE",
|
||||||
|
Name=prop.property_name,
|
||||||
|
EnumerationValues=tuple(
|
||||||
|
self.file.create_entity(prop.primary_measure_type, ev[prop_type])
|
||||||
|
for ev in prop.enum_values
|
||||||
|
if ev.is_selected == True
|
||||||
|
),
|
||||||
|
EnumerationReference=prop_enum,
|
||||||
|
)
|
||||||
|
return prop_enum_value
|
||||||
|
|
||||||
|
|
||||||
class BIM_OT_bulk_remove_psets(bpy.types.Operator):
|
class BIM_OT_bulk_remove_psets(bpy.types.Operator):
|
||||||
bl_label = "Bulk remove psets from selected objects"
|
bl_label = "Bulk remove psets from selected objects"
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
import blenderbim.bim.schema
|
import blenderbim.bim.schema
|
||||||
from blenderbim.bim.prop import Attribute
|
from blenderbim.bim.prop import Attribute, StrProperty
|
||||||
import ifcopenshell
|
import ifcopenshell
|
||||||
from ifcopenshell.api.pset.data import Data
|
from ifcopenshell.api.pset.data import Data
|
||||||
from blenderbim.bim.module.pset.data import AddEditCustomPropertiesData
|
from blenderbim.bim.module.pset.data import AddEditCustomPropertiesData
|
||||||
@@ -47,7 +47,7 @@ def purge():
|
|||||||
qtonames = {}
|
qtonames = {}
|
||||||
|
|
||||||
|
|
||||||
def getPsetNames(self, context):
|
def get_pset_names(self, context):
|
||||||
global psetnames
|
global psetnames
|
||||||
obj = context.active_object
|
obj = context.active_object
|
||||||
if not obj.BIMObjectProperties.ifc_definition_id:
|
if not obj.BIMObjectProperties.ifc_definition_id:
|
||||||
@@ -122,7 +122,7 @@ def getWorkSchedulePsetNames(self, context):
|
|||||||
return psetnames[ifc_class]
|
return psetnames[ifc_class]
|
||||||
|
|
||||||
|
|
||||||
def getQtoNames(self, context):
|
def get_qto_names(self, context):
|
||||||
global qtonames
|
global qtonames
|
||||||
if "/" in context.active_object.name:
|
if "/" in context.active_object.name:
|
||||||
ifc_class = context.active_object.name.split("/")[0]
|
ifc_class = context.active_object.name.split("/")[0]
|
||||||
@@ -139,18 +139,30 @@ def get_primary_measure_type(self, context):
|
|||||||
return AddEditCustomPropertiesData.data["primary_measure_type"]
|
return AddEditCustomPropertiesData.data["primary_measure_type"]
|
||||||
|
|
||||||
|
|
||||||
|
class IfcPropertyEnumeratedValue(PropertyGroup):
|
||||||
|
enumerated_values: CollectionProperty(type=Attribute)
|
||||||
|
|
||||||
|
|
||||||
|
class IfcProperty(PropertyGroup):
|
||||||
|
metadata: PointerProperty(type=Attribute)
|
||||||
|
value_type: EnumProperty(
|
||||||
|
items=[(v, v, v) for v in ("IfcPropertySingleValue", "IfcPropertyEnumeratedValue")], name="Value Type"
|
||||||
|
)
|
||||||
|
enumerated_value: PointerProperty(type=IfcPropertyEnumeratedValue)
|
||||||
|
|
||||||
|
|
||||||
class PsetProperties(PropertyGroup):
|
class PsetProperties(PropertyGroup):
|
||||||
active_pset_id: IntProperty(name="Active Pset ID")
|
active_pset_id: IntProperty(name="Active Pset ID")
|
||||||
active_pset_name: StringProperty(name="Pset Name")
|
active_pset_name: StringProperty(name="Pset Name")
|
||||||
properties: CollectionProperty(name="Properties", type=Attribute)
|
properties: CollectionProperty(name="Properties", type=IfcProperty)
|
||||||
pset_name: EnumProperty(items=getPsetNames, name="Pset Name")
|
pset_name: EnumProperty(items=get_pset_names, name="Pset Name")
|
||||||
qto_name: EnumProperty(items=getQtoNames, name="Qto Name")
|
qto_name: EnumProperty(items=get_qto_names, name="Qto Name")
|
||||||
|
|
||||||
|
|
||||||
class MaterialPsetProperties(PropertyGroup):
|
class MaterialPsetProperties(PropertyGroup):
|
||||||
active_pset_id: IntProperty(name="Active Pset ID")
|
active_pset_id: IntProperty(name="Active Pset ID")
|
||||||
active_pset_name: StringProperty(name="Pset Name")
|
active_pset_name: StringProperty(name="Pset Name")
|
||||||
properties: CollectionProperty(name="Properties", type=Attribute)
|
properties: CollectionProperty(name="Properties", type=IfcProperty)
|
||||||
pset_name: EnumProperty(items=getMaterialPsetNames, name="Pset Name")
|
pset_name: EnumProperty(items=getMaterialPsetNames, name="Pset Name")
|
||||||
|
|
||||||
|
|
||||||
@@ -172,14 +184,14 @@ class ResourcePsetProperties(PropertyGroup):
|
|||||||
class ProfilePsetProperties(PropertyGroup):
|
class ProfilePsetProperties(PropertyGroup):
|
||||||
active_pset_id: IntProperty(name="Active Pset ID")
|
active_pset_id: IntProperty(name="Active Pset ID")
|
||||||
active_pset_name: StringProperty(name="Pset Name")
|
active_pset_name: StringProperty(name="Pset Name")
|
||||||
properties: CollectionProperty(name="Properties", type=Attribute)
|
properties: CollectionProperty(name="Properties", type=IfcProperty)
|
||||||
pset_name: EnumProperty(items=getProfilePsetNames, name="Pset Name")
|
pset_name: EnumProperty(items=getProfilePsetNames, name="Pset Name")
|
||||||
|
|
||||||
|
|
||||||
class WorkSchedulePsetProperties(PropertyGroup):
|
class WorkSchedulePsetProperties(PropertyGroup):
|
||||||
active_pset_id: IntProperty(name="Active Pset ID")
|
active_pset_id: IntProperty(name="Active Pset ID")
|
||||||
active_pset_name: StringProperty(name="Pset Name")
|
active_pset_name: StringProperty(name="Pset Name")
|
||||||
properties: CollectionProperty(name="Properties", type=Attribute)
|
properties: CollectionProperty(name="Properties", type=IfcProperty)
|
||||||
pset_name: EnumProperty(items=getWorkSchedulePsetNames, name="Pset Name")
|
pset_name: EnumProperty(items=getWorkSchedulePsetNames, name="Pset Name")
|
||||||
|
|
||||||
|
|
||||||
@@ -197,6 +209,14 @@ class AddEditProperties(PropertyGroup):
|
|||||||
int_value: IntProperty(name="Value")
|
int_value: IntProperty(name="Value")
|
||||||
float_value: FloatProperty(name="Value")
|
float_value: FloatProperty(name="Value")
|
||||||
primary_measure_type: EnumProperty(items=get_primary_measure_type, name="Primary Measure Type")
|
primary_measure_type: EnumProperty(items=get_primary_measure_type, name="Primary Measure Type")
|
||||||
|
template_type: EnumProperty(
|
||||||
|
items=[
|
||||||
|
("IfcPropertySingleValue", "IfcPropertySingleValue", "IfcPropertySingleValue"),
|
||||||
|
("IfcPropertyEnumeratedValue", "IfcPropertyEnumeratedValue", "IfcPropertyEnumeratedValue"),
|
||||||
|
],
|
||||||
|
name="Template Type",
|
||||||
|
)
|
||||||
|
enum_values: CollectionProperty(name="Enum Values", type=Attribute)
|
||||||
|
|
||||||
def get_value_name(self):
|
def get_value_name(self):
|
||||||
ifc_data_type = IfcStore.get_schema().declaration_by_name(self.primary_measure_type)
|
ifc_data_type = IfcStore.get_schema().declaration_by_name(self.primary_measure_type)
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
from bpy.types import Panel
|
from bpy.types import Panel
|
||||||
from blenderbim.bim.ifc import IfcStore
|
from blenderbim.bim.ifc import IfcStore
|
||||||
from blenderbim.bim.helper import draw_attribute
|
from blenderbim.bim.helper import prop_with_search
|
||||||
from blenderbim.bim.module.pset.data import (
|
from blenderbim.bim.module.pset.data import (
|
||||||
ObjectPsetsData,
|
ObjectPsetsData,
|
||||||
ObjectQtosData,
|
ObjectQtosData,
|
||||||
@@ -31,6 +31,45 @@ from blenderbim.bim.module.pset.data import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def draw_property(prop, layout, copy_operator=None):
|
||||||
|
if prop.value_type == "IfcPropertySingleValue":
|
||||||
|
draw_single_property(prop, layout, copy_operator)
|
||||||
|
elif prop.value_type == "IfcPropertyEnumeratedValue":
|
||||||
|
draw_enumerated_property(prop, layout, copy_operator)
|
||||||
|
|
||||||
|
|
||||||
|
def draw_single_property(prop, layout, copy_operator=None):
|
||||||
|
value_name = prop.metadata.get_value_name()
|
||||||
|
if not value_name:
|
||||||
|
layout.label(text=prop["Name"])
|
||||||
|
return
|
||||||
|
layout.prop(
|
||||||
|
prop.metadata,
|
||||||
|
value_name,
|
||||||
|
text=prop.metadata.name,
|
||||||
|
)
|
||||||
|
if prop.metadata.is_optional:
|
||||||
|
layout.prop(prop.metadata, "is_null", icon="RADIOBUT_OFF" if prop.metadata.is_null else "RADIOBUT_ON", text="")
|
||||||
|
if copy_operator:
|
||||||
|
op = layout.operator(f"{copy_operator}", text="", icon="COPYDOWN")
|
||||||
|
op.name = prop.metadata.name
|
||||||
|
if prop.metadata.is_uri:
|
||||||
|
op = layout.operator("bim.select_uri_prop", text="", icon="FILE_FOLDER")
|
||||||
|
op.data_path = prop.metadata.path_from_id("string_value")
|
||||||
|
|
||||||
|
|
||||||
|
def draw_enumerated_property(prop, layout, copy_operator=None):
|
||||||
|
value_name = prop.metadata.get_value_name()
|
||||||
|
if not value_name:
|
||||||
|
layout.label(text=prop.metadata.name)
|
||||||
|
return
|
||||||
|
if len(prop.enumerated_value.enumerated_values) != 0:
|
||||||
|
layout.label(text=prop.metadata.name)
|
||||||
|
grid = layout.column_flow(columns=3)
|
||||||
|
for e in prop.enumerated_value.enumerated_values:
|
||||||
|
grid.prop(e, "is_selected", text=str(e[value_name]))
|
||||||
|
|
||||||
|
|
||||||
def get_active_pset_obj_name(context, obj_type):
|
def get_active_pset_obj_name(context, obj_type):
|
||||||
if obj_type == "Object":
|
if obj_type == "Object":
|
||||||
return context.active_object.name
|
return context.active_object.name
|
||||||
@@ -95,7 +134,7 @@ def draw_psetqto_ui(context, pset_id, pset, props, layout, obj_type):
|
|||||||
|
|
||||||
def draw_psetqto_editable_ui(box, props, prop):
|
def draw_psetqto_editable_ui(box, props, prop):
|
||||||
row = box.row(align=True)
|
row = box.row(align=True)
|
||||||
draw_attribute(prop, row, copy_operator="bim.copy_property_to_selection")
|
draw_property(prop, row, copy_operator="bim.copy_property_to_selection")
|
||||||
if (
|
if (
|
||||||
"length" in prop.name.lower()
|
"length" in prop.name.lower()
|
||||||
or "width" in prop.name.lower()
|
or "width" in prop.name.lower()
|
||||||
@@ -138,7 +177,7 @@ class BIM_PT_object_psets(Panel):
|
|||||||
|
|
||||||
props = context.active_object.PsetProperties
|
props = context.active_object.PsetProperties
|
||||||
row = self.layout.row(align=True)
|
row = self.layout.row(align=True)
|
||||||
row.prop(props, "pset_name", text="")
|
prop_with_search(row, props, "pset_name", text="")
|
||||||
op = row.operator("bim.add_pset", icon="ADD", text="")
|
op = row.operator("bim.add_pset", icon="ADD", text="")
|
||||||
op.obj = context.active_object.name
|
op.obj = context.active_object.name
|
||||||
op.obj_type = "Object"
|
op.obj_type = "Object"
|
||||||
@@ -177,7 +216,7 @@ class BIM_PT_object_qtos(Panel):
|
|||||||
|
|
||||||
props = context.active_object.PsetProperties
|
props = context.active_object.PsetProperties
|
||||||
row = self.layout.row(align=True)
|
row = self.layout.row(align=True)
|
||||||
row.prop(props, "qto_name", text="")
|
prop_with_search(row, props, "qto_name", text="")
|
||||||
op = row.operator("bim.add_qto", icon="ADD", text="")
|
op = row.operator("bim.add_qto", icon="ADD", text="")
|
||||||
op.obj = context.active_object.name
|
op.obj = context.active_object.name
|
||||||
op.obj_type = "Object"
|
op.obj_type = "Object"
|
||||||
@@ -213,7 +252,7 @@ class BIM_PT_material_psets(Panel):
|
|||||||
|
|
||||||
props = context.active_object.active_material.PsetProperties
|
props = context.active_object.active_material.PsetProperties
|
||||||
row = self.layout.row(align=True)
|
row = self.layout.row(align=True)
|
||||||
row.prop(props, "pset_name", text="")
|
prop_with_search(row, props, "pset_name", text="")
|
||||||
op = row.operator("bim.add_pset", icon="ADD", text="")
|
op = row.operator("bim.add_pset", icon="ADD", text="")
|
||||||
op.obj = context.active_object.active_material.name
|
op.obj = context.active_object.active_material.name
|
||||||
op.obj_type = "Material"
|
op.obj_type = "Material"
|
||||||
@@ -309,7 +348,7 @@ class BIM_PT_resource_psets(Panel):
|
|||||||
|
|
||||||
props = context.scene.ResourcePsetProperties
|
props = context.scene.ResourcePsetProperties
|
||||||
row = self.layout.row(align=True)
|
row = self.layout.row(align=True)
|
||||||
row.prop(props, "pset_name", text="")
|
prop_with_search(row, props, "pset_name", text="")
|
||||||
op = row.operator("bim.add_pset", icon="ADD", text="")
|
op = row.operator("bim.add_pset", icon="ADD", text="")
|
||||||
op.obj_type = "Resource"
|
op.obj_type = "Resource"
|
||||||
|
|
||||||
@@ -342,7 +381,7 @@ class BIM_PT_profile_psets(Panel):
|
|||||||
|
|
||||||
props = context.scene.ProfilePsetProperties
|
props = context.scene.ProfilePsetProperties
|
||||||
row = self.layout.row(align=True)
|
row = self.layout.row(align=True)
|
||||||
row.prop(props, "pset_name", text="")
|
prop_with_search(row, props, "pset_name", text="")
|
||||||
op = row.operator("bim.add_pset", icon="ADD", text="")
|
op = row.operator("bim.add_pset", icon="ADD", text="")
|
||||||
op.obj_type = "Profile"
|
op.obj_type = "Profile"
|
||||||
|
|
||||||
@@ -371,7 +410,7 @@ class BIM_PT_work_schedule_psets(Panel):
|
|||||||
|
|
||||||
props = context.scene.WorkSchedulePsetProperties
|
props = context.scene.WorkSchedulePsetProperties
|
||||||
row = self.layout.row(align=True)
|
row = self.layout.row(align=True)
|
||||||
row.prop(props, "pset_name", text="")
|
prop_with_search(row, props, "pset_name", text="")
|
||||||
op = row.operator("bim.add_pset", icon="ADD", text="")
|
op = row.operator("bim.add_pset", icon="ADD", text="")
|
||||||
op.obj_type = "WorkSchedule"
|
op.obj_type = "WorkSchedule"
|
||||||
|
|
||||||
@@ -411,7 +450,7 @@ class BIM_PT_rename_parameters(Panel):
|
|||||||
if props:
|
if props:
|
||||||
for index, prop in enumerate(props):
|
for index, prop in enumerate(props):
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
row.prop(prop, "pset_name", text="")
|
prop_with_search(row, prop, "pset_name", text="")
|
||||||
row.prop(prop, "existing_property_name", text="")
|
row.prop(prop, "existing_property_name", text="")
|
||||||
row.prop(prop, "new_property_name", text="")
|
row.prop(prop, "new_property_name", text="")
|
||||||
op = row.operator("bim.remove_property_to_edit", icon="X", text="")
|
op = row.operator("bim.remove_property_to_edit", icon="X", text="")
|
||||||
@@ -441,18 +480,36 @@ class BIM_PT_add_edit_custom_properties(Panel):
|
|||||||
row = layout.row()
|
row = layout.row()
|
||||||
op = row.operator("bim.add_property_to_edit", icon="ADD")
|
op = row.operator("bim.add_property_to_edit", icon="ADD")
|
||||||
op.option = "AddEditProperties"
|
op.option = "AddEditProperties"
|
||||||
|
op.index = -1
|
||||||
|
|
||||||
if props:
|
if props:
|
||||||
for index, prop in enumerate(props):
|
for index, prop in enumerate(props):
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
row.prop(prop, "pset_name", text="")
|
prop_with_search(row, prop, "pset_name", text="")
|
||||||
row.prop(prop, "property_name", text="")
|
row.prop(prop, "property_name", text="")
|
||||||
row.prop(prop, prop.get_value_name(), text="")
|
if prop.template_type == "IfcPropertySingleValue":
|
||||||
row.prop(prop, "primary_measure_type", text="")
|
row.prop(prop, prop.get_value_name(), text="")
|
||||||
|
prop_with_search(row, prop, "primary_measure_type", text="")
|
||||||
|
row.prop(prop, "template_type", text="")
|
||||||
op = row.operator("bim.remove_property_to_edit", icon="X", text="")
|
op = row.operator("bim.remove_property_to_edit", icon="X", text="")
|
||||||
op.index = index
|
op.index = index
|
||||||
op.option = "AddEditProperties"
|
op.option = "AddEditProperties"
|
||||||
|
|
||||||
|
if prop.template_type == "IfcPropertyEnumeratedValue":
|
||||||
|
op = row.operator("bim.add_property_to_edit", icon="ADD", text="Add Enum")
|
||||||
|
op.option = "AddEditProperties"
|
||||||
|
op.index = index
|
||||||
|
for index2, prop2 in enumerate(prop.enum_values):
|
||||||
|
row = layout.row()
|
||||||
|
row.separator()
|
||||||
|
row.separator()
|
||||||
|
row.prop(prop2, prop.get_value_name(), text=f"#{index2}")
|
||||||
|
row.prop(prop2, "is_selected")
|
||||||
|
op = row.operator("bim.remove_property_to_edit", icon="X", text="")
|
||||||
|
op.index = index
|
||||||
|
op.index2 = index2
|
||||||
|
op.option = "AddEditProperties"
|
||||||
|
|
||||||
if props:
|
if props:
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
op = row.operator("bim.add_edit_custom_property", icon="CHECKMARK", text="Apply Changes")
|
op = row.operator("bim.add_edit_custom_property", icon="CHECKMARK", text="Apply Changes")
|
||||||
@@ -480,7 +537,7 @@ class BIM_PT_delete_psets(Panel):
|
|||||||
if props:
|
if props:
|
||||||
for index, prop in enumerate(props):
|
for index, prop in enumerate(props):
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
row.prop(prop, "pset_name", text="")
|
prop_with_search(row, prop, "pset_name", text="")
|
||||||
op = row.operator("bim.remove_property_to_edit", icon="X", text="")
|
op = row.operator("bim.remove_property_to_edit", icon="X", text="")
|
||||||
op.index = index
|
op.index = index
|
||||||
op.option = "DeletePsets"
|
op.option = "DeletePsets"
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ import bpy
|
|||||||
from . import ui, prop, operator
|
from . import ui, prop, operator
|
||||||
|
|
||||||
classes = (
|
classes = (
|
||||||
|
operator.RefreshPsetTemplates,
|
||||||
|
operator.AddPsetFile,
|
||||||
operator.SavePsetTemplateFile,
|
operator.SavePsetTemplateFile,
|
||||||
operator.AddPsetTemplate,
|
operator.AddPsetTemplate,
|
||||||
operator.EditPsetTemplate,
|
operator.EditPsetTemplate,
|
||||||
@@ -30,8 +32,11 @@ classes = (
|
|||||||
operator.EnableEditingPsetTemplate,
|
operator.EnableEditingPsetTemplate,
|
||||||
operator.DisableEditingPsetTemplate,
|
operator.DisableEditingPsetTemplate,
|
||||||
operator.EnableEditingPropTemplate,
|
operator.EnableEditingPropTemplate,
|
||||||
|
operator.DeletePropEnum,
|
||||||
|
operator.AddPropEnum,
|
||||||
operator.DisableEditingPropTemplate,
|
operator.DisableEditingPropTemplate,
|
||||||
prop.PsetTemplate,
|
prop.PsetTemplate,
|
||||||
|
prop.EnumerationValues,
|
||||||
prop.PropTemplate,
|
prop.PropTemplate,
|
||||||
prop.BIMPsetTemplateProperties,
|
prop.BIMPsetTemplateProperties,
|
||||||
ui.BIM_PT_pset_template,
|
ui.BIM_PT_pset_template,
|
||||||
|
|||||||
@@ -16,16 +16,66 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import os
|
||||||
import bpy
|
import bpy
|
||||||
import ifcopenshell
|
import ifcopenshell
|
||||||
import ifcopenshell.api
|
import ifcopenshell.api
|
||||||
import blenderbim.bim.schema
|
import blenderbim.bim.schema
|
||||||
import blenderbim.bim.handler
|
import blenderbim.bim.handler
|
||||||
from blenderbim.bim.module.pset_template.prop import updatePsetTemplateFiles, updatePsetTemplates, getPsetTemplates
|
from blenderbim.bim.module.pset_template.prop import purge as purge_templates, updatePsetTemplates, getPsetTemplates
|
||||||
|
from blenderbim.bim.module.pset.prop import purge as purge_psets
|
||||||
from ifcopenshell.api.pset_template.data import Data
|
from ifcopenshell.api.pset_template.data import Data
|
||||||
from blenderbim.bim.ifc import IfcStore
|
from blenderbim.bim.ifc import IfcStore
|
||||||
|
|
||||||
|
|
||||||
|
# This is just a temporary operator until
|
||||||
|
# @Moult performs some of his refactor-magic ;) - vulevukusej
|
||||||
|
class RefreshPsetTemplates(bpy.types.Operator):
|
||||||
|
bl_idname = "bim.refresh_psettemplates"
|
||||||
|
bl_label = "Refresh the data for PsetTemplates"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
purge_templates()
|
||||||
|
purge_psets()
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
class AddPsetFile(bpy.types.Operator):
|
||||||
|
bl_idname = "bim.add_pset_file"
|
||||||
|
bl_label = "Add Pset File"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
return context.window_manager.invoke_props_dialog(self, width=250)
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
self.props = context.scene.BIMPsetTemplateProperties
|
||||||
|
self.layout.prop(self.props, "new_template_filename", text="Filename:")
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
template = ifcopenshell.file()
|
||||||
|
filepath = os.path.join(
|
||||||
|
context.scene.BIMProperties.data_dir,
|
||||||
|
"pset",
|
||||||
|
self.props.new_template_filename + ".ifc",
|
||||||
|
)
|
||||||
|
|
||||||
|
template.create_entity(
|
||||||
|
"IFCPROPERTYSETTEMPLATE",
|
||||||
|
**{
|
||||||
|
"GlobalId": ifcopenshell.guid.new(),
|
||||||
|
"Name": "Name",
|
||||||
|
"Description": "Description",
|
||||||
|
"TemplateType": "PSET_TYPEDRIVENONLY",
|
||||||
|
"ApplicableEntity": "IfcTypeObject",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
template.write(filepath)
|
||||||
|
self.props.new_template_filename = ""
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
class AddPsetTemplate(bpy.types.Operator):
|
class AddPsetTemplate(bpy.types.Operator):
|
||||||
bl_idname = "bim.add_pset_template"
|
bl_idname = "bim.add_pset_template"
|
||||||
bl_label = "Add Pset Template"
|
bl_label = "Add Pset Template"
|
||||||
@@ -41,7 +91,6 @@ class AddPsetTemplate(bpy.types.Operator):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
def _execute(self, context):
|
def _execute(self, context):
|
||||||
props = context.scene.BIMPsetTemplateProperties
|
|
||||||
ifcopenshell.api.run("pset_template.add_pset_template", IfcStore.pset_template_file)
|
ifcopenshell.api.run("pset_template.add_pset_template", IfcStore.pset_template_file)
|
||||||
Data.load(IfcStore.pset_template_file)
|
Data.load(IfcStore.pset_template_file)
|
||||||
updatePsetTemplates(self, context)
|
updatePsetTemplates(self, context)
|
||||||
@@ -139,6 +188,40 @@ class EnableEditingPropTemplate(bpy.types.Operator):
|
|||||||
props.active_prop_template.name = template["Name"] or ""
|
props.active_prop_template.name = template["Name"] or ""
|
||||||
props.active_prop_template.description = template["Description"] or ""
|
props.active_prop_template.description = template["Description"] or ""
|
||||||
props.active_prop_template.primary_measure_type = template["PrimaryMeasureType"]
|
props.active_prop_template.primary_measure_type = template["PrimaryMeasureType"]
|
||||||
|
props.active_prop_template.template_type = template["TemplateType"]
|
||||||
|
props.active_prop_template.enum_values.clear()
|
||||||
|
|
||||||
|
if template["Enumerators"]:
|
||||||
|
props.active_prop_template.enum_values.clear()
|
||||||
|
data_type = props.active_prop_template.get_value_name()
|
||||||
|
for e in template["Enumerators"].EnumerationValues:
|
||||||
|
new = props.active_prop_template.enum_values.add()
|
||||||
|
setattr(new, data_type, e.wrappedValue)
|
||||||
|
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
class DeletePropEnum(bpy.types.Operator):
|
||||||
|
bl_idname = "bim.delete_prop_enum"
|
||||||
|
bl_label = "delete property enumeration"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
index: bpy.props.IntProperty()
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
active_prop = context.scene.BIMPsetTemplateProperties.active_prop_template
|
||||||
|
active_prop.enum_values.remove(self.index)
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
class AddPropEnum(bpy.types.Operator):
|
||||||
|
bl_idname = "bim.add_prop_enum"
|
||||||
|
bl_label = "add property enumeration"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
index: bpy.props.IntProperty()
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
active_prop = context.scene.BIMPsetTemplateProperties.active_prop_template
|
||||||
|
active_prop.enum_values.add()
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
@@ -285,6 +368,10 @@ class EditPropTemplate(bpy.types.Operator):
|
|||||||
|
|
||||||
def _execute(self, context):
|
def _execute(self, context):
|
||||||
props = context.scene.BIMPsetTemplateProperties
|
props = context.scene.BIMPsetTemplateProperties
|
||||||
|
if props.active_prop_template.template_type == "P_ENUMERATEDVALUE":
|
||||||
|
enumerator = self.generate_prop_enum(props)
|
||||||
|
else:
|
||||||
|
enumerator = None
|
||||||
ifcopenshell.api.run(
|
ifcopenshell.api.run(
|
||||||
"pset_template.edit_prop_template",
|
"pset_template.edit_prop_template",
|
||||||
IfcStore.pset_template_file,
|
IfcStore.pset_template_file,
|
||||||
@@ -294,6 +381,8 @@ class EditPropTemplate(bpy.types.Operator):
|
|||||||
"Name": props.active_prop_template.name,
|
"Name": props.active_prop_template.name,
|
||||||
"Description": props.active_prop_template.description,
|
"Description": props.active_prop_template.description,
|
||||||
"PrimaryMeasureType": props.active_prop_template.primary_measure_type,
|
"PrimaryMeasureType": props.active_prop_template.primary_measure_type,
|
||||||
|
"TemplateType": props.active_prop_template.template_type,
|
||||||
|
"Enumerators": enumerator,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -301,6 +390,21 @@ class EditPropTemplate(bpy.types.Operator):
|
|||||||
bpy.ops.bim.disable_editing_prop_template()
|
bpy.ops.bim.disable_editing_prop_template()
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
# TODO -This will need to go into the
|
||||||
|
# api code at some point - vulevukusej
|
||||||
|
def generate_prop_enum(self, props):
|
||||||
|
self.file = IfcStore.pset_template_file
|
||||||
|
data_type = props.active_prop_template.get_value_name()
|
||||||
|
prop = props.active_prop_template
|
||||||
|
prop_enum = self.file.create_entity(
|
||||||
|
"IFCPROPERTYENUMERATION",
|
||||||
|
Name=prop.name,
|
||||||
|
EnumerationValues=tuple(
|
||||||
|
self.file.create_entity(prop.primary_measure_type, ev[data_type]) for ev in prop.enum_values
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return prop_enum
|
||||||
|
|
||||||
def rollback(self, data):
|
def rollback(self, data):
|
||||||
IfcStore.pset_template_file.undo()
|
IfcStore.pset_template_file.undo()
|
||||||
|
|
||||||
|
|||||||
@@ -94,63 +94,88 @@ def get_primary_measure_type(self, context):
|
|||||||
return PsetTemplatesData.data["primary_measure_type"]
|
return PsetTemplatesData.data["primary_measure_type"]
|
||||||
|
|
||||||
|
|
||||||
|
def get_template_type(self, context):
|
||||||
|
return [
|
||||||
|
(
|
||||||
|
"PSET_TYPEDRIVENONLY",
|
||||||
|
"Pset - IfcTypeObject",
|
||||||
|
"The property sets defined by this IfcPropertySetTemplate can only be assigned to subtypes of IfcTypeObject.",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"PSET_TYPEDRIVENOVERRIDE",
|
||||||
|
"Pset - IfcTypeObject - Override",
|
||||||
|
"The property sets defined by this IfcPropertySetTemplate can only be assigned to subtypes of IfcTypeObject.",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"PSET_OCCURRENCEDRIVEN",
|
||||||
|
"Pset - IfcObject",
|
||||||
|
"The property sets defined by this IfcPropertySetTemplate can only be assigned to subtypes of IfcObject.",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"PSET_PERFORMANCEDRIVEN",
|
||||||
|
"Pset - IfcPerformanceHistory",
|
||||||
|
"The property sets defined by this IfcPropertySetTemplate can only be assigned to IfcPerformanceHistory.",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"QTO_TYPEDRIVENONLY",
|
||||||
|
"Qto - IfcTypeObject",
|
||||||
|
"The element quantity defined by this IfcPropertySetTemplate can only be assigned to subtypes of IfcTypeObject.",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"QTO_TYPEDRIVENOVERRIDE",
|
||||||
|
"Qto - IfcTypeObject - Override",
|
||||||
|
"The element quantity defined by this IfcPropertySetTemplate can be assigned to subtypes of IfcTypeObject and can be overridden by an element quantity with same name at subtypes of IfcObject.",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"QTO_OCCURRENCEDRIVEN",
|
||||||
|
"Qto - IfcObject",
|
||||||
|
"The element quantity defined by this IfcPropertySetTemplate can only be assigned to subtypes of IfcObject.",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"NOTDEFINED",
|
||||||
|
"Not defined",
|
||||||
|
"No restriction provided, the property sets defined by this IfcPropertySetTemplate can be assigned to any entity, if not otherwise restricted by the ApplicableEntity attribute.",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class PsetTemplate(PropertyGroup):
|
class PsetTemplate(PropertyGroup):
|
||||||
global_id: StringProperty(name="Global ID")
|
global_id: StringProperty(name="Global ID")
|
||||||
name: StringProperty(name="Name")
|
name: StringProperty(name="Name")
|
||||||
description: StringProperty(name="Description")
|
description: StringProperty(name="Description")
|
||||||
template_type: EnumProperty(
|
template_type: EnumProperty(items=get_template_type, name="Template Type")
|
||||||
items=[
|
|
||||||
(
|
|
||||||
"PSET_TYPEDRIVENONLY",
|
|
||||||
"Pset - IfcTypeObject",
|
|
||||||
"The property sets defined by this IfcPropertySetTemplate can only be assigned to subtypes of IfcTypeObject.",
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"PSET_TYPEDRIVENOVERRIDE",
|
|
||||||
"Pset - IfcTypeObject - Override",
|
|
||||||
"The property sets defined by this IfcPropertySetTemplate can only be assigned to subtypes of IfcTypeObject.",
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"PSET_OCCURRENCEDRIVEN",
|
|
||||||
"Pset - IfcObject",
|
|
||||||
"The property sets defined by this IfcPropertySetTemplate can only be assigned to subtypes of IfcObject.",
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"PSET_PERFORMANCEDRIVEN",
|
|
||||||
"Pset - IfcPerformanceHistory",
|
|
||||||
"The property sets defined by this IfcPropertySetTemplate can only be assigned to IfcPerformanceHistory.",
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"QTO_TYPEDRIVENONLY",
|
|
||||||
"Qto - IfcTypeObject",
|
|
||||||
"The element quantity defined by this IfcPropertySetTemplate can only be assigned to subtypes of IfcTypeObject.",
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"QTO_TYPEDRIVENOVERRIDE",
|
|
||||||
"Qto - IfcTypeObject - Override",
|
|
||||||
"The element quantity defined by this IfcPropertySetTemplate can be assigned to subtypes of IfcTypeObject and can be overridden by an element quantity with same name at subtypes of IfcObject.",
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"QTO_OCCURRENCEDRIVEN",
|
|
||||||
"Qto - IfcObject",
|
|
||||||
"The element quantity defined by this IfcPropertySetTemplate can only be assigned to subtypes of IfcObject.",
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"NOTDEFINED",
|
|
||||||
"Not defined",
|
|
||||||
"No restriction provided, the property sets defined by this IfcPropertySetTemplate can be assigned to any entity, if not otherwise restricted by the ApplicableEntity attribute.",
|
|
||||||
),
|
|
||||||
],
|
|
||||||
name="Template Type",
|
|
||||||
)
|
|
||||||
applicable_entity: StringProperty(name="Applicable Entity")
|
applicable_entity: StringProperty(name="Applicable Entity")
|
||||||
|
|
||||||
|
|
||||||
|
class EnumerationValues(PropertyGroup):
|
||||||
|
string_value: StringProperty(name="Value")
|
||||||
|
bool_value: BoolProperty(name="Value")
|
||||||
|
int_value: IntProperty(name="Value")
|
||||||
|
float_value: FloatProperty(name="Value")
|
||||||
|
|
||||||
|
|
||||||
class PropTemplate(PropertyGroup):
|
class PropTemplate(PropertyGroup):
|
||||||
global_id: StringProperty(name="Global ID")
|
global_id: StringProperty(name="Global ID")
|
||||||
name: StringProperty(name="Name")
|
name: StringProperty(name="Name")
|
||||||
description: StringProperty(name="Description")
|
description: StringProperty(name="Description")
|
||||||
primary_measure_type: EnumProperty(items=get_primary_measure_type, name="Primary Measure Type")
|
primary_measure_type: EnumProperty(items=get_primary_measure_type, name="Primary Measure Type")
|
||||||
|
template_type: EnumProperty(
|
||||||
|
items=[("P_SINGLEVALUE", "P_SINGLEVALUE", ""), ("P_ENUMERATEDVALUE", "P_ENUMERATEDVALUE", "")],
|
||||||
|
name="Template Type",
|
||||||
|
)
|
||||||
|
enum_values: CollectionProperty(type=EnumerationValues)
|
||||||
|
|
||||||
|
def get_value_name(self):
|
||||||
|
ifc_data_type = IfcStore.get_schema().declaration_by_name(self.primary_measure_type)
|
||||||
|
data_type = ifcopenshell.util.attribute.get_primitive_type(ifc_data_type)
|
||||||
|
if data_type == "string":
|
||||||
|
return "string_value"
|
||||||
|
elif data_type == "boolean":
|
||||||
|
return "bool_value"
|
||||||
|
elif data_type == "integer":
|
||||||
|
return "int_value"
|
||||||
|
elif data_type == "float":
|
||||||
|
return "float_value"
|
||||||
|
|
||||||
|
|
||||||
class BIMPsetTemplateProperties(PropertyGroup):
|
class BIMPsetTemplateProperties(PropertyGroup):
|
||||||
@@ -162,3 +187,4 @@ class BIMPsetTemplateProperties(PropertyGroup):
|
|||||||
active_prop_template_id: IntProperty(name="Active Prop Template Id")
|
active_prop_template_id: IntProperty(name="Active Prop Template Id")
|
||||||
active_pset_template: PointerProperty(type=PsetTemplate)
|
active_pset_template: PointerProperty(type=PsetTemplate)
|
||||||
active_prop_template: PointerProperty(type=PropTemplate)
|
active_prop_template: PointerProperty(type=PropTemplate)
|
||||||
|
new_template_filename: StringProperty("New TemplateFileName")
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
import bpy
|
import bpy
|
||||||
from bpy.types import Panel
|
from bpy.types import Panel
|
||||||
from blenderbim.bim.ifc import IfcStore
|
from blenderbim.bim.ifc import IfcStore
|
||||||
|
from blenderbim.bim.helper import prop_with_search
|
||||||
from blenderbim.bim.module.pset_template.prop import (
|
from blenderbim.bim.module.pset_template.prop import (
|
||||||
getPsetTemplates,
|
getPsetTemplates,
|
||||||
)
|
)
|
||||||
@@ -39,13 +40,15 @@ class BIM_PT_pset_template(Panel):
|
|||||||
props = context.scene.BIMPsetTemplateProperties
|
props = context.scene.BIMPsetTemplateProperties
|
||||||
|
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
row.prop(props, "pset_template_files", text="")
|
prop_with_search(row, props, "pset_template_files", text="")
|
||||||
row.operator("bim.save_pset_template_file", text="", icon="EXPORT")
|
row.operator("bim.save_pset_template_file", text="", icon="EXPORT")
|
||||||
|
row.operator("bim.add_pset_file", icon="ADD", text="")
|
||||||
|
row.operator("bim.refresh_psettemplates", icon="FILE_REFRESH", text="")
|
||||||
|
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
|
|
||||||
if bool(getPsetTemplates(props, context)):
|
if bool(getPsetTemplates(props, context)):
|
||||||
row.prop(props, "pset_templates", text="", icon="COPY_ID")
|
prop_with_search(row, props, "pset_templates", text="", icon="COPY_ID")
|
||||||
else:
|
else:
|
||||||
row.label(text="No Pset Templates", icon="COPY_ID")
|
row.label(text="No Pset Templates", icon="COPY_ID")
|
||||||
|
|
||||||
@@ -69,8 +72,7 @@ class BIM_PT_pset_template(Panel):
|
|||||||
row.prop(props.active_pset_template, "name")
|
row.prop(props.active_pset_template, "name")
|
||||||
row = layout.row()
|
row = layout.row()
|
||||||
row.prop(props.active_pset_template, "description")
|
row.prop(props.active_pset_template, "description")
|
||||||
row = layout.row()
|
prop_with_search(layout, props.active_pset_template, "template_type")
|
||||||
row.prop(props.active_pset_template, "template_type")
|
|
||||||
row = layout.row()
|
row = layout.row()
|
||||||
row.prop(props.active_pset_template, "applicable_entity")
|
row.prop(props.active_pset_template, "applicable_entity")
|
||||||
else:
|
else:
|
||||||
@@ -86,6 +88,9 @@ class BIM_PT_pset_template(Panel):
|
|||||||
row.label(text="Property Templates", icon="COPY_ID")
|
row.label(text="Property Templates", icon="COPY_ID")
|
||||||
row.operator("bim.add_prop_template", icon="ADD", text="")
|
row.operator("bim.add_prop_template", icon="ADD", text="")
|
||||||
|
|
||||||
|
row = layout.row()
|
||||||
|
row.label(text="Name | Description | PrimaryMeasureType | TemplateType")
|
||||||
|
|
||||||
for prop_template_id in pset_template["HasPropertyTemplates"]:
|
for prop_template_id in pset_template["HasPropertyTemplates"]:
|
||||||
prop_template = Data.prop_templates[prop_template_id]
|
prop_template = Data.prop_templates[prop_template_id]
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
@@ -93,11 +98,34 @@ class BIM_PT_pset_template(Panel):
|
|||||||
if props.active_prop_template_id and props.active_prop_template_id == prop_template_id:
|
if props.active_prop_template_id and props.active_prop_template_id == prop_template_id:
|
||||||
row.prop(props.active_prop_template, "name", text="")
|
row.prop(props.active_prop_template, "name", text="")
|
||||||
row.prop(props.active_prop_template, "description", text="")
|
row.prop(props.active_prop_template, "description", text="")
|
||||||
row.prop(props.active_prop_template, "primary_measure_type", text="")
|
prop_with_search(row, props.active_prop_template, "primary_measure_type", text="")
|
||||||
|
row.prop(props.active_prop_template, "template_type", text="")
|
||||||
|
|
||||||
|
if props.active_prop_template.template_type == "P_ENUMERATEDVALUE":
|
||||||
|
row = layout.row(align=True)
|
||||||
|
split = row.split(factor=0.2)
|
||||||
|
c = split.column()
|
||||||
|
c.label(
|
||||||
|
text="Enumerations:",
|
||||||
|
)
|
||||||
|
|
||||||
|
c = split.column()
|
||||||
|
box = c.box()
|
||||||
|
grid = box.column_flow(columns=4)
|
||||||
|
value_name = props.active_prop_template.get_value_name()
|
||||||
|
r = grid.row()
|
||||||
|
r.operator("bim.add_prop_enum", text="Add Enum")
|
||||||
|
for k, v in enumerate(props.active_prop_template.enum_values):
|
||||||
|
r = grid.row(align=True)
|
||||||
|
r.prop(v, value_name, text="")
|
||||||
|
op = r.operator("bim.delete_prop_enum", icon="X", text="")
|
||||||
|
op.index = k
|
||||||
|
|
||||||
else:
|
else:
|
||||||
row.label(text=prop_template["Name"])
|
row.label(text=prop_template["Name"])
|
||||||
row.label(text=prop_template["Description"])
|
row.label(text=prop_template["Description"])
|
||||||
row.label(text=prop_template["PrimaryMeasureType"])
|
row.label(text=prop_template["PrimaryMeasureType"])
|
||||||
|
row.label(text=prop_template["TemplateType"])
|
||||||
|
|
||||||
if props.active_prop_template_id and props.active_prop_template_id == prop_template_id:
|
if props.active_prop_template_id and props.active_prop_template_id == prop_template_id:
|
||||||
op = row.operator("bim.edit_prop_template", icon="CHECKMARK", text="")
|
op = row.operator("bim.edit_prop_template", icon="CHECKMARK", text="")
|
||||||
|
|||||||
@@ -16,9 +16,11 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from collections import defaultdict
|
||||||
import bpy
|
import bpy
|
||||||
import ifcopenshell.util.element
|
import ifcopenshell.util.element
|
||||||
import blenderbim.tool as tool
|
import blenderbim.tool as tool
|
||||||
|
from blenderbim.bim.ifc import IfcStore
|
||||||
|
|
||||||
|
|
||||||
def refresh():
|
def refresh():
|
||||||
@@ -35,6 +37,7 @@ class IfcClassData:
|
|||||||
cls.data = {}
|
cls.data = {}
|
||||||
cls.data["ifc_products"] = cls.ifc_products()
|
cls.data["ifc_products"] = cls.ifc_products()
|
||||||
cls.data["ifc_classes"] = cls.ifc_classes()
|
cls.data["ifc_classes"] = cls.ifc_classes()
|
||||||
|
cls.data["ifc_classes_suggestions"] = cls.ifc_classes_suggestions()
|
||||||
cls.data["contexts"] = cls.contexts()
|
cls.data["contexts"] = cls.contexts()
|
||||||
cls.data["has_entity"] = cls.has_entity()
|
cls.data["has_entity"] = cls.has_entity()
|
||||||
cls.data["name"] = cls.name()
|
cls.data["name"] = cls.name()
|
||||||
@@ -74,6 +77,41 @@ class IfcClassData:
|
|||||||
names.extend(("IfcDoorStyle", "IfcWindowStyle"))
|
names.extend(("IfcDoorStyle", "IfcWindowStyle"))
|
||||||
return [(c, c, "") for c in sorted(names)]
|
return [(c, c, "") for c in sorted(names)]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def ifc_classes_suggestions(cls):
|
||||||
|
suggestions = defaultdict(list)
|
||||||
|
suggestions.update(
|
||||||
|
{
|
||||||
|
"IfcWall": ["Glazing", "Glass", "Pane"],
|
||||||
|
"IfcWindow": ["Glazing", "Glass", "Pane"],
|
||||||
|
"IfcPlate": ["Glazing", "Glass", "Pane"],
|
||||||
|
"IfcFurniture": ["Signage"],
|
||||||
|
"IfcSlab": ["Hob"],
|
||||||
|
"IfcCovering": ["Flashing", "Capping"],
|
||||||
|
"IfcCableSegment": ["Lighting Rod"],
|
||||||
|
"IfcSensor": ["Card Reader", "Fob Reader"],
|
||||||
|
"IfcSwitchingDevice": ["Reed Switch", "Electric Isolating Switch"],
|
||||||
|
"IfcActuator": ["Electric Strike"],
|
||||||
|
"IfcAirTerminalBox": ["VAV Box"],
|
||||||
|
"IfcUnitaryEquipment": ["Fan Coil Unit"],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
file = IfcStore.get_file()
|
||||||
|
if file:
|
||||||
|
for ifc_class in cls.ifc_classes():
|
||||||
|
ifc_class = ifc_class[0]
|
||||||
|
declaration = IfcStore.get_schema().declaration_by_name(ifc_class)
|
||||||
|
for attribute in declaration.attributes():
|
||||||
|
if attribute.name() == "PredefinedType":
|
||||||
|
for e in attribute.type_of_attribute().declared_type().enumeration_items():
|
||||||
|
if e in (
|
||||||
|
"NOTDEFINED",
|
||||||
|
"USERDEFINED",
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
suggestions[ifc_class].append(e.title())
|
||||||
|
return suggestions
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def contexts(cls):
|
def contexts(cls):
|
||||||
results = []
|
results = []
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ import blenderbim.core.root as core
|
|||||||
import blenderbim.tool as tool
|
import blenderbim.tool as tool
|
||||||
from ifcopenshell.api.void.data import Data as VoidData
|
from ifcopenshell.api.void.data import Data as VoidData
|
||||||
from blenderbim.bim.ifc import IfcStore
|
from blenderbim.bim.ifc import IfcStore
|
||||||
from blenderbim.bim.module.root.prop import get_contexts
|
from blenderbim.bim.helper import get_enum_items
|
||||||
|
|
||||||
|
|
||||||
class Operator:
|
class Operator:
|
||||||
@@ -127,12 +127,13 @@ class AssignClass(bpy.types.Operator, Operator):
|
|||||||
ifc_representation_class: bpy.props.StringProperty()
|
ifc_representation_class: bpy.props.StringProperty()
|
||||||
|
|
||||||
def _execute(self, context):
|
def _execute(self, context):
|
||||||
|
props = context.scene.BIMRootProperties
|
||||||
objects = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects
|
objects = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects
|
||||||
ifc_class = self.ifc_class or context.scene.BIMRootProperties.ifc_class
|
ifc_class = self.ifc_class or props.ifc_class
|
||||||
predefined_type = self.userdefined_type if self.predefined_type == "USERDEFINED" else self.predefined_type
|
predefined_type = self.userdefined_type if self.predefined_type == "USERDEFINED" else self.predefined_type
|
||||||
ifc_context = self.context_id
|
ifc_context = self.context_id
|
||||||
if not ifc_context and get_contexts(self, context):
|
if not ifc_context and get_enum_items(props, "contexts", context):
|
||||||
ifc_context = int(context.scene.BIMRootProperties.contexts or "0") or None
|
ifc_context = int(props.contexts or "0") or None
|
||||||
if ifc_context:
|
if ifc_context:
|
||||||
ifc_context = tool.Ifc.get().by_id(ifc_context)
|
ifc_context = tool.Ifc.get().by_id(ifc_context)
|
||||||
active_object = context.active_object
|
active_object = context.active_object
|
||||||
|
|||||||
@@ -34,11 +34,14 @@ from bpy.props import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
types_enum = []
|
types_enum = []
|
||||||
|
classes_enum = []
|
||||||
|
|
||||||
|
|
||||||
def purge():
|
def purge():
|
||||||
global types_enum
|
global types_enum
|
||||||
types_enum = []
|
types_enum = []
|
||||||
|
global classes_enum
|
||||||
|
classes_enum = []
|
||||||
|
|
||||||
|
|
||||||
def getIfcPredefinedTypes(self, context):
|
def getIfcPredefinedTypes(self, context):
|
||||||
@@ -69,6 +72,10 @@ def refreshPredefinedTypes(self, context):
|
|||||||
context.scene.BIMRootProperties.ifc_predefined_type = enum[0][0]
|
context.scene.BIMRootProperties.ifc_predefined_type = enum[0][0]
|
||||||
|
|
||||||
|
|
||||||
|
def update_class_enum(self, context):
|
||||||
|
self.ifc_class = self.ifc_class_filter_enum
|
||||||
|
|
||||||
|
|
||||||
def get_ifc_products(self, context):
|
def get_ifc_products(self, context):
|
||||||
if not IfcClassData.is_loaded:
|
if not IfcClassData.is_loaded:
|
||||||
IfcClassData.load()
|
IfcClassData.load()
|
||||||
@@ -81,6 +88,12 @@ def get_ifc_classes(self, context):
|
|||||||
return IfcClassData.data["ifc_classes"]
|
return IfcClassData.data["ifc_classes"]
|
||||||
|
|
||||||
|
|
||||||
|
def get_ifc_classes_suggestions():
|
||||||
|
if not IfcClassData.is_loaded:
|
||||||
|
IfcClassData.load()
|
||||||
|
return IfcClassData.data["ifc_classes_suggestions"]
|
||||||
|
|
||||||
|
|
||||||
def get_contexts(self, context):
|
def get_contexts(self, context):
|
||||||
if not IfcClassData.is_loaded:
|
if not IfcClassData.is_loaded:
|
||||||
IfcClassData.load()
|
IfcClassData.load()
|
||||||
@@ -93,3 +106,7 @@ class BIMRootProperties(PropertyGroup):
|
|||||||
ifc_class: EnumProperty(items=get_ifc_classes, name="Class", update=refreshPredefinedTypes)
|
ifc_class: EnumProperty(items=get_ifc_classes, name="Class", update=refreshPredefinedTypes)
|
||||||
ifc_predefined_type: EnumProperty(items=getIfcPredefinedTypes, name="Predefined Type", default=None)
|
ifc_predefined_type: EnumProperty(items=getIfcPredefinedTypes, name="Predefined Type", default=None)
|
||||||
ifc_userdefined_type: StringProperty(name="Userdefined Type")
|
ifc_userdefined_type: StringProperty(name="Userdefined Type")
|
||||||
|
|
||||||
|
getter_enum_suggestions = {
|
||||||
|
"ifc_class": get_ifc_classes_suggestions,
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import bpy
|
|||||||
import blenderbim.bim.module.root.prop as root_prop
|
import blenderbim.bim.module.root.prop as root_prop
|
||||||
from bpy.types import Panel
|
from bpy.types import Panel
|
||||||
from blenderbim.bim.ifc import IfcStore
|
from blenderbim.bim.ifc import IfcStore
|
||||||
|
from blenderbim.bim.helper import prop_with_search
|
||||||
from blenderbim.bim.module.root.data import IfcClassData
|
from blenderbim.bim.module.root.data import IfcClassData
|
||||||
|
|
||||||
|
|
||||||
@@ -76,17 +77,14 @@ class BIM_PT_class(Panel):
|
|||||||
|
|
||||||
def draw_class_dropdowns(self, context, ifc_predefined_types, is_reassigning_class=False):
|
def draw_class_dropdowns(self, context, ifc_predefined_types, is_reassigning_class=False):
|
||||||
props = context.scene.BIMRootProperties
|
props = context.scene.BIMRootProperties
|
||||||
|
layout = self.layout
|
||||||
if not is_reassigning_class:
|
if not is_reassigning_class:
|
||||||
row = self.layout.row()
|
prop_with_search(layout, props, "ifc_product")
|
||||||
row.prop(props, "ifc_product")
|
prop_with_search(layout, props, "ifc_class")
|
||||||
row = self.layout.row()
|
|
||||||
row.prop(props, "ifc_class")
|
|
||||||
if ifc_predefined_types:
|
if ifc_predefined_types:
|
||||||
row = self.layout.row()
|
prop_with_search(layout, props, "ifc_predefined_type")
|
||||||
row.prop(props, "ifc_predefined_type")
|
if props.ifc_predefined_type == "USERDEFINED":
|
||||||
if ifc_predefined_types == "USERDEFINED":
|
row = layout.row()
|
||||||
row = self.layout.row()
|
row.prop(props, "ifc_userdefined_type")
|
||||||
row.prop(props, "ifc_userdefined_type")
|
|
||||||
if not is_reassigning_class:
|
if not is_reassigning_class:
|
||||||
row = self.layout.row()
|
prop_with_search(layout, props, "contexts")
|
||||||
row.prop(context.scene.BIMRootProperties, "contexts")
|
|
||||||
|
|||||||
@@ -31,18 +31,33 @@ classes = (
|
|||||||
operator.SelectGlobalId,
|
operator.SelectGlobalId,
|
||||||
operator.SelectIfcClass,
|
operator.SelectIfcClass,
|
||||||
operator.SelectPset,
|
operator.SelectPset,
|
||||||
|
operator.UnhideAllElements,
|
||||||
|
operator.FilterModelElements,
|
||||||
|
operator.IfcSelector,
|
||||||
|
operator.SaveSelectorQuery,
|
||||||
|
operator.OpenQueryLibrary,
|
||||||
|
operator.LoadQuery,
|
||||||
|
operator.AddToIfcGroup,
|
||||||
prop.BIMFilterClasses,
|
prop.BIMFilterClasses,
|
||||||
prop.BIMFilterBuildingStoreys,
|
prop.BIMFilterBuildingStoreys,
|
||||||
prop.BIMSearchProperties,
|
prop.BIMSearchProperties,
|
||||||
|
prop.SearchCollection,
|
||||||
|
prop.SearchQueryFilter,
|
||||||
|
prop.SearchQuery,
|
||||||
|
prop.SearchQueryGroup,
|
||||||
|
prop.IfcSelectorProperties,
|
||||||
ui.BIM_PT_search,
|
ui.BIM_PT_search,
|
||||||
ui.BIM_UL_ifc_class_filter,
|
ui.BIM_UL_ifc_class_filter,
|
||||||
ui.BIM_UL_ifc_building_storey_filter,
|
ui.BIM_UL_ifc_building_storey_filter,
|
||||||
|
ui.BIM_PT_IFCSelector,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
bpy.types.Scene.BIMSearchProperties = bpy.props.PointerProperty(type=prop.BIMSearchProperties)
|
bpy.types.Scene.BIMSearchProperties = bpy.props.PointerProperty(type=prop.BIMSearchProperties)
|
||||||
|
bpy.types.Scene.IfcSelectorProperties = bpy.props.PointerProperty(type=prop.IfcSelectorProperties)
|
||||||
|
|
||||||
|
|
||||||
def unregister():
|
def unregister():
|
||||||
del bpy.types.Scene.BIMSearchProperties
|
del bpy.types.Scene.BIMSearchProperties
|
||||||
|
del bpy.types.Scene.IfcSelectorProperties
|
||||||
|
|||||||
@@ -20,9 +20,24 @@ import re
|
|||||||
import bpy
|
import bpy
|
||||||
import ifcopenshell
|
import ifcopenshell
|
||||||
import ifcopenshell.util.element
|
import ifcopenshell.util.element
|
||||||
|
from ifcopenshell.api.group.data import Data
|
||||||
|
from ifcopenshell.util.selector import Selector
|
||||||
import blenderbim.tool as tool
|
import blenderbim.tool as tool
|
||||||
from blenderbim.bim.ifc import IfcStore
|
from blenderbim.bim.ifc import IfcStore
|
||||||
|
from blenderbim.bim.helper import close_operator_panel
|
||||||
|
from blenderbim.bim.module.group import ui
|
||||||
from itertools import cycle
|
from itertools import cycle
|
||||||
|
from bpy.types import PropertyGroup, Operator
|
||||||
|
from bpy.props import (
|
||||||
|
PointerProperty,
|
||||||
|
StringProperty,
|
||||||
|
EnumProperty,
|
||||||
|
BoolProperty,
|
||||||
|
IntProperty,
|
||||||
|
FloatProperty,
|
||||||
|
FloatVectorProperty,
|
||||||
|
CollectionProperty,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
colour_list = [
|
colour_list = [
|
||||||
@@ -54,13 +69,13 @@ def does_keyword_exist(pattern, string, context):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
class SelectGlobalId(bpy.types.Operator):
|
class SelectGlobalId(Operator):
|
||||||
"""Click to select the objects that match with the given Global ID"""
|
"""Click to select the objects that match with the given Global ID"""
|
||||||
|
|
||||||
bl_idname = "bim.select_global_id"
|
bl_idname = "bim.select_global_id"
|
||||||
bl_label = "Select GlobalId"
|
bl_label = "Select GlobalId"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
global_id: bpy.props.StringProperty()
|
global_id: StringProperty()
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
ifc_file = tool.Ifc.get()
|
ifc_file = tool.Ifc.get()
|
||||||
@@ -73,13 +88,13 @@ class SelectGlobalId(bpy.types.Operator):
|
|||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
class SelectIfcClass(bpy.types.Operator):
|
class SelectIfcClass(Operator):
|
||||||
"""Click to select all objects that match with the given IFC class"""
|
"""Click to select all objects that match with the given IFC class"""
|
||||||
|
|
||||||
bl_idname = "bim.select_ifc_class"
|
bl_idname = "bim.select_ifc_class"
|
||||||
bl_label = "Select IFC Class"
|
bl_label = "Select IFC Class"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
ifc_class: bpy.props.StringProperty()
|
ifc_class: StringProperty()
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
self.file = IfcStore.get_file()
|
self.file = IfcStore.get_file()
|
||||||
@@ -92,7 +107,7 @@ class SelectIfcClass(bpy.types.Operator):
|
|||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
class SelectAttribute(bpy.types.Operator):
|
class SelectAttribute(Operator):
|
||||||
"""Click to select all objects that match with the given Attribute Name and Value"""
|
"""Click to select all objects that match with the given Attribute Name and Value"""
|
||||||
|
|
||||||
bl_idname = "bim.select_attribute"
|
bl_idname = "bim.select_attribute"
|
||||||
@@ -118,7 +133,7 @@ class SelectAttribute(bpy.types.Operator):
|
|||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
class SelectPset(bpy.types.Operator):
|
class SelectPset(Operator):
|
||||||
"""Click to select all objects that match with the given Pset Name, Properties Name and Value"""
|
"""Click to select all objects that match with the given Pset Name, Properties Name and Value"""
|
||||||
|
|
||||||
bl_idname = "bim.select_pset"
|
bl_idname = "bim.select_pset"
|
||||||
@@ -152,7 +167,7 @@ class SelectPset(bpy.types.Operator):
|
|||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
class ColourByAttribute(bpy.types.Operator):
|
class ColourByAttribute(Operator):
|
||||||
"""Click to colour different objects according to given Attribute Name"""
|
"""Click to colour different objects according to given Attribute Name"""
|
||||||
|
|
||||||
bl_idname = "bim.colour_by_attribute"
|
bl_idname = "bim.colour_by_attribute"
|
||||||
@@ -203,7 +218,7 @@ class ColourByAttribute(bpy.types.Operator):
|
|||||||
data["area"].spaces[0].shading.color_type = "OBJECT"
|
data["area"].spaces[0].shading.color_type = "OBJECT"
|
||||||
|
|
||||||
|
|
||||||
class ColourByPset(bpy.types.Operator):
|
class ColourByPset(Operator):
|
||||||
"""Click to colour different objects according to given Prop Name"""
|
"""Click to colour different objects according to given Prop Name"""
|
||||||
|
|
||||||
bl_idname = "bim.colour_by_pset"
|
bl_idname = "bim.colour_by_pset"
|
||||||
@@ -262,7 +277,7 @@ class ColourByPset(bpy.types.Operator):
|
|||||||
data["area"].spaces[0].shading.color_type = "OBJECT"
|
data["area"].spaces[0].shading.color_type = "OBJECT"
|
||||||
|
|
||||||
|
|
||||||
class ColourByClass(bpy.types.Operator):
|
class ColourByClass(Operator):
|
||||||
"""Click to colour different objects according to their IFC Classes"""
|
"""Click to colour different objects according to their IFC Classes"""
|
||||||
|
|
||||||
bl_idname = "bim.colour_by_class"
|
bl_idname = "bim.colour_by_class"
|
||||||
@@ -308,7 +323,7 @@ class ColourByClass(bpy.types.Operator):
|
|||||||
data["area"].spaces[0].shading.color_type = "OBJECT"
|
data["area"].spaces[0].shading.color_type = "OBJECT"
|
||||||
|
|
||||||
|
|
||||||
class ResetObjectColours(bpy.types.Operator):
|
class ResetObjectColours(Operator):
|
||||||
"""Reset the colour of selected objects"""
|
"""Reset the colour of selected objects"""
|
||||||
|
|
||||||
bl_idname = "bim.reset_object_colours"
|
bl_idname = "bim.reset_object_colours"
|
||||||
@@ -320,11 +335,11 @@ class ResetObjectColours(bpy.types.Operator):
|
|||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
class ToggleFilterSelection(bpy.types.Operator):
|
class ToggleFilterSelection(Operator):
|
||||||
"Click to select/deselect current selection"
|
"Click to select/deselect current selection"
|
||||||
bl_idname = "bim.toggle_filter_selection"
|
bl_idname = "bim.toggle_filter_selection"
|
||||||
bl_label = "Toggle Filter Selection"
|
bl_label = "Toggle Filter Selection"
|
||||||
action: bpy.props.EnumProperty(items=(("SELECT", "Select", ""), ("DESELECT", "Deselect", "")))
|
action: EnumProperty(items=(("SELECT", "Select", ""), ("DESELECT", "Deselect", "")))
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
props = bpy.context.scene.BIMSearchProperties
|
props = bpy.context.scene.BIMSearchProperties
|
||||||
@@ -341,7 +356,7 @@ class ToggleFilterSelection(bpy.types.Operator):
|
|||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
class ActivateIfcClassFilter(bpy.types.Operator):
|
class ActivateIfcClassFilter(Operator):
|
||||||
"""Filter the current selection by IFC class"""
|
"""Filter the current selection by IFC class"""
|
||||||
|
|
||||||
bl_idname = "bim.activate_ifc_class_filter"
|
bl_idname = "bim.activate_ifc_class_filter"
|
||||||
@@ -387,7 +402,7 @@ class ActivateIfcClassFilter(bpy.types.Operator):
|
|||||||
row.operator("bim.toggle_filter_selection", text="Deselect All").action = "DESELECT"
|
row.operator("bim.toggle_filter_selection", text="Deselect All").action = "DESELECT"
|
||||||
|
|
||||||
|
|
||||||
class ActivateIfcBuildingStoreyFilter(bpy.types.Operator):
|
class ActivateIfcBuildingStoreyFilter(Operator):
|
||||||
"""Filter the current selection by Building Storey"""
|
"""Filter the current selection by Building Storey"""
|
||||||
|
|
||||||
bl_idname = "bim.activate_ifc_building_storey_filter"
|
bl_idname = "bim.activate_ifc_building_storey_filter"
|
||||||
@@ -433,3 +448,236 @@ class ActivateIfcBuildingStoreyFilter(bpy.types.Operator):
|
|||||||
row = self.layout.row(align=True)
|
row = self.layout.row(align=True)
|
||||||
row.operator("bim.toggle_filter_selection", text="Select All").action = "SELECT"
|
row.operator("bim.toggle_filter_selection", text="Select All").action = "SELECT"
|
||||||
row.operator("bim.toggle_filter_selection", text="Deselect All").action = "DESELECT"
|
row.operator("bim.toggle_filter_selection", text="Deselect All").action = "DESELECT"
|
||||||
|
|
||||||
|
|
||||||
|
class UnhideAllElements(Operator):
|
||||||
|
"""Filter model elements based on selection"""
|
||||||
|
|
||||||
|
bl_idname = "bim.reset_3d_view"
|
||||||
|
bl_label = "Reset 3D View"
|
||||||
|
bl_idname = "bim.unhide_all_elements"
|
||||||
|
bl_label = "Unhide All Elements"
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
for obj in bpy.data.scenes["Scene"].objects:
|
||||||
|
obj.hide_set(False)
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
class FilterModelElements(Operator):
|
||||||
|
"""Filter model elements based on selection"""
|
||||||
|
|
||||||
|
bl_idname = "bim.filter_model_elements"
|
||||||
|
bl_label = "Filter Model Elements"
|
||||||
|
option: StringProperty("select|isolate|hide")
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
selector = context.scene.IfcSelectorProperties
|
||||||
|
selection = selector.selector_query_syntax if selector.manual_override else self.add_groups(selector)
|
||||||
|
selector.selector_query_syntax = selection
|
||||||
|
self.update_model_view(context, selection)
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
def add_groups(self, selector):
|
||||||
|
selection = ""
|
||||||
|
for group_index, group in enumerate(selector.groups):
|
||||||
|
if group_index != 0:
|
||||||
|
selection += " | "
|
||||||
|
selection += "(" if len(selector.groups) > 1 else ""
|
||||||
|
selection = self.add_queries(selection, group)
|
||||||
|
|
||||||
|
selection += ")" if len(selector.groups) > 1 else ""
|
||||||
|
return selection
|
||||||
|
|
||||||
|
def add_queries(self, selection, group):
|
||||||
|
for query_index, query in enumerate(group.queries):
|
||||||
|
if query_index != 0:
|
||||||
|
selection += " & " if query.and_or == "and" else " | "
|
||||||
|
|
||||||
|
if query.selector == "IFC Class":
|
||||||
|
active_option = query.active_option.split(": ")[1]
|
||||||
|
selection += f".{active_option}"
|
||||||
|
selection = self.add_filters(selection, query)
|
||||||
|
|
||||||
|
elif query.selector == "GlobalId":
|
||||||
|
selection += f"#{query.value}"
|
||||||
|
|
||||||
|
elif query.selector == "IfcElementType":
|
||||||
|
index = int(query.active_sub_option.split(":")[0])
|
||||||
|
selection += f"* #{query.sub_options[index].global_id}"
|
||||||
|
|
||||||
|
elif query.selector == "IfcSpatialElement":
|
||||||
|
index = int(query.active_sub_option.split(":")[0])
|
||||||
|
selection += f"@ #{query.sub_options[index].global_id}"
|
||||||
|
return selection
|
||||||
|
|
||||||
|
def add_filters(self, selection, query):
|
||||||
|
for f_index, f in enumerate(query.filters):
|
||||||
|
|
||||||
|
if f_index != 0:
|
||||||
|
selection += " & " if f.and_or == "and" else " | "
|
||||||
|
selection += f".{query.active_option}"
|
||||||
|
|
||||||
|
selection += "["
|
||||||
|
|
||||||
|
if f.selector == "IfcPropertySet":
|
||||||
|
selection += f'{f.active_option.split(": ")[1]}.{f.active_sub_option.split(": ")[1]} {"!" if f.negation else ""}="{f.value}"'
|
||||||
|
elif f.selector == "Attribute":
|
||||||
|
selection += f'{f.attribute} {"!" if f.negation else ""}= "{f.value}"'
|
||||||
|
|
||||||
|
selection += "]"
|
||||||
|
return selection
|
||||||
|
|
||||||
|
def update_model_view(self, context, selection):
|
||||||
|
query = Selector.parse(IfcStore.file, selection)
|
||||||
|
sel_element_ids = [e.id() for e in query]
|
||||||
|
bpy.ops.object.select_all(action="DESELECT")
|
||||||
|
|
||||||
|
for obj in bpy.data.scenes["Scene"].objects:
|
||||||
|
obj.hide_set(False) # reset 3d view
|
||||||
|
|
||||||
|
if self.option == "select":
|
||||||
|
if obj.BIMObjectProperties.ifc_definition_id in sel_element_ids:
|
||||||
|
obj.select_set(True)
|
||||||
|
elif self.option == "isolate":
|
||||||
|
if obj.BIMObjectProperties.ifc_definition_id not in sel_element_ids:
|
||||||
|
obj.hide_set(True)
|
||||||
|
elif self.option == "hide":
|
||||||
|
if obj.BIMObjectProperties.ifc_definition_id in sel_element_ids:
|
||||||
|
obj.hide_set(True)
|
||||||
|
|
||||||
|
#This needs to be moved into ui code, I know ;) - vulevukusej
|
||||||
|
class IfcSelector(Operator):
|
||||||
|
"""Select elements in model with IFC Selector"""
|
||||||
|
|
||||||
|
bl_idname = "bim.ifc_selector"
|
||||||
|
bl_label = "Select elements with IFC Selector"
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
return context.window_manager.invoke_props_dialog(self, width=800)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return IfcStore.get_file()
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
from . import ui
|
||||||
|
ui.IfcSelectorUI.draw(context, self.layout)
|
||||||
|
|
||||||
|
|
||||||
|
class SaveSelectorQuery(Operator):
|
||||||
|
bl_idname = "bim.save_selector_query"
|
||||||
|
bl_label = "Save Selector Query"
|
||||||
|
save_name: StringProperty()
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
return context.window_manager.invoke_props_dialog(self, width=400)
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
layout.prop(self, "save_name")
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
ifc_selector = context.scene.IfcSelectorProperties
|
||||||
|
new = ifc_selector.query_library.add()
|
||||||
|
new.name = self.save_name
|
||||||
|
new.query = ifc_selector.selector_query_syntax
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
class OpenQueryLibrary(Operator):
|
||||||
|
"""Open Query Library"""
|
||||||
|
|
||||||
|
bl_idname = "bim.open_query_library"
|
||||||
|
bl_label = "Open Query Library"
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
return context.window_manager.invoke_popup(self, width=400)
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
ifc_selector = context.scene.IfcSelectorProperties
|
||||||
|
|
||||||
|
for index, query in enumerate(ifc_selector.query_library):
|
||||||
|
row = layout.row(align=True)
|
||||||
|
row.prop(query, "query", text=query.name)
|
||||||
|
op = row.operator("bim.load_query", icon="SORT_ASC", text="")
|
||||||
|
op.index = index
|
||||||
|
|
||||||
|
row.context_pointer_set(name="bim_prop_group", data=ifc_selector)
|
||||||
|
op = row.operator("bim.edit_blender_collection", icon="REMOVE", text="")
|
||||||
|
op.option = "remove"
|
||||||
|
op.collection = "query_library"
|
||||||
|
op.index = index
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
class LoadQuery(Operator):
|
||||||
|
bl_idname = "bim.load_query"
|
||||||
|
bl_label = "Load Query"
|
||||||
|
index: IntProperty()
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
close_operator_panel(event)
|
||||||
|
return self.execute(context)
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
ifc_selector = context.scene.IfcSelectorProperties
|
||||||
|
ifc_selector.selector_query_syntax = ifc_selector.query_library[self.index].query
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
class AddToIfcGroup(Operator):
|
||||||
|
bl_idname = "bim.add_to_ifc_group"
|
||||||
|
bl_label = "Add to IFC Group"
|
||||||
|
group_name: StringProperty(name="Group Name")
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
bpy.ops.bim.load_groups()
|
||||||
|
return context.window_manager.invoke_props_dialog(self, width=400)
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
self.props = context.scene.BIMGroupProperties
|
||||||
|
row = self.layout.row()
|
||||||
|
row.operator("bim.add_group")
|
||||||
|
|
||||||
|
self.layout.template_list(
|
||||||
|
"BIM_UL_groups",
|
||||||
|
"",
|
||||||
|
self.props,
|
||||||
|
"groups",
|
||||||
|
self.props,
|
||||||
|
"active_group_index",
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.props.active_group_id:
|
||||||
|
for attribute in self.props.group_attributes:
|
||||||
|
if attribute.name in ["Name", "Description"]:
|
||||||
|
row = self.layout.row(align=True)
|
||||||
|
row.prop(attribute, "string_value", text=attribute.name)
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
active_group_index = self.props.active_group_index
|
||||||
|
ifc_definition_id = self.props.groups[active_group_index].ifc_definition_id
|
||||||
|
|
||||||
|
bpy.ops.bim.enable_editing_group(group=ifc_definition_id)
|
||||||
|
|
||||||
|
selector_query_syntax = context.scene.IfcSelectorProperties.selector_query_syntax
|
||||||
|
|
||||||
|
for attribute in self.props.group_attributes:
|
||||||
|
if attribute.name == "Description":
|
||||||
|
if "*selector*" not in attribute.string_value:
|
||||||
|
attribute.string_value += f" *selector*{selector_query_syntax}*selector*"
|
||||||
|
else:
|
||||||
|
new_description = attribute.string_value.split("*selector*")
|
||||||
|
new_description[1] = selector_query_syntax
|
||||||
|
attribute.string_value = "*selector*".join(new_description)
|
||||||
|
|
||||||
|
bpy.ops.bim.edit_group()
|
||||||
|
bpy.ops.bim.disable_group_editing_ui()
|
||||||
|
return {"FINISHED"}
|
||||||
|
|||||||
@@ -17,9 +17,13 @@
|
|||||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
|
from ifcopenshell import util
|
||||||
|
from ifcopenshell.util.selector import Selector
|
||||||
import blenderbim.tool as tool
|
import blenderbim.tool as tool
|
||||||
from blenderbim.bim.prop import ObjProperty
|
from blenderbim.bim.prop import ObjProperty, StrProperty
|
||||||
|
from blenderbim.bim.ifc import IfcStore
|
||||||
from bpy.types import PropertyGroup
|
from bpy.types import PropertyGroup
|
||||||
|
from blenderbim.tool.ifc import Ifc
|
||||||
from . import ui, prop, operator
|
from . import ui, prop, operator
|
||||||
from bpy.props import (
|
from bpy.props import (
|
||||||
PointerProperty,
|
PointerProperty,
|
||||||
@@ -90,3 +94,147 @@ class BIMSearchProperties(PropertyGroup):
|
|||||||
filter_classes_index: IntProperty(name="Filter Classes Index")
|
filter_classes_index: IntProperty(name="Filter Classes Index")
|
||||||
filter_building_storeys: CollectionProperty(type=BIMFilterBuildingStoreys, name="Filter Level")
|
filter_building_storeys: CollectionProperty(type=BIMFilterBuildingStoreys, name="Filter Level")
|
||||||
filter_building_storeys_index: IntProperty(name="Filter Level Index")
|
filter_building_storeys_index: IntProperty(name="Filter Level Index")
|
||||||
|
|
||||||
|
|
||||||
|
def get_classes(self, ifc_product):
|
||||||
|
declaration = tool.Ifc.schema().declaration_by_name(ifc_product)
|
||||||
|
declarations = util.schema.get_subtypes(declaration)
|
||||||
|
names = [d.name() for d in declarations]
|
||||||
|
return [(c, c, "") for c in sorted(names)]
|
||||||
|
|
||||||
|
|
||||||
|
def load_sub_options(self, context):
|
||||||
|
if self.selector not in ["IfcClass"]:
|
||||||
|
self.load_option = "sub_options"
|
||||||
|
load_selection_options(self, context)
|
||||||
|
|
||||||
|
|
||||||
|
def load_selection_options(self, context):
|
||||||
|
ifc = IfcStore.file
|
||||||
|
load_option = self.load_option
|
||||||
|
op = getattr(self, load_option)
|
||||||
|
op.clear()
|
||||||
|
options = []
|
||||||
|
|
||||||
|
if load_option == "options":
|
||||||
|
self.sub_options.clear()
|
||||||
|
if self.selector == "IFC Class":
|
||||||
|
options = get_classes(self, "IfcElement")
|
||||||
|
options.append(("IfcSpace", "IfcSpace", ""))
|
||||||
|
elif self.selector == "IfcSpatialElement":
|
||||||
|
options = get_classes(self, "IfcSpatialElement")
|
||||||
|
elif self.selector == "IfcElementType":
|
||||||
|
options = get_classes(self, "IfcElementType")
|
||||||
|
elif self.selector == "GlobalId":
|
||||||
|
return
|
||||||
|
elif self.selector == "IfcPropertySet":
|
||||||
|
psets = Selector.parse(ifc, ".IfcPropertySet")
|
||||||
|
options = set([o.Name for o in psets])
|
||||||
|
|
||||||
|
elif load_option == "sub_options":
|
||||||
|
if self.selector in ["IfcSpatialElement", "IfcElementType"]:
|
||||||
|
active_option = self.active_option.split(": ")[1]
|
||||||
|
options = Selector.parse(ifc, f".{active_option}")
|
||||||
|
|
||||||
|
elif self.selector == "IfcPropertySet":
|
||||||
|
options = set()
|
||||||
|
active_pset = self.active_option.split(": ")[1]
|
||||||
|
psets_in_file = Selector.parse(ifc, f'.IfcPropertySet[Name="{active_pset}"]')
|
||||||
|
for pset in psets_in_file:
|
||||||
|
for prop in pset.HasProperties:
|
||||||
|
options.add(prop.Name)
|
||||||
|
|
||||||
|
for index, option in enumerate(options):
|
||||||
|
new = op.add()
|
||||||
|
if self.selector in ["IfcSpatialElement", "IfcElementType"]:
|
||||||
|
if self.load_option == "sub_options":
|
||||||
|
new.name = f"{index}: {option.Name}"
|
||||||
|
new.global_id = option.GlobalId
|
||||||
|
else:
|
||||||
|
new.name = f"{index}: {option[0]}"
|
||||||
|
elif self.selector == "IfcPropertySet":
|
||||||
|
new.name = f"{index}: {option}"
|
||||||
|
else:
|
||||||
|
new.name = f"{index}: {option[0]}"
|
||||||
|
self.load_option = "options"
|
||||||
|
|
||||||
|
|
||||||
|
def load_spatial_elements(self, context):
|
||||||
|
ifc = IfcStore.file
|
||||||
|
col_items = Selector.parse(ifc, f".{self.selected_spatial_element}")
|
||||||
|
collection = getattr(self, "sub_spatial_elements", None)
|
||||||
|
collection.clear()
|
||||||
|
for index, c in enumerate(col_items):
|
||||||
|
new = collection.add()
|
||||||
|
new.name = f"{index}-{c.Name}"
|
||||||
|
|
||||||
|
|
||||||
|
class SearchCollection(PropertyGroup):
|
||||||
|
name: StringProperty()
|
||||||
|
long_name: StringProperty()
|
||||||
|
global_id: StringProperty()
|
||||||
|
query: StringProperty()
|
||||||
|
|
||||||
|
|
||||||
|
class IfcSelector:
|
||||||
|
and_or: EnumProperty(
|
||||||
|
items=[(i, i, i) for i in ["and", "or"]],
|
||||||
|
)
|
||||||
|
negation: BoolProperty(name="not")
|
||||||
|
comparison: EnumProperty(
|
||||||
|
items=[
|
||||||
|
("=", "equal to", ""),
|
||||||
|
("*=", "contains", ""),
|
||||||
|
(">=", "greater than or equal to", ""),
|
||||||
|
("<=", "lesser than or equal to", ""),
|
||||||
|
(">", "greater than", ""),
|
||||||
|
("<", "less than", ""),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
load_option: StringProperty(
|
||||||
|
default="options", description="controls whether or not options or sub_options are loaded"
|
||||||
|
)
|
||||||
|
options: CollectionProperty(type=SearchCollection)
|
||||||
|
active_option: StringProperty(update=load_sub_options)
|
||||||
|
sub_options: CollectionProperty(type=SearchCollection)
|
||||||
|
active_sub_option: StringProperty()
|
||||||
|
value: StringProperty(description="generic 'value' that can be used in multiple scenarios")
|
||||||
|
|
||||||
|
|
||||||
|
class SearchQueryFilter(PropertyGroup, IfcSelector):
|
||||||
|
selector: EnumProperty(
|
||||||
|
items=[(i, i, i) for i in ["-", "IfcPropertySet", "Attribute"]],
|
||||||
|
name="Filter selection by",
|
||||||
|
update=load_selection_options,
|
||||||
|
default="-",
|
||||||
|
)
|
||||||
|
attribute: EnumProperty(
|
||||||
|
items=[(i, i, i) for i in ["GlobalId", "Name", "Description", "ObjectType", "Tag", "PredefinedType"]],
|
||||||
|
name="Filter selection by",
|
||||||
|
update=load_selection_options,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SearchQuery(PropertyGroup, IfcSelector):
|
||||||
|
filters: CollectionProperty(type=SearchQueryFilter)
|
||||||
|
selector: EnumProperty(
|
||||||
|
items=[(i, i, i) for i in ["-", "IFC Class", "IfcSpatialElement", "IfcElementType", "GlobalId"]],
|
||||||
|
name="Selector type",
|
||||||
|
update=load_selection_options,
|
||||||
|
default="-",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SearchQueryGroup(PropertyGroup, IfcSelector):
|
||||||
|
queries: CollectionProperty(type=SearchQuery)
|
||||||
|
|
||||||
|
|
||||||
|
class IfcSelectorProperties(PropertyGroup, IfcSelector):
|
||||||
|
groups: CollectionProperty(type=SearchQueryGroup)
|
||||||
|
selector_query_syntax: StringProperty()
|
||||||
|
|
||||||
|
query_library: CollectionProperty(type=SearchCollection)
|
||||||
|
active_query: StringProperty()
|
||||||
|
|
||||||
|
active_query: StringProperty()
|
||||||
|
manual_override: BoolProperty(default=False, description="Toggle to allow manual typing of query-syntax")
|
||||||
|
|||||||
@@ -102,3 +102,153 @@ class BIM_UL_ifc_building_storey_filter(bpy.types.UIList):
|
|||||||
split = split.column()
|
split = split.column()
|
||||||
split.scale_x = 0.5
|
split.scale_x = 0.5
|
||||||
split.label(text=str(item.total))
|
split.label(text=str(item.total))
|
||||||
|
|
||||||
|
|
||||||
|
class BIM_PT_IFCSelector(Panel):
|
||||||
|
bl_label = "IFC Selector"
|
||||||
|
bl_idname = "BIM_PT_ifc_selector"
|
||||||
|
bl_options = {"DEFAULT_CLOSED"}
|
||||||
|
bl_space_type = "PROPERTIES"
|
||||||
|
bl_region_type = "WINDOW"
|
||||||
|
bl_context = "scene"
|
||||||
|
bl_parent_id = "BIM_PT_collaboration"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return IfcStore.get_file()
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
layout.operator("bim.ifc_selector")
|
||||||
|
|
||||||
|
|
||||||
|
# this doesn't inherit from Panel since it's just a class to abstract away UI code from the IfcSelector Operator
|
||||||
|
class IfcSelectorUI:
|
||||||
|
@classmethod
|
||||||
|
def draw(self, context, layout):
|
||||||
|
ifc_selector = context.scene.IfcSelectorProperties
|
||||||
|
row = layout.row()
|
||||||
|
|
||||||
|
row.context_pointer_set(name="bim_prop_group", data=ifc_selector)
|
||||||
|
op = row.operator("bim.edit_blender_collection", text="Add selection group")
|
||||||
|
op.option = "add"
|
||||||
|
op.collection = "groups"
|
||||||
|
layout.separator()
|
||||||
|
|
||||||
|
self.draw_query_group_ui(self, ifc_selector, layout)
|
||||||
|
|
||||||
|
if len(ifc_selector.groups) != 0:
|
||||||
|
row = layout.row(align=True)
|
||||||
|
row.prop(ifc_selector, "selector_query_syntax", text="Query Syntax")
|
||||||
|
row.prop(ifc_selector, "manual_override", text="")
|
||||||
|
|
||||||
|
select = row.operator("bim.filter_model_elements", text="", icon="RESTRICT_SELECT_OFF")
|
||||||
|
select.option = "select"
|
||||||
|
isolate = row.operator("bim.filter_model_elements", text="", icon="ZOOM_SELECTED")
|
||||||
|
isolate.option = "isolate"
|
||||||
|
hide = row.operator("bim.filter_model_elements", text="", icon="HIDE_ON")
|
||||||
|
hide.option = "hide"
|
||||||
|
row.operator("bim.unhide_all_elements", text="", icon="HIDE_OFF")
|
||||||
|
|
||||||
|
row = layout.row(align=True)
|
||||||
|
row.alignment = "CENTER"
|
||||||
|
row.operator("bim.save_selector_query", text="Save Query")
|
||||||
|
op = row.operator("bim.open_query_library", text="Load Query")
|
||||||
|
row.operator("bim.add_to_ifc_group", text="Add to IFC Group")
|
||||||
|
|
||||||
|
def draw_query_group_ui(self, ifc_selector, layout):
|
||||||
|
for index, group in enumerate(ifc_selector.groups):
|
||||||
|
row = layout.row()
|
||||||
|
row.alignment = "CENTER"
|
||||||
|
row.label(text="or") if index != 0 else None
|
||||||
|
|
||||||
|
box = layout.box()
|
||||||
|
row = box.row(align=True)
|
||||||
|
row.alignment = "CENTER"
|
||||||
|
row.label(text=f"Group #{str(index+1)}")
|
||||||
|
row.context_pointer_set(name="bim_prop_group", data=group)
|
||||||
|
op = row.operator("bim.edit_blender_collection", text="Add query", icon="PLUS")
|
||||||
|
op.option = "add"
|
||||||
|
op.collection = "queries"
|
||||||
|
|
||||||
|
row.context_pointer_set(name="bim_prop_group", data=ifc_selector)
|
||||||
|
op = row.operator("bim.edit_blender_collection", text="Remove selection group")
|
||||||
|
op.option = "remove"
|
||||||
|
op.collection = "groups"
|
||||||
|
op.index = index
|
||||||
|
|
||||||
|
self.draw_query_ui(self, group, box)
|
||||||
|
|
||||||
|
def draw_query_ui(self, group, box):
|
||||||
|
for index, query in enumerate(group.queries):
|
||||||
|
row = box.row()
|
||||||
|
row.alignment = "LEFT"
|
||||||
|
|
||||||
|
row.prop(query, "and_or", text="") if index != 0 else None
|
||||||
|
if query.and_or == "or":
|
||||||
|
row = box.row()
|
||||||
|
row.alignment = "LEFT"
|
||||||
|
row.prop(query, "selector", text="")
|
||||||
|
|
||||||
|
if query.selector in ["IFC Class", "IfcSpatialElement", "IfcElementType"]:
|
||||||
|
row.label(text="Equals")
|
||||||
|
row.prop_search(query, "active_option", query, "options", text="")
|
||||||
|
row.prop_search(query, "active_sub_option", query, "sub_options", text="") if len(
|
||||||
|
query.sub_options
|
||||||
|
) != 0 else None
|
||||||
|
self.draw_filter_ui(self, box, query)
|
||||||
|
|
||||||
|
elif query.selector in ["GlobalId", "Attribute"]:
|
||||||
|
row.prop(query, "negation", text="")
|
||||||
|
row.prop(query, "comparison", text="")
|
||||||
|
row.prop(query, "value", text="")
|
||||||
|
|
||||||
|
row.context_pointer_set(name="bim_prop_group", data=group)
|
||||||
|
op = row.operator("bim.edit_blender_collection", icon="REMOVE", text="")
|
||||||
|
op.option = "remove"
|
||||||
|
op.collection = "queries"
|
||||||
|
op.index = index
|
||||||
|
|
||||||
|
row.context_pointer_set(name="bim_prop_group", data=query)
|
||||||
|
op = (
|
||||||
|
row.operator("bim.edit_blender_collection", text="Add filter")
|
||||||
|
if query.selector == "IFC Class"
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
if op:
|
||||||
|
op.option = "add"
|
||||||
|
op.collection = "filters"
|
||||||
|
|
||||||
|
def draw_filter_ui(self, box, query):
|
||||||
|
for filter_index, f in enumerate(query.filters):
|
||||||
|
row = box.row()
|
||||||
|
row.alignment = "LEFT"
|
||||||
|
row.label(text=" ↪")
|
||||||
|
row.prop(f, "and_or", text="") if filter_index != 0 else None
|
||||||
|
if f.and_or == "or":
|
||||||
|
row = box.row()
|
||||||
|
row.alignment = "LEFT"
|
||||||
|
row.label(text=" ↪")
|
||||||
|
row.prop(f, "selector", text="")
|
||||||
|
|
||||||
|
if f.selector == "Attribute":
|
||||||
|
row.prop(f, "attribute", text="")
|
||||||
|
row.prop(
|
||||||
|
f,
|
||||||
|
"negation",
|
||||||
|
)
|
||||||
|
row.prop(f, "comparison", text="")
|
||||||
|
row.prop(f, "value", text="")
|
||||||
|
|
||||||
|
elif f.selector == "IfcPropertySet":
|
||||||
|
row.prop_search(f, "active_option", f, "options", text="")
|
||||||
|
row.prop_search(f, "active_sub_option", f, "sub_options", text="")
|
||||||
|
row.prop(f, "negation")
|
||||||
|
row.prop(f, "comparison", text="")
|
||||||
|
row.prop(f, "value", text="")
|
||||||
|
|
||||||
|
row.context_pointer_set(name="bim_prop_group", data=query)
|
||||||
|
op = row.operator("bim.edit_blender_collection", icon="REMOVE", text="")
|
||||||
|
op.option = "remove"
|
||||||
|
op.collection = "filters"
|
||||||
|
op.index = filter_index
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||||
|
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>, 2021-2022 Yassine Oualid <yassine@sigmadimensions.com>
|
||||||
|
#
|
||||||
|
# This file is part of BlenderBIM Add-on.
|
||||||
|
#
|
||||||
|
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
import blenderbim.tool as tool
|
||||||
|
|
||||||
|
|
||||||
|
def refresh():
|
||||||
|
SequenceData.is_loaded = False
|
||||||
|
|
||||||
|
|
||||||
|
class SequenceData:
|
||||||
|
is_loaded = False
|
||||||
|
number_of_work_plans_loaded = 0
|
||||||
|
number_of_work_schedules_loaded = 0
|
||||||
|
number_of_tasks_loaded = 0
|
||||||
|
tasks = {}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def load(cls):
|
||||||
|
cls.load_work_plans()
|
||||||
|
cls.is_loaded = True
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def load_work_plans(cls):
|
||||||
|
cls.work_plans = {}
|
||||||
|
cls.number_of_work_plans_loaded = len(tool.Ifc.get().by_type("IfcWorkPlan"))
|
||||||
|
for work_plan in tool.Ifc.get().by_type("IfcWorkPlan"):
|
||||||
|
cls.work_plans[work_plan.id()] = {"Name": work_plan.Name}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||||
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
|
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>, 2022 Yassine Oualid <yassine@sigmadimensions.com>
|
||||||
#
|
#
|
||||||
# This file is part of BlenderBIM Add-on.
|
# This file is part of BlenderBIM Add-on.
|
||||||
#
|
#
|
||||||
@@ -28,6 +28,8 @@ import webbrowser
|
|||||||
import ifcopenshell.api
|
import ifcopenshell.api
|
||||||
import ifcopenshell.util.date
|
import ifcopenshell.util.date
|
||||||
import ifcopenshell.util.sequence
|
import ifcopenshell.util.sequence
|
||||||
|
import blenderbim.core.sequence as core
|
||||||
|
import blenderbim.tool as tool
|
||||||
import blenderbim.bim.helper
|
import blenderbim.bim.helper
|
||||||
import blenderbim.bim.module.sequence.helper as helper
|
import blenderbim.bim.module.sequence.helper as helper
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
@@ -52,7 +54,7 @@ def animate_text(scene, context):
|
|||||||
data.body = frame_date.date().isoformat()
|
data.body = frame_date.date().isoformat()
|
||||||
|
|
||||||
|
|
||||||
class AddWorkPlan(bpy.types.Operator):
|
class AddWorkPlan(bpy.types.Operator, tool.Ifc.Operator):
|
||||||
bl_idname = "bim.add_work_plan"
|
bl_idname = "bim.add_work_plan"
|
||||||
bl_label = "Add Work Plan"
|
bl_label = "Add Work Plan"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
@@ -61,12 +63,11 @@ class AddWorkPlan(bpy.types.Operator):
|
|||||||
return IfcStore.execute_ifc_operator(self, context)
|
return IfcStore.execute_ifc_operator(self, context)
|
||||||
|
|
||||||
def _execute(self, context):
|
def _execute(self, context):
|
||||||
ifcopenshell.api.run("sequence.add_work_plan", IfcStore.get_file())
|
core.add_work_plan(tool.Ifc, tool.Sequence)
|
||||||
Data.load(IfcStore.get_file())
|
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
class EditWorkPlan(bpy.types.Operator):
|
class EditWorkPlan(bpy.types.Operator, tool.Ifc.Operator):
|
||||||
bl_idname = "bim.edit_work_plan"
|
bl_idname = "bim.edit_work_plan"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
bl_label = "Edit Work Plan"
|
bl_label = "Edit Work Plan"
|
||||||
@@ -75,34 +76,11 @@ class EditWorkPlan(bpy.types.Operator):
|
|||||||
return IfcStore.execute_ifc_operator(self, context)
|
return IfcStore.execute_ifc_operator(self, context)
|
||||||
|
|
||||||
def _execute(self, context):
|
def _execute(self, context):
|
||||||
props = context.scene.BIMWorkPlanProperties
|
core.edit_work_plan(tool.Ifc, tool.Sequence)
|
||||||
attributes = blenderbim.bim.helper.export_attributes(props.work_plan_attributes, self.export_attributes)
|
|
||||||
self.file = IfcStore.get_file()
|
|
||||||
ifcopenshell.api.run(
|
|
||||||
"sequence.edit_work_plan",
|
|
||||||
self.file,
|
|
||||||
**{"work_plan": self.file.by_id(props.active_work_plan_id), "attributes": attributes},
|
|
||||||
)
|
|
||||||
Data.load(IfcStore.get_file())
|
|
||||||
bpy.ops.bim.disable_editing_work_plan()
|
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
def export_attributes(self, attributes, prop):
|
|
||||||
if "Date" in prop.name or "Time" in prop.name:
|
|
||||||
if prop.is_null:
|
|
||||||
attributes[prop.name] = None
|
|
||||||
return True
|
|
||||||
attributes[prop.name] = helper.parse_datetime(prop.string_value)
|
|
||||||
return True
|
|
||||||
elif prop.name == "Duration" or prop.name == "TotalFloat":
|
|
||||||
if prop.is_null:
|
|
||||||
attributes[prop.name] = None
|
|
||||||
return True
|
|
||||||
attributes[prop.name] = helper.parse_duration(prop.string_value)
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
class RemoveWorkPlan(bpy.types.Operator, tool.Ifc.Operator):
|
||||||
class RemoveWorkPlan(bpy.types.Operator):
|
|
||||||
bl_idname = "bim.remove_work_plan"
|
bl_idname = "bim.remove_work_plan"
|
||||||
bl_label = "Remove Work Plan"
|
bl_label = "Remove Work Plan"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
@@ -112,9 +90,7 @@ class RemoveWorkPlan(bpy.types.Operator):
|
|||||||
return IfcStore.execute_ifc_operator(self, context)
|
return IfcStore.execute_ifc_operator(self, context)
|
||||||
|
|
||||||
def _execute(self, context):
|
def _execute(self, context):
|
||||||
self.file = IfcStore.get_file()
|
core.remove_work_plan(tool.Ifc, tool.Sequence, work_plan=self.work_plan)
|
||||||
ifcopenshell.api.run("sequence.remove_work_plan", self.file, **{"work_plan": self.file.by_id(self.work_plan)})
|
|
||||||
Data.load(self.file)
|
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
@@ -125,22 +101,9 @@ class EnableEditingWorkPlan(bpy.types.Operator):
|
|||||||
work_plan: bpy.props.IntProperty()
|
work_plan: bpy.props.IntProperty()
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
props = context.scene.BIMWorkPlanProperties
|
core.enable_editing_work_plan(tool.Sequence, work_plan=self.work_plan)
|
||||||
props.work_plan_attributes.clear()
|
|
||||||
|
|
||||||
data = Data.work_plans[self.work_plan]
|
|
||||||
|
|
||||||
blenderbim.bim.helper.import_attributes("IfcWorkPlan", props.work_plan_attributes, data, self.import_attributes)
|
|
||||||
|
|
||||||
props.active_work_plan_id = self.work_plan
|
|
||||||
props.editing_type = "ATTRIBUTES"
|
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
def import_attributes(self, name, prop, data):
|
|
||||||
if name in ["CreationDate", "StartTime", "FinishTime"]:
|
|
||||||
prop.string_value = "" if prop.is_null else data[name].isoformat()
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
class DisableEditingWorkPlan(bpy.types.Operator):
|
class DisableEditingWorkPlan(bpy.types.Operator):
|
||||||
bl_idname = "bim.disable_editing_work_plan"
|
bl_idname = "bim.disable_editing_work_plan"
|
||||||
@@ -148,7 +111,7 @@ class DisableEditingWorkPlan(bpy.types.Operator):
|
|||||||
bl_label = "Disable Editing Work Plan"
|
bl_label = "Disable Editing Work Plan"
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
context.scene.BIMWorkPlanProperties.active_work_plan_id = 0
|
core.disable_editing_work_plan(tool.Sequence)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
@@ -624,6 +587,7 @@ class EditTaskTime(bpy.types.Operator):
|
|||||||
if prop.is_null:
|
if prop.is_null:
|
||||||
attributes[prop.name] = None
|
attributes[prop.name] = None
|
||||||
return True
|
return True
|
||||||
|
# TODO make this parse PT32 as P4D
|
||||||
attributes[prop.name] = helper.parse_duration(prop.string_value)
|
attributes[prop.name] = helper.parse_duration(prop.string_value)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -1885,6 +1849,7 @@ class VisualiseWorkScheduleDateRange(bpy.types.Operator):
|
|||||||
self.start_frame = 1
|
self.start_frame = 1
|
||||||
self.total_frames = self.calculate_total_frames(context)
|
self.total_frames = self.calculate_total_frames(context)
|
||||||
self.preprocess_tasks()
|
self.preprocess_tasks()
|
||||||
|
|
||||||
for obj in bpy.data.objects:
|
for obj in bpy.data.objects:
|
||||||
if not obj.BIMObjectProperties.ifc_definition_id:
|
if not obj.BIMObjectProperties.ifc_definition_id:
|
||||||
continue
|
continue
|
||||||
@@ -1931,7 +1896,7 @@ class VisualiseWorkScheduleDateRange(bpy.types.Operator):
|
|||||||
self.animate_consumption(obj, product_frame)
|
self.animate_consumption(obj, product_frame)
|
||||||
|
|
||||||
def animate_output(self, obj, product_frame):
|
def animate_output(self, obj, product_frame):
|
||||||
if product_frame["type"] in ["CONSTRUCTION", "INSTALLATION"]:
|
if product_frame["type"] in ["CONSTRUCTION", "INSTALLATION", "NOTDEFINED"]:
|
||||||
self.animate_creation(obj, product_frame)
|
self.animate_creation(obj, product_frame)
|
||||||
elif product_frame["type"] in ["DEMOLITION", "DISMANTLE", "DISPOSAL", "REMOVAL"]:
|
elif product_frame["type"] in ["DEMOLITION", "DISMANTLE", "DISPOSAL", "REMOVAL"]:
|
||||||
self.animate_destruction(obj, product_frame)
|
self.animate_destruction(obj, product_frame)
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ from blenderbim.bim.helper import draw_attributes
|
|||||||
from ifcopenshell.api.sequence.data import Data
|
from ifcopenshell.api.sequence.data import Data
|
||||||
from ifcopenshell.api.resource.data import Data as ResourceData
|
from ifcopenshell.api.resource.data import Data as ResourceData
|
||||||
import blenderbim.bim.module.sequence.helper as helper
|
import blenderbim.bim.module.sequence.helper as helper
|
||||||
|
from blenderbim.bim.module.sequence.data import SequenceData
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
@@ -42,14 +43,18 @@ class BIM_PT_work_plans(Panel):
|
|||||||
return file and file.schema != "IFC2X3"
|
return file and file.schema != "IFC2X3"
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
if not Data.is_loaded:
|
if not SequenceData.is_loaded:
|
||||||
Data.load(IfcStore.get_file())
|
SequenceData.load()
|
||||||
self.props = context.scene.BIMWorkPlanProperties
|
self.props = context.scene.BIMWorkPlanProperties
|
||||||
|
|
||||||
row = self.layout.row()
|
row = self.layout.row()
|
||||||
|
row.label(
|
||||||
|
text="{} Work Plans Found".format(SequenceData.number_of_work_plans_loaded),
|
||||||
|
icon="TEXT",
|
||||||
|
)
|
||||||
row.operator("bim.add_work_plan", icon="ADD")
|
row.operator("bim.add_work_plan", icon="ADD")
|
||||||
|
|
||||||
for work_plan_id, work_plan in Data.work_plans.items():
|
for work_plan_id, work_plan in SequenceData.work_plans.items():
|
||||||
self.draw_work_plan_ui(work_plan_id, work_plan)
|
self.draw_work_plan_ui(work_plan_id, work_plan)
|
||||||
|
|
||||||
def draw_work_plan_ui(self, work_plan_id, work_plan):
|
def draw_work_plan_ui(self, work_plan_id, work_plan):
|
||||||
|
|||||||
@@ -662,7 +662,9 @@ class AddStructuralLoadGroup(bpy.types.Operator):
|
|||||||
def _execute(self, context):
|
def _execute(self, context):
|
||||||
self.file = IfcStore.get_file()
|
self.file = IfcStore.get_file()
|
||||||
load_group = ifcopenshell.api.run("structural.add_structural_load_group", self.file)
|
load_group = ifcopenshell.api.run("structural.add_structural_load_group", self.file)
|
||||||
ifcopenshell.api.run("group.assign_group", self.file, product=load_group, group=self.file.by_id(self.load_case))
|
ifcopenshell.api.run(
|
||||||
|
"group.assign_group", self.file, product=[load_group], group=self.file.by_id(self.load_case)
|
||||||
|
)
|
||||||
Data.load(IfcStore.get_file())
|
Data.load(IfcStore.get_file())
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
@@ -754,7 +756,7 @@ class AddStructuralActivity(bpy.types.Operator):
|
|||||||
structural_member=element,
|
structural_member=element,
|
||||||
)
|
)
|
||||||
ifcopenshell.api.run(
|
ifcopenshell.api.run(
|
||||||
"group.assign_group", self.file, product=activity, group=self.file.by_id(self.load_group)
|
"group.assign_group", self.file, product=[activity], group=self.file.by_id(self.load_group)
|
||||||
)
|
)
|
||||||
Data.load(IfcStore.get_file())
|
Data.load(IfcStore.get_file())
|
||||||
bpy.ops.bim.enable_editing_structural_load_group_activities(load_group=self.load_group)
|
bpy.ops.bim.enable_editing_structural_load_group_activities(load_group=self.load_group)
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import bpy
|
|||||||
import blenderbim.bim.helper
|
import blenderbim.bim.helper
|
||||||
from bpy.types import Panel, UIList
|
from bpy.types import Panel, UIList
|
||||||
from blenderbim.bim.ifc import IfcStore
|
from blenderbim.bim.ifc import IfcStore
|
||||||
from blenderbim.bim.helper import draw_attributes
|
from blenderbim.bim.helper import draw_attributes, prop_with_search
|
||||||
from ifcopenshell.api.structural.data import Data
|
from ifcopenshell.api.structural.data import Data
|
||||||
from blenderbim.bim.module.structural.data import StructuralData
|
from blenderbim.bim.module.structural.data import StructuralData
|
||||||
|
|
||||||
@@ -482,7 +482,7 @@ class BIM_PT_structural_loads(Panel):
|
|||||||
row.operator("bim.disable_structural_load_editing_ui", text="", icon="SCREEN_BACK")
|
row.operator("bim.disable_structural_load_editing_ui", text="", icon="SCREEN_BACK")
|
||||||
|
|
||||||
row = self.layout.row(align=True)
|
row = self.layout.row(align=True)
|
||||||
row.prop(self.props, "structural_load_types", text="")
|
prop_with_search(row, self.props, "structural_load_types", text="")
|
||||||
row.operator("bim.add_structural_load", text="", icon="ADD").ifc_class = self.props.structural_load_types
|
row.operator("bim.add_structural_load", text="", icon="ADD").ifc_class = self.props.structural_load_types
|
||||||
else:
|
else:
|
||||||
row.operator("bim.load_structural_loads", text="", icon="GREASEPENCIL")
|
row.operator("bim.load_structural_loads", text="", icon="GREASEPENCIL")
|
||||||
@@ -551,7 +551,7 @@ class BIM_PT_boundary_conditions(Panel):
|
|||||||
row.operator("bim.disable_boundary_condition_editing_ui", text="", icon="SCREEN_BACK")
|
row.operator("bim.disable_boundary_condition_editing_ui", text="", icon="SCREEN_BACK")
|
||||||
|
|
||||||
row = self.layout.row(align=True)
|
row = self.layout.row(align=True)
|
||||||
row.prop(self.props, "boundary_condition_types", text="")
|
prop_with_search(row, self.props, "boundary_condition_types", text="")
|
||||||
row.operator(
|
row.operator(
|
||||||
"bim.add_boundary_condition", text="", icon="ADD"
|
"bim.add_boundary_condition", text="", icon="ADD"
|
||||||
).ifc_class = self.props.boundary_condition_types
|
).ifc_class = self.props.boundary_condition_types
|
||||||
|
|||||||
@@ -47,14 +47,14 @@ class BIM_PT_styles(Panel):
|
|||||||
if self.props.is_editing:
|
if self.props.is_editing:
|
||||||
row.operator("bim.disable_editing_styles", text="", icon="CANCEL")
|
row.operator("bim.disable_editing_styles", text="", icon="CANCEL")
|
||||||
else:
|
else:
|
||||||
row.prop(self.props, "style_type", text="")
|
blenderbim.bim.helper.prop_with_search(row, self.props, "style_type", text="")
|
||||||
row.operator("bim.load_styles", text="", icon="IMPORT").style_type = self.props.style_type
|
row.operator("bim.load_styles", text="", icon="IMPORT").style_type = self.props.style_type
|
||||||
return
|
return
|
||||||
|
|
||||||
row = self.layout.row(align=True)
|
row = self.layout.row(align=True)
|
||||||
row.alignment = "RIGHT"
|
row.alignment = "RIGHT"
|
||||||
|
|
||||||
#row.operator("bim.add_presentation_style", text="", icon="ADD")
|
# row.operator("bim.add_presentation_style", text="", icon="ADD")
|
||||||
if self.props.styles and self.props.active_style_index < len(self.props.styles):
|
if self.props.styles and self.props.active_style_index < len(self.props.styles):
|
||||||
style = self.props.styles[self.props.active_style_index]
|
style = self.props.styles[self.props.active_style_index]
|
||||||
op = row.operator("bim.select_by_style", text="", icon="RESTRICT_SELECT_OFF")
|
op = row.operator("bim.select_by_style", text="", icon="RESTRICT_SELECT_OFF")
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from blenderbim.bim.helper import prop_with_search
|
||||||
import blenderbim.tool as tool
|
import blenderbim.tool as tool
|
||||||
from bpy.types import Panel, UIList
|
from bpy.types import Panel, UIList
|
||||||
from blenderbim.bim.ifc import IfcStore
|
from blenderbim.bim.ifc import IfcStore
|
||||||
@@ -46,7 +47,7 @@ class BIM_PT_systems(Panel):
|
|||||||
row.operator("bim.disable_system_editing_ui", text="", icon="CANCEL")
|
row.operator("bim.disable_system_editing_ui", text="", icon="CANCEL")
|
||||||
|
|
||||||
row = self.layout.row(align=True)
|
row = self.layout.row(align=True)
|
||||||
row.prop(self.props, "system_class", text="")
|
prop_with_search(row, self.props, "system_class", text="")
|
||||||
row.operator("bim.add_system", text="", icon="ADD")
|
row.operator("bim.add_system", text="", icon="ADD")
|
||||||
else:
|
else:
|
||||||
row.operator("bim.load_systems", text="", icon="GREASEPENCIL")
|
row.operator("bim.load_systems", text="", icon="GREASEPENCIL")
|
||||||
|
|||||||
@@ -88,4 +88,4 @@ class BIM_PT_type(Panel):
|
|||||||
|
|
||||||
|
|
||||||
def add_object_button(self, context):
|
def add_object_button(self, context):
|
||||||
self.layout.operator("bim.add_type_instance", icon="PLUGIN")
|
self.layout.operator("bim.add_constr_type_instance", icon="PLUGIN")
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ class UnitsData:
|
|||||||
"unit_classes": cls.unit_classes(),
|
"unit_classes": cls.unit_classes(),
|
||||||
"named_unit_types": cls.named_unit_types(),
|
"named_unit_types": cls.named_unit_types(),
|
||||||
"conversion_unit_types": cls.conversion_unit_types(),
|
"conversion_unit_types": cls.conversion_unit_types(),
|
||||||
"total_units": cls.get_total_units()
|
"total_units": cls.get_total_units(),
|
||||||
}
|
}
|
||||||
cls.is_loaded = True
|
cls.is_loaded = True
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
import blenderbim.bim.helper
|
import blenderbim.bim.helper
|
||||||
from bpy.types import Panel, UIList
|
from bpy.types import Panel, UIList
|
||||||
from blenderbim.bim.ifc import IfcStore
|
from blenderbim.bim.ifc import IfcStore
|
||||||
|
from blenderbim.bim.helper import prop_with_search
|
||||||
from blenderbim.bim.module.unit.data import UnitsData
|
from blenderbim.bim.module.unit.data import UnitsData
|
||||||
|
|
||||||
|
|
||||||
@@ -53,22 +54,22 @@ class BIM_PT_units(Panel):
|
|||||||
return
|
return
|
||||||
|
|
||||||
row = self.layout.row(align=True)
|
row = self.layout.row(align=True)
|
||||||
row.prop(self.props, "unit_classes", text="")
|
prop_with_search(row, self.props, "unit_classes", text="")
|
||||||
|
|
||||||
if self.props.unit_classes == "IfcMonetaryUnit":
|
if self.props.unit_classes == "IfcMonetaryUnit":
|
||||||
row.operator("bim.add_monetary_unit", text="", icon="ADD")
|
row.operator("bim.add_monetary_unit", text="", icon="ADD")
|
||||||
elif self.props.unit_classes in ("IfcConversionBasedUnit", "IfcConversionBasedUnitWithOffset"):
|
elif self.props.unit_classes in ("IfcConversionBasedUnit", "IfcConversionBasedUnitWithOffset"):
|
||||||
row.prop(self.props, "conversion_unit_types", text="")
|
prop_with_search(row, self.props, "conversion_unit_types", text="")
|
||||||
op = row.operator("bim.add_conversion_based_unit", text="", icon="ADD")
|
op = row.operator("bim.add_conversion_based_unit", text="", icon="ADD")
|
||||||
op.name = self.props.conversion_unit_types
|
op.name = self.props.conversion_unit_types
|
||||||
elif self.props.unit_classes == "IfcDerivedUnit":
|
elif self.props.unit_classes == "IfcDerivedUnit":
|
||||||
pass # TODO
|
pass # TODO
|
||||||
elif self.props.unit_classes == "IfcSIUnit":
|
elif self.props.unit_classes == "IfcSIUnit":
|
||||||
row.prop(self.props, "named_unit_types", text="")
|
prop_with_search(row, self.props, "named_unit_types", text="")
|
||||||
op = row.operator("bim.add_si_unit", text="", icon="ADD")
|
op = row.operator("bim.add_si_unit", text="", icon="ADD")
|
||||||
op.unit_type = self.props.named_unit_types
|
op.unit_type = self.props.named_unit_types
|
||||||
elif self.props.unit_classes == "IfcContextDependentUnit":
|
elif self.props.unit_classes == "IfcContextDependentUnit":
|
||||||
row.prop(self.props, "named_unit_types", text="")
|
prop_with_search(row, self.props, "named_unit_types", text="")
|
||||||
op = row.operator("bim.add_context_dependent_unit", text="", icon="ADD")
|
op = row.operator("bim.add_context_dependent_unit", text="", icon="ADD")
|
||||||
op.name = "THINGAMAJIG"
|
op.name = "THINGAMAJIG"
|
||||||
op.unit_type = self.props.named_unit_types
|
op.unit_type = self.props.named_unit_types
|
||||||
|
|||||||
@@ -25,7 +25,9 @@ import blenderbim.bim.handler
|
|||||||
import blenderbim.tool as tool
|
import blenderbim.tool as tool
|
||||||
from . import schema
|
from . import schema
|
||||||
from blenderbim.bim.ifc import IfcStore
|
from blenderbim.bim.ifc import IfcStore
|
||||||
|
from blenderbim.bim.prop import StrProperty
|
||||||
from blenderbim.bim.ui import IFCFileSelector
|
from blenderbim.bim.ui import IFCFileSelector
|
||||||
|
from blenderbim.bim.helper import get_enum_items, close_operator_panel
|
||||||
from mathutils import Vector, Matrix, Euler
|
from mathutils import Vector, Matrix, Euler
|
||||||
from math import radians
|
from math import radians
|
||||||
|
|
||||||
@@ -523,3 +525,88 @@ class ConfigureVisibility(bpy.types.Operator):
|
|||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
def update_enum_property_search_prop(self, context):
|
||||||
|
for i, prop in enumerate(self.collection_names):
|
||||||
|
if prop.name == self.dummy_name:
|
||||||
|
setattr(context.data, self.prop_name, self.collection_identifiers[i].name)
|
||||||
|
close_operator_panel(self)
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
class BIM_OT_enum_property_search(bpy.types.Operator):
|
||||||
|
bl_idname = "bim.enum_property_search"
|
||||||
|
bl_label = "Search For Property"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
dummy_name: bpy.props.StringProperty(name="Property", update=update_enum_property_search_prop)
|
||||||
|
collection_names: bpy.props.CollectionProperty(type=StrProperty)
|
||||||
|
collection_identifiers: bpy.props.CollectionProperty(type=StrProperty)
|
||||||
|
prop_name: bpy.props.StringProperty()
|
||||||
|
mouse_x: bpy.props.IntProperty()
|
||||||
|
mouse_y: bpy.props.IntProperty()
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
self.mouse_x, self.mouse_y = event.mouse_x, event.mouse_y
|
||||||
|
self.clear_collections()
|
||||||
|
self.data = context.data
|
||||||
|
items = get_enum_items(self.data, self.prop_name, context)
|
||||||
|
if items is None:
|
||||||
|
return {"FINISHED"}
|
||||||
|
self.add_items_regular(items)
|
||||||
|
self.add_items_suggestions()
|
||||||
|
# Cursor is moved when we update dummy_name, set it back
|
||||||
|
context.window.cursor_warp(self.mouse_x, self.mouse_y)
|
||||||
|
return context.window_manager.invoke_props_popup(self, event)
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
# Mandatory to access context.data in update :
|
||||||
|
self.layout.context_pointer_set(name="data", data=self.data)
|
||||||
|
self.layout.prop_search(self, "dummy_name", self, "collection_names")
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
def clear_collections(self):
|
||||||
|
self.collection_names.clear()
|
||||||
|
self.collection_identifiers.clear()
|
||||||
|
|
||||||
|
def add_item(self, identifier: str, name: str):
|
||||||
|
self.collection_identifiers.add().name = identifier
|
||||||
|
self.collection_names.add().name = name
|
||||||
|
|
||||||
|
def add_items_regular(self, items):
|
||||||
|
self.identifiers = []
|
||||||
|
for item in items:
|
||||||
|
self.identifiers.append(item[0])
|
||||||
|
self.add_item(identifier=item[0], name=item[1])
|
||||||
|
if item[0] == getattr(self.data, self.prop_name):
|
||||||
|
self.dummy_name = item[1] # We found the current enum name
|
||||||
|
|
||||||
|
def add_items_suggestions(self):
|
||||||
|
getter_suggestions = getattr(self.data, "getter_enum_suggestions", None)
|
||||||
|
if getter_suggestions is not None:
|
||||||
|
mapping = getter_suggestions.get(self.prop_name)
|
||||||
|
if mapping is None:
|
||||||
|
return
|
||||||
|
for key, values in mapping().items():
|
||||||
|
if key in self.identifiers:
|
||||||
|
if not isinstance(values, (tuple, list)):
|
||||||
|
values = [values]
|
||||||
|
for value in values:
|
||||||
|
self.add_item(identifier=key, name=key + " (" + value + ")")
|
||||||
|
|
||||||
|
class EditBlenderCollection(bpy.types.Operator):
|
||||||
|
bl_idname = "bim.edit_blender_collection"
|
||||||
|
bl_label = "Add or Remove blender collection item"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
option: bpy.props.StringProperty(description="add or remove item from collection")
|
||||||
|
collection: bpy.props.StringProperty(description="collection to be edited")
|
||||||
|
index: bpy.props.IntProperty(description="index of item to be removed")
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
if self.option == "add":
|
||||||
|
getattr(context.bim_prop_group, self.collection).add()
|
||||||
|
else:
|
||||||
|
getattr(context.bim_prop_group, self.collection).remove(self.index)
|
||||||
|
return {"FINISHED"}
|
||||||
|
|||||||
@@ -195,6 +195,7 @@ class Attribute(PropertyGroup):
|
|||||||
is_null: BoolProperty(name="Is Null")
|
is_null: BoolProperty(name="Is Null")
|
||||||
is_optional: BoolProperty(name="Is Optional")
|
is_optional: BoolProperty(name="Is Optional")
|
||||||
is_uri: BoolProperty(name="Is Uri", default=False)
|
is_uri: BoolProperty(name="Is Uri", default=False)
|
||||||
|
is_selected: BoolProperty(name="Is Selected", default=False)
|
||||||
|
|
||||||
def get_value(self):
|
def get_value(self):
|
||||||
if self.is_null:
|
if self.is_null:
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ def add_drawing(ifc, collector, drawing, target_view=None, location_hint=None):
|
|||||||
)
|
)
|
||||||
group = ifc.run("group.add_group")
|
group = ifc.run("group.add_group")
|
||||||
ifc.run("group.edit_group", group=group, attributes={"Name": drawing_name, "ObjectType": "DRAWING"})
|
ifc.run("group.edit_group", group=group, attributes={"Name": drawing_name, "ObjectType": "DRAWING"})
|
||||||
ifc.run("group.assign_group", group=group, product=element)
|
ifc.run("group.assign_group", group=group, product=[element])
|
||||||
collector.assign(camera)
|
collector.assign(camera)
|
||||||
pset = ifc.run("pset.add_pset", product=element, name="EPset_Drawing")
|
pset = ifc.run("pset.add_pset", product=element, name="EPset_Drawing")
|
||||||
ifc.run(
|
ifc.run(
|
||||||
@@ -190,7 +190,7 @@ def update_drawing_name(ifc, drawing_tool, drawing=None, name=None):
|
|||||||
def add_annotation(ifc, collector, drawing_tool, drawing=None, object_type=None):
|
def add_annotation(ifc, collector, drawing_tool, drawing=None, object_type=None):
|
||||||
context = drawing_tool.get_annotation_context(drawing_tool.get_drawing_target_view(drawing))
|
context = drawing_tool.get_annotation_context(drawing_tool.get_drawing_target_view(drawing))
|
||||||
if not context:
|
if not context:
|
||||||
return
|
return "No annotation context for drawing"
|
||||||
drawing_tool.show_decorations()
|
drawing_tool.show_decorations()
|
||||||
obj = drawing_tool.create_annotation_object(drawing, object_type)
|
obj = drawing_tool.create_annotation_object(drawing, object_type)
|
||||||
element = ifc.get_entity(obj)
|
element = ifc.get_entity(obj)
|
||||||
@@ -203,7 +203,7 @@ def add_annotation(ifc, collector, drawing_tool, drawing=None, object_type=None)
|
|||||||
context=context,
|
context=context,
|
||||||
ifc_representation_class=drawing_tool.get_ifc_representation_class(object_type),
|
ifc_representation_class=drawing_tool.get_ifc_representation_class(object_type),
|
||||||
)
|
)
|
||||||
ifc.run("group.assign_group", group=drawing_tool.get_drawing_group(drawing), product=element)
|
ifc.run("group.assign_group", group=drawing_tool.get_drawing_group(drawing), product=[element])
|
||||||
collector.assign(obj)
|
collector.assign(obj)
|
||||||
drawing_tool.enable_editing(obj)
|
drawing_tool.enable_editing(obj)
|
||||||
|
|
||||||
@@ -251,7 +251,7 @@ def sync_references(ifc, collector, drawing_tool, drawing=None):
|
|||||||
annotation = drawing_tool.generate_reference_annotation(drawing, reference_element, context)
|
annotation = drawing_tool.generate_reference_annotation(drawing, reference_element, context)
|
||||||
if annotation:
|
if annotation:
|
||||||
ifc.run("drawing.assign_product", relating_product=reference_element, related_object=annotation)
|
ifc.run("drawing.assign_product", relating_product=reference_element, related_object=annotation)
|
||||||
ifc.run("group.assign_group", group=group, product=annotation)
|
ifc.run("group.assign_group", group=group, product=[annotation])
|
||||||
collector.assign(ifc.get_object(annotation))
|
collector.assign(ifc.get_object(annotation))
|
||||||
|
|
||||||
if reference_obj and ifc.is_moved(reference_obj):
|
if reference_obj and ifc.is_moved(reference_obj):
|
||||||
|
|||||||
@@ -120,8 +120,8 @@ def get_representation_ifc_parameters(geometry, obj=None, should_sync_changes_fi
|
|||||||
|
|
||||||
def remove_representation(ifc, geometry, obj=None, representation=None):
|
def remove_representation(ifc, geometry, obj=None, representation=None):
|
||||||
element = ifc.get_entity(obj)
|
element = ifc.get_entity(obj)
|
||||||
if geometry.is_mapped_representation(representation) or geometry.is_type_product(element):
|
type = geometry.get_element_type(element)
|
||||||
type = geometry.get_element_type(element)
|
if type and (geometry.is_mapped_representation(representation) or geometry.is_type_product(element)):
|
||||||
representation = geometry.resolve_mapped_representation(representation)
|
representation = geometry.resolve_mapped_representation(representation)
|
||||||
data = geometry.get_representation_data(representation)
|
data = geometry.get_representation_data(representation)
|
||||||
if data and geometry.has_data_users(data):
|
if data and geometry.has_data_users(data):
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||||
|
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>, 2022 Yassine Oualid <yassine@sigmadimensions.com>
|
||||||
|
#
|
||||||
|
# This file is part of BlenderBIM Add-on.
|
||||||
|
#
|
||||||
|
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
def add_work_plan(ifc, sequence):
|
||||||
|
ifc.run("sequence.add_work_plan")
|
||||||
|
sequence.load_work_plans()
|
||||||
|
|
||||||
|
|
||||||
|
def remove_work_plan(ifc, sequence, work_plan=None):
|
||||||
|
ifc.run("sequence.remove_work_plan", **{"work_plan": ifc.get().by_id(work_plan)})
|
||||||
|
sequence.load_work_plans()
|
||||||
|
|
||||||
|
|
||||||
|
def load_work_plan_attributes(sequence, work_plan=None):
|
||||||
|
data = sequence.get_ifc_work_plan_attributes(work_plan)
|
||||||
|
sequence.load_work_plan_attributes(data)
|
||||||
|
|
||||||
|
|
||||||
|
def enable_editing_work_plan(sequence, work_plan=None):
|
||||||
|
load_work_plan_attributes(sequence, work_plan)
|
||||||
|
sequence.enable_editing_work_plan(work_plan)
|
||||||
|
|
||||||
|
|
||||||
|
def disable_editing_work_plan(sequence):
|
||||||
|
sequence.disable_editing_work_plan()
|
||||||
|
|
||||||
|
|
||||||
|
def edit_work_plan(ifc, sequence):
|
||||||
|
work_plan = sequence.get_current_ifc_work_plan()
|
||||||
|
attributes = sequence.get_work_plan_attributes()
|
||||||
|
ifc.run("sequence.edit_work_plan", **{"work_plan": work_plan, "attributes": attributes})
|
||||||
|
sequence.disable_editing_work_plan()
|
||||||
|
sequence.load_work_plans()
|
||||||
@@ -421,6 +421,19 @@ class Selector:
|
|||||||
def set_active(cls, obj): pass
|
def set_active(cls, obj): pass
|
||||||
|
|
||||||
|
|
||||||
|
@interface
|
||||||
|
class Sequence:
|
||||||
|
def get_work_plans(cls): pass
|
||||||
|
def load_work_plans(cls): pass
|
||||||
|
def enable_editing_work_plan(cls, work_plan): pass
|
||||||
|
def disable_editing_work_plan(cls): pass
|
||||||
|
def get_current_ifc_work_plan(cls): pass
|
||||||
|
def get_ifc_work_plan_attributes(cls): pass
|
||||||
|
def load_work_plan_attributes(cls): pass
|
||||||
|
def import_attributes(cls): pass
|
||||||
|
def export_attributes(cls): pass
|
||||||
|
|
||||||
|
|
||||||
@interface
|
@interface
|
||||||
class Spatial:
|
class Spatial:
|
||||||
def can_contain(cls, structure_obj, element_obj): pass
|
def can_contain(cls, structure_obj, element_obj): pass
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ from blenderbim.tool.qto import Qto
|
|||||||
from blenderbim.tool.root import Root
|
from blenderbim.tool.root import Root
|
||||||
from blenderbim.tool.spatial import Spatial
|
from blenderbim.tool.spatial import Spatial
|
||||||
from blenderbim.tool.structural import Structural
|
from blenderbim.tool.structural import Structural
|
||||||
|
from blenderbim.tool.sequence import Sequence
|
||||||
from blenderbim.tool.style import Style
|
from blenderbim.tool.style import Style
|
||||||
from blenderbim.tool.surveyor import Surveyor
|
from blenderbim.tool.surveyor import Surveyor
|
||||||
from blenderbim.tool.system import System
|
from blenderbim.tool.system import System
|
||||||
|
|||||||
@@ -82,7 +82,8 @@ class Collector(blenderbim.core.tool.Collector):
|
|||||||
if obj.users_collection != (object_collection,):
|
if obj.users_collection != (object_collection,):
|
||||||
for collection in obj.users_collection:
|
for collection in obj.users_collection:
|
||||||
collection.objects.unlink(obj)
|
collection.objects.unlink(obj)
|
||||||
object_collection.objects.link(obj)
|
if object_collection is not None:
|
||||||
|
object_collection.objects.link(obj)
|
||||||
|
|
||||||
if collection_collection and collection_collection.children.find(object_collection.name) == -1:
|
if collection_collection and collection_collection.children.find(object_collection.name) == -1:
|
||||||
if bpy.context.scene.collection.children.find(object_collection.name) != -1:
|
if bpy.context.scene.collection.children.find(object_collection.name) != -1:
|
||||||
|
|||||||
@@ -375,15 +375,11 @@ class Drawing(blenderbim.core.tool.Drawing):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def open_spreadsheet(cls, uri):
|
def open_spreadsheet(cls, uri):
|
||||||
cls.open_with_user_command(
|
cls.open_with_user_command(bpy.context.preferences.addons["blenderbim"].preferences.spreadsheet_command, uri)
|
||||||
bpy.context.preferences.addons["blenderbim"].preferences.spreadsheet_command, uri
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def open_svg(cls, uri):
|
def open_svg(cls, uri):
|
||||||
cls.open_with_user_command(
|
cls.open_with_user_command(bpy.context.preferences.addons["blenderbim"].preferences.svg_command, uri)
|
||||||
bpy.context.preferences.addons["blenderbim"].preferences.svg_command, uri
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def run_root_assign_class(
|
def run_root_assign_class(
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ class Material(blenderbim.core.tool.Material):
|
|||||||
materials = sorted(tool.Ifc.get().by_type(material_type), key=get_name)
|
materials = sorted(tool.Ifc.get().by_type(material_type), key=get_name)
|
||||||
categories = {}
|
categories = {}
|
||||||
if material_type == "IfcMaterial":
|
if material_type == "IfcMaterial":
|
||||||
[categories.setdefault(m.Category, []).append(m) for m in materials]
|
[categories.setdefault(getattr(m, "Category", "Uncategorised"), []).append(m) for m in materials]
|
||||||
for category, mats in categories.items():
|
for category, mats in categories.items():
|
||||||
cat = props.materials.add()
|
cat = props.materials.add()
|
||||||
cat.name = category or ""
|
cat.name = category or ""
|
||||||
@@ -75,7 +75,9 @@ class Material(blenderbim.core.tool.Material):
|
|||||||
new = props.materials.add()
|
new = props.materials.add()
|
||||||
new.ifc_definition_id = material.id()
|
new.ifc_definition_id = material.id()
|
||||||
new.name = get_name(material)
|
new.name = get_name(material)
|
||||||
new.total_elements = len(ifcopenshell.util.element.get_elements_by_material(tool.Ifc.get(), material))
|
new.total_elements = len(
|
||||||
|
ifcopenshell.util.element.get_elements_by_material(tool.Ifc.get(), material)
|
||||||
|
)
|
||||||
return
|
return
|
||||||
for material in materials:
|
for material in materials:
|
||||||
new = props.materials.add()
|
new = props.materials.add()
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||||
|
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>, 2022 Yassine Oualid <yassine@sigmadimensions.com>
|
||||||
|
#
|
||||||
|
# This file is part of BlenderBIM Add-on.
|
||||||
|
#
|
||||||
|
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
import ifcopenshell
|
||||||
|
import json
|
||||||
|
import blenderbim.core.tool
|
||||||
|
import blenderbim.tool as tool
|
||||||
|
import blenderbim.bim.helper
|
||||||
|
import blenderbim.bim.module.sequence.helper as helper
|
||||||
|
|
||||||
|
|
||||||
|
class Sequence(blenderbim.core.tool.Sequence):
|
||||||
|
@classmethod
|
||||||
|
def get_work_plans(cls):
|
||||||
|
work_plans = {}
|
||||||
|
for work_plan in tool.Ifc.get().by_type("IfcWorkPlan"):
|
||||||
|
work_plans[work_plan.id()] = {"Name": work_plan.Name}
|
||||||
|
return work_plans
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def load_work_plans(cls):
|
||||||
|
work_plans = tool.Sequence.get_work_plans()
|
||||||
|
props = bpy.context.scene.BIMWorkPlanProperties
|
||||||
|
props.work_plans.clear()
|
||||||
|
for ifc_definition_id, work_plan in work_plans.items():
|
||||||
|
new = props.work_plans.add()
|
||||||
|
new.ifc_definition_id = ifc_definition_id
|
||||||
|
new.name = work_plan["Name"] or "Unnamed"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def enable_editing_work_plan(cls, work_plan):
|
||||||
|
if work_plan:
|
||||||
|
bpy.context.scene.BIMWorkPlanProperties.active_work_plan_id = work_plan
|
||||||
|
bpy.context.scene.BIMWorkPlanProperties.editing_type = "ATTRIBUTES"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def disable_editing_work_plan(cls):
|
||||||
|
bpy.context.scene.BIMWorkPlanProperties.active_work_plan_id = 0
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_current_ifc_work_plan(cls):
|
||||||
|
active_work_plan = bpy.context.scene.BIMWorkPlanProperties.active_work_plan_id
|
||||||
|
ifc_work_plan = tool.Ifc.get().by_id(active_work_plan)
|
||||||
|
return ifc_work_plan
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_ifc_work_plan_attributes(cls, work_plan):
|
||||||
|
if work_plan:
|
||||||
|
ifc_work_plan = tool.Ifc.get().by_id(work_plan)
|
||||||
|
data = ifc_work_plan.get_info()
|
||||||
|
del data["OwnerHistory"]
|
||||||
|
if data["Creators"]:
|
||||||
|
data["Creators"] = [p.id() for p in data["Creators"]]
|
||||||
|
data["CreationDate"] = ifcopenshell.util.date.ifc2datetime(data["CreationDate"])
|
||||||
|
data["StartTime"] = ifcopenshell.util.date.ifc2datetime(data["StartTime"])
|
||||||
|
if data["FinishTime"]:
|
||||||
|
data["FinishTime"] = ifcopenshell.util.date.ifc2datetime(data["FinishTime"])
|
||||||
|
data["IsDecomposedBy"] = []
|
||||||
|
for rel in ifc_work_plan.IsDecomposedBy:
|
||||||
|
data["IsDecomposedBy"].extend([o.id() for o in rel.RelatedObjects])
|
||||||
|
return data
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def load_work_plan_attributes(cls, data):
|
||||||
|
props = bpy.context.scene.BIMWorkPlanProperties
|
||||||
|
props.work_plan_attributes.clear()
|
||||||
|
blenderbim.bim.helper.import_attributes(
|
||||||
|
"IfcWorkPlan", props.work_plan_attributes, data, tool.Sequence.import_attributes
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def import_attributes(name, prop, data):
|
||||||
|
if name in ["CreationDate", "StartTime", "FinishTime"]:
|
||||||
|
prop.string_value = "" if prop.is_null else data[name].isoformat()
|
||||||
|
return True
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_work_plan_attributes(cls):
|
||||||
|
props = bpy.context.scene.BIMWorkPlanProperties
|
||||||
|
attributes = blenderbim.bim.helper.export_attributes(
|
||||||
|
props.work_plan_attributes, tool.Sequence.export_attributes
|
||||||
|
)
|
||||||
|
return attributes
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def export_attributes(attributes, prop):
|
||||||
|
if "Date" in prop.name or "Time" in prop.name:
|
||||||
|
if prop.is_null:
|
||||||
|
attributes[prop.name] = None
|
||||||
|
return True
|
||||||
|
attributes[prop.name] = helper.parse_datetime(prop.string_value)
|
||||||
|
return True
|
||||||
|
elif prop.name == "Duration" or prop.name == "TotalFloat":
|
||||||
|
if prop.is_null:
|
||||||
|
attributes[prop.name] = None
|
||||||
|
return True
|
||||||
|
attributes[prop.name] = helper.parse_duration(prop.string_value)
|
||||||
|
return True
|
||||||
@@ -63,7 +63,7 @@ architecture flow charts and diagrams. The code and its comments will guide you
|
|||||||
through the process.
|
through the process.
|
||||||
|
|
||||||
Before playing with the demo module, you may want to switch to using a source
|
Before playing with the demo module, you may want to switch to using a source
|
||||||
installation. See `blenderbim/installation <./installation.rst>`_ for details.
|
installation. See `blenderbim/installation <./installation.html>`_ for details.
|
||||||
|
|
||||||
To see the demo module in action, you'll need to enable it. In
|
To see the demo module in action, you'll need to enable it. In
|
||||||
``src/blenderbim/blenderbim/bim/__init__.py``, uncomment the line for the demo
|
``src/blenderbim/blenderbim/bim/__init__.py``, uncomment the line for the demo
|
||||||
@@ -95,7 +95,7 @@ and how to test and structure it so that you can build incredibly complex
|
|||||||
features in a maintainable way.
|
features in a maintainable way.
|
||||||
|
|
||||||
Tests for quality checking also exist. The system is designed so that you can
|
Tests for quality checking also exist. The system is designed so that you can
|
||||||
do "Test Driven Development". For reference on how to run these tests, see `blenderbim/running_tests <./running_tests.rst>`_
|
do "Test Driven Development". For reference on how to run these tests, see `blenderbim/running_tests <./running_tests.html>`_
|
||||||
for details. You can find the tests here:
|
for details. You can find the tests here:
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|||||||
@@ -108,12 +108,12 @@ On Windows:
|
|||||||
$ mklink /D "\path\to\blender\X.XX\scripts\addons\blenderbim\bim" "src\blenderbim\blenderbim\bim"
|
$ mklink /D "\path\to\blender\X.XX\scripts\addons\blenderbim\bim" "src\blenderbim\blenderbim\bim"
|
||||||
|
|
||||||
# Remove the IfcOpenShell dependency Python code
|
# Remove the IfcOpenShell dependency Python code
|
||||||
$ rd \S \Q "\path\to\blender\X.XX\scripts\addons\blenderbim\libs\site\packages\ifcopenshell\api"
|
$ rd /S /Q "\path\to\blender\X.XX\scripts\addons\blenderbim\libs\site\packages\ifcopenshell\api"
|
||||||
$ rd \S \Q "\path\to\blender\X.XX\scripts\addons\blenderbim\libs\site\packages\ifcopenshell\util"
|
$ rd /S /Q "\path\to\blender\X.XX\scripts\addons\blenderbim\libs\site\packages\ifcopenshell\util"
|
||||||
|
|
||||||
# Replace them with links to the Git repository
|
# Replace them with links to the Git repository
|
||||||
$ mklink \D "\path\to\blender\X.XX\scripts\addons\blenderbim\libs\site\packages\ifcopenshell\api" "src\ifcopenshell-python\ifcopenshell\api"
|
$ mklink /D "\path\to\blender\X.XX\scripts\addons\blenderbim\libs\site\packages\ifcopenshell\api" "src\ifcopenshell-python\ifcopenshell\api"
|
||||||
$ mklink \D "\path\to\blender\X.XX\scripts\addons\blenderbim\libs\site\packages\ifcopenshell\util" "src\ifcopenshell-python\ifcopenshell\util"
|
$ mklink /D "\path\to\blender\X.XX\scripts\addons\blenderbim\libs\site\packages\ifcopenshell\util" "src\ifcopenshell-python\ifcopenshell\util"
|
||||||
|
|
||||||
|
|
||||||
After you modify your code in the Git repository, you will need to restart
|
After you modify your code in the Git repository, you will need to restart
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ Running tests
|
|||||||
The BlenderBIM Add-on has three layers of tests for each of its three technology
|
The BlenderBIM Add-on has three layers of tests for each of its three technology
|
||||||
layers. These roughly form a test pyramid, moving from many abstract domain
|
layers. These roughly form a test pyramid, moving from many abstract domain
|
||||||
logic tests, to low-level concrete unit tests, to a minimal number of UI and
|
logic tests, to low-level concrete unit tests, to a minimal number of UI and
|
||||||
smoke tests. These tests use ``pytest`` as the test framework and runner.
|
smoke tests. These tests use ``pytest`` as the test framework and runner, so you
|
||||||
|
will need to install ``pytest``.
|
||||||
|
|
||||||
All development is expected to use test driven development, and so we expect
|
All development is expected to use test driven development, and so we expect
|
||||||
test coverage to be 100% where it is technically possible to test.
|
test coverage to be 100% where it is technically possible to test.
|
||||||
@@ -39,8 +40,8 @@ similar.
|
|||||||
Tool tests
|
Tool tests
|
||||||
----------
|
----------
|
||||||
|
|
||||||
The tool layer tests actual concrete functions. These have the following
|
The tool layer tests actual concrete functions. You will need to install the
|
||||||
dependencies:
|
following dependencies:
|
||||||
|
|
||||||
* pytest-blender, accessible to your system's Python
|
* pytest-blender, accessible to your system's Python
|
||||||
* Blender executable, accessible to pytest-blender on your system's Python
|
* Blender executable, accessible to pytest-blender on your system's Python
|
||||||
@@ -51,6 +52,16 @@ dependencies:
|
|||||||
You can install the dependencies by running the ``scripts/setup_pytest.py``
|
You can install the dependencies by running the ``scripts/setup_pytest.py``
|
||||||
script in Blender.
|
script in Blender.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
The ``scripts/setup_pytest.py`` may not work for all operating systems and
|
||||||
|
installation environments. In this case, you may be required to install the
|
||||||
|
dependencies manually.
|
||||||
|
|
||||||
|
Please be aware that some Blender may come packaged with its own Python,
|
||||||
|
which may be separate to the Python installation on your system. Be sure to
|
||||||
|
install the dependencies to the correct Python environment.
|
||||||
|
|
||||||
Then, run the tests. This will launch Blender headlessly and check the behaviour
|
Then, run the tests. This will launch Blender headlessly and check the behaviour
|
||||||
of all concrete functions.
|
of all concrete functions.
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ markers =
|
|||||||
pset_template
|
pset_template
|
||||||
qto
|
qto
|
||||||
root
|
root
|
||||||
|
search
|
||||||
sequence
|
sequence
|
||||||
spatial
|
spatial
|
||||||
structural
|
structural
|
||||||
|
|||||||
@@ -16,13 +16,40 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import subprocess
|
|
||||||
import sys
|
import sys
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
print("Here are the detected system paths:")
|
||||||
|
print(sys.path)
|
||||||
|
|
||||||
py_exec = str(sys.executable)
|
py_exec = str(sys.executable)
|
||||||
|
|
||||||
|
print("Detected executable:", py_exec)
|
||||||
|
|
||||||
subprocess.call([py_exec, "-m", "ensurepip", "--user"])
|
subprocess.call([py_exec, "-m", "ensurepip", "--user"])
|
||||||
subprocess.call([py_exec, "-m", "pip", "install", "--upgrade", "pip"])
|
subprocess.call([py_exec, "-m", "pip", "install", "--upgrade", "pip"])
|
||||||
subprocess.call([py_exec, "-m", "pip", "install", "pytest"])
|
|
||||||
subprocess.call([py_exec, "-m", "pip", "install", "pytest-blender"])
|
sys_paths = [p for p in sys.path if "site-packages" in p]
|
||||||
subprocess.call([py_exec, "-m", "pip", "install", "pytest-bdd"])
|
if sys_paths:
|
||||||
subprocess.call([py_exec, "-m", "pip", "install", "pygments"])
|
print("Detected installation directory:", sys_paths[-1])
|
||||||
|
subprocess.call([py_exec, "-m", "pip", "install", f"--target={sys_paths[-1]}", "pytest"])
|
||||||
|
subprocess.call([py_exec, "-m", "pip", "install", f"--target={sys_paths[-1]}", "pytest-bdd"])
|
||||||
|
subprocess.call([py_exec, "-m", "pip", "install", f"--target={sys_paths[-1]}", "pytest-blender"])
|
||||||
|
subprocess.call([py_exec, "-m", "pip", "install", f"--target={sys_paths[-1]}", "pygments"])
|
||||||
|
else:
|
||||||
|
print("Could not detect installation directory. Good luck.")
|
||||||
|
subprocess.call([py_exec, "-m", "pip", "install", "pytest"])
|
||||||
|
subprocess.call([py_exec, "-m", "pip", "install", "pytest-bdd"])
|
||||||
|
subprocess.call([py_exec, "-m", "pip", "install", "pytest-blender"])
|
||||||
|
subprocess.call([py_exec, "-m", "pip", "install", "pygments"])
|
||||||
|
|
||||||
|
try:
|
||||||
|
import pytest
|
||||||
|
import pytest_bdd
|
||||||
|
import pytest_blender
|
||||||
|
import pygments
|
||||||
|
|
||||||
|
print("Test dependency installation was successful!")
|
||||||
|
except Exception as e:
|
||||||
|
print("Installation failed :(")
|
||||||
|
print(e)
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ Scenario: Add representation - add a new representation to a typed instance
|
|||||||
And I set "scene.BIMRootProperties.ifc_product" to "IfcElementType"
|
And I set "scene.BIMRootProperties.ifc_product" to "IfcElementType"
|
||||||
And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType"
|
And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType"
|
||||||
And I press "bim.assign_class"
|
And I press "bim.assign_class"
|
||||||
And I press "bim.add_type_instance"
|
And I press "bim.add_constr_type_instance"
|
||||||
And I press "bim.add_type_instance"
|
And I press "bim.add_constr_type_instance"
|
||||||
Then the object "IfcWall/Wall" data is a "Tessellation" representation of "Model/Body/MODEL_VIEW"
|
Then the object "IfcWall/Wall" data is a "Tessellation" representation of "Model/Body/MODEL_VIEW"
|
||||||
And the object "IfcWall/Wall.001" data is a "Tessellation" representation of "Model/Body/MODEL_VIEW"
|
And the object "IfcWall/Wall.001" data is a "Tessellation" representation of "Model/Body/MODEL_VIEW"
|
||||||
When the object "IfcWall/Wall" is selected
|
When the object "IfcWall/Wall" is selected
|
||||||
@@ -146,9 +146,9 @@ Scenario: Remove representation - remove an instanced representation from an act
|
|||||||
And I press "bim.assign_class"
|
And I press "bim.assign_class"
|
||||||
And I set "scene.BIMModelProperties.ifc_class" to "IfcWallType"
|
And I set "scene.BIMModelProperties.ifc_class" to "IfcWallType"
|
||||||
And the variable "cube" is "{ifc}.by_type('IfcWallType')[0].id()"
|
And the variable "cube" is "{ifc}.by_type('IfcWallType')[0].id()"
|
||||||
And I set "scene.BIMModelProperties.relating_type" to "{cube}"
|
And I set "scene.BIMModelProperties.relating_type_id" to "{cube}"
|
||||||
And I press "bim.add_type_instance"
|
And I press "bim.add_constr_type_instance"
|
||||||
And I press "bim.add_type_instance"
|
And I press "bim.add_constr_type_instance"
|
||||||
And the object "IfcWallType/Cube" is selected
|
And the object "IfcWallType/Cube" is selected
|
||||||
When the variable "representation" is "{ifc}.by_type('IfcWallType')[0].RepresentationMaps[1].MappedRepresentation.id()"
|
When the variable "representation" is "{ifc}.by_type('IfcWallType')[0].RepresentationMaps[1].MappedRepresentation.id()"
|
||||||
And I press "bim.remove_representation(representation_id={representation})"
|
And I press "bim.remove_representation(representation_id={representation})"
|
||||||
@@ -165,9 +165,9 @@ Scenario: Remove representation - remove an instanced representation from an act
|
|||||||
And I press "bim.assign_class"
|
And I press "bim.assign_class"
|
||||||
And I set "scene.BIMModelProperties.ifc_class" to "IfcWallType"
|
And I set "scene.BIMModelProperties.ifc_class" to "IfcWallType"
|
||||||
And the variable "cube" is "{ifc}.by_type('IfcWallType')[0].id()"
|
And the variable "cube" is "{ifc}.by_type('IfcWallType')[0].id()"
|
||||||
And I set "scene.BIMModelProperties.relating_type" to "{cube}"
|
And I set "scene.BIMModelProperties.relating_type_id" to "{cube}"
|
||||||
And I press "bim.add_type_instance"
|
And I press "bim.add_constr_type_instance"
|
||||||
And I press "bim.add_type_instance"
|
And I press "bim.add_constr_type_instance"
|
||||||
And the object "IfcWall/Wall" is selected
|
And the object "IfcWall/Wall" is selected
|
||||||
When the variable "representation" is "{ifc}.by_type('IfcWall')[0].Representation.Representations[1].id()"
|
When the variable "representation" is "{ifc}.by_type('IfcWall')[0].Representation.Representations[1].id()"
|
||||||
And I press "bim.remove_representation(representation_id={representation})"
|
And I press "bim.remove_representation(representation_id={representation})"
|
||||||
@@ -339,8 +339,8 @@ Scenario: Override duplicate move - copying a type instance with a representatio
|
|||||||
And I press "bim.assign_class"
|
And I press "bim.assign_class"
|
||||||
And I set "scene.BIMModelProperties.ifc_class" to "IfcWallType"
|
And I set "scene.BIMModelProperties.ifc_class" to "IfcWallType"
|
||||||
And the variable "cube" is "{ifc}.by_type('IfcWallType')[0].id()"
|
And the variable "cube" is "{ifc}.by_type('IfcWallType')[0].id()"
|
||||||
And I set "scene.BIMModelProperties.relating_type" to "{cube}"
|
And I set "scene.BIMModelProperties.relating_type_id" to "{cube}"
|
||||||
And I press "bim.add_type_instance"
|
And I press "bim.add_constr_type_instance"
|
||||||
And the object "IfcWall/Wall" is selected
|
And the object "IfcWall/Wall" is selected
|
||||||
When I press "object.duplicate_move"
|
When I press "object.duplicate_move"
|
||||||
Then the object "IfcWall/Wall.001" exists
|
Then the object "IfcWall/Wall.001" exists
|
||||||
|
|||||||
@@ -111,6 +111,17 @@ Scenario: Assign material - Assign a material profile set
|
|||||||
And I press "bim.assign_material"
|
And I press "bim.assign_material"
|
||||||
Then nothing happens
|
Then nothing happens
|
||||||
|
|
||||||
|
Scenario: Assign material - Assign a material constituent set
|
||||||
|
Given an empty IFC project
|
||||||
|
And I add a cube
|
||||||
|
And the object "Cube" is selected
|
||||||
|
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
|
||||||
|
And I press "bim.assign_class"
|
||||||
|
And I press "bim.add_material(obj='')"
|
||||||
|
When I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterialConstituentSet"
|
||||||
|
And I press "bim.assign_material"
|
||||||
|
Then nothing happens
|
||||||
|
|
||||||
Scenario: Select by material
|
Scenario: Select by material
|
||||||
Given an empty IFC project
|
Given an empty IFC project
|
||||||
And I press "bim.load_materials"
|
And I press "bim.load_materials"
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user