Merge branch 'IfcOpenShell:v0.7.0' into pr-daily-builds

This commit is contained in:
Kristoffer Andersen
2022-08-06 14:58:50 +02:00
committed by GitHub
181 changed files with 104255 additions and 5442 deletions
+11 -1
View File
@@ -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
+95
View File
@@ -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
+1 -1
View File
@@ -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
View File
@@ -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
+34
View File
@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Do not remove this test for UTF-8: if “Ω” doesnt 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

+78
View File
@@ -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
View File
@@ -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)
+3 -3
View File
@@ -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,
+41 -5
View File
@@ -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
+8 -13
View File
@@ -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)
+108 -40
View File
@@ -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)
+70 -13
View File
@@ -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
+87
View File
@@ -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"}
+1
View File
@@ -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:
+4 -4
View File
@@ -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):
+2 -2
View File
@@ -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()
+13
View File
@@ -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
+2 -1
View File
@@ -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:
+2 -6
View File
@@ -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(
+4 -2
View File
@@ -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()
+114
View File
@@ -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
+2 -2
View File
@@ -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:
:: ::
+4 -4
View File
@@ -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
+14 -3
View File
@@ -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.
+1
View File
@@ -22,6 +22,7 @@ markers =
pset_template pset_template
qto qto
root root
search
sequence sequence
spatial spatial
structural structural
+32 -5
View File
@@ -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