diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml
index 6211ac53ae..823780ccf2 100644
--- a/.github/workflows/cd.yml
+++ b/.github/workflows/cd.yml
@@ -16,7 +16,7 @@ jobs:
- run: echo ok go
build:
- runs-on: ubuntu-20.04
+ runs-on: ubuntu-latest
needs: activate
steps:
- uses: actions/checkout@v2
@@ -80,7 +80,7 @@ jobs:
path: build/assets/Ifc*
deliver:
- runs-on: ubuntu-20.04
+ runs-on: ubuntu-latest
needs: build
name: Docker Build, Tag, Push
diff --git a/.github/workflows/ci-bsdd-pypi.yaml b/.github/workflows/ci-bsdd-pypi.yaml
new file mode 100644
index 0000000000..e1ccf133b0
--- /dev/null
+++ b/.github/workflows/ci-bsdd-pypi.yaml
@@ -0,0 +1,48 @@
+name: ci-bsdd-pypi
+
+on:
+ schedule:
+ # ┌───────────── minute (0 - 59)
+ # │ ┌───────────── hour (0 - 23)
+ # │ │ ┌───────────── day of the month (1 - 31)
+ # │ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
+ # │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
+ # * * * * *
+ - cron: "0 0 18 * *"
+ workflow_dispatch:
+
+env:
+ major: 0
+ minor: 0
+ name: ifcopenshell
+
+jobs:
+ activate:
+ runs-on: ubuntu-latest
+ if: |
+ github.repository == 'IfcOpenShell/IfcOpenShell'
+ steps:
+ - name: Set env
+ run: echo ok go
+
+ build:
+ needs: activate
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions/setup-python@v2 # https://github.com/actions/setup-python
+ with:
+ python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
+ - name: Compile
+ run: |
+ pip install build
+ cd src/bsdd &&
+ make dist
+ - name: Publish a Python distribution to PyPI
+ uses: ortega2247/pypi-upload-action@master
+ with:
+ user: __token__
+ password: ${{ secrets.PYPI_API_TOKEN }}
+ packages_dir: src/bsdd/dist
diff --git a/.github/workflows/ci-ifcclash-pypi.yaml b/.github/workflows/ci-ifcclash-pypi.yaml
new file mode 100644
index 0000000000..9aa0349d0b
--- /dev/null
+++ b/.github/workflows/ci-ifcclash-pypi.yaml
@@ -0,0 +1,48 @@
+name: ci-ifcclash-pypi
+
+on:
+ schedule:
+ # ┌───────────── minute (0 - 59)
+ # │ ┌───────────── hour (0 - 23)
+ # │ │ ┌───────────── day of the month (1 - 31)
+ # │ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
+ # │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
+ # * * * * *
+ - cron: "0 0 18 * *"
+ workflow_dispatch:
+
+env:
+ major: 0
+ minor: 0
+ name: ifcopenshell
+
+jobs:
+ activate:
+ runs-on: ubuntu-latest
+ if: |
+ github.repository == 'IfcOpenShell/IfcOpenShell'
+ steps:
+ - name: Set env
+ run: echo ok go
+
+ build:
+ needs: activate
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions/setup-python@v2 # https://github.com/actions/setup-python
+ with:
+ python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
+ - name: Compile
+ run: |
+ pip install build
+ cd src/ifcclash &&
+ make dist
+ - name: Publish a Python distribution to PyPI
+ uses: ortega2247/pypi-upload-action@master
+ with:
+ user: __token__
+ password: ${{ secrets.PYPI_API_TOKEN }}
+ packages_dir: src/ifcclash/dist
diff --git a/.github/workflows/ci-ifccsv-pypi.yaml b/.github/workflows/ci-ifccsv-pypi.yaml
new file mode 100644
index 0000000000..c60fa790fe
--- /dev/null
+++ b/.github/workflows/ci-ifccsv-pypi.yaml
@@ -0,0 +1,48 @@
+name: ci-ifccsv-pypi
+
+on:
+ schedule:
+ # ┌───────────── minute (0 - 59)
+ # │ ┌───────────── hour (0 - 23)
+ # │ │ ┌───────────── day of the month (1 - 31)
+ # │ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
+ # │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
+ # * * * * *
+ - cron: "0 0 18 * *"
+ workflow_dispatch:
+
+env:
+ major: 0
+ minor: 0
+ name: ifcopenshell
+
+jobs:
+ activate:
+ runs-on: ubuntu-latest
+ if: |
+ github.repository == 'IfcOpenShell/IfcOpenShell'
+ steps:
+ - name: Set env
+ run: echo ok go
+
+ build:
+ needs: activate
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions/setup-python@v2 # https://github.com/actions/setup-python
+ with:
+ python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
+ - name: Compile
+ run: |
+ pip install build
+ cd src/ifccsv &&
+ make dist
+ - name: Publish a Python distribution to PyPI
+ uses: ortega2247/pypi-upload-action@master
+ with:
+ user: __token__
+ password: ${{ secrets.PYPI_API_TOKEN }}
+ packages_dir: src/ifccsv/dist
diff --git a/.github/workflows/ci-ifcopenshell-pypi.yml b/.github/workflows/ci-ifcopenshell-pypi.yml
index 0d0bf10fcd..5ee327e15c 100644
--- a/.github/workflows/ci-ifcopenshell-pypi.yml
+++ b/.github/workflows/ci-ifcopenshell-pypi.yml
@@ -1,6 +1,7 @@
name: ci-ifcopenshell-pypi
on:
+ workflow_dispatch:
schedule:
# ┌───────────── minute (0 - 59)
# │ ┌───────────── hour (0 - 23)
@@ -35,7 +36,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- pyver: [py36, py37, py38, py39, py310, py311]
+ pyver: [py39, py310, py311, py312]
config:
- {
name: "Windows Build",
@@ -57,7 +58,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
with:
- python-version: '3.10' # Version range or exact version of a Python version to use, using SemVer's version range syntax
+ python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
- run: echo ${{ env.DATE }}
- name: Get current date
diff --git a/.github/workflows/ci-ifcpatch-pypi.yaml b/.github/workflows/ci-ifcpatch-pypi.yaml
new file mode 100644
index 0000000000..710c3fba74
--- /dev/null
+++ b/.github/workflows/ci-ifcpatch-pypi.yaml
@@ -0,0 +1,48 @@
+name: ci-ifcpatch-pypi
+
+on:
+ schedule:
+ # ┌───────────── minute (0 - 59)
+ # │ ┌───────────── hour (0 - 23)
+ # │ │ ┌───────────── day of the month (1 - 31)
+ # │ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
+ # │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
+ # * * * * *
+ - cron: "0 0 18 * *"
+ workflow_dispatch:
+
+env:
+ major: 0
+ minor: 0
+ name: ifcopenshell
+
+jobs:
+ activate:
+ runs-on: ubuntu-latest
+ if: |
+ github.repository == 'IfcOpenShell/IfcOpenShell'
+ steps:
+ - name: Set env
+ run: echo ok go
+
+ build:
+ needs: activate
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions/setup-python@v2 # https://github.com/actions/setup-python
+ with:
+ python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
+ - name: Compile
+ run: |
+ pip install build
+ cd src/ifcpatch &&
+ make dist
+ - name: Publish a Python distribution to PyPI
+ uses: ortega2247/pypi-upload-action@master
+ with:
+ user: __token__
+ password: ${{ secrets.PYPI_API_TOKEN }}
+ packages_dir: src/ifcpatch/dist
diff --git a/.github/workflows/ci-ifcsverchok-build.yml b/.github/workflows/ci-ifcsverchok-build.yml
new file mode 100644
index 0000000000..637adc4441
--- /dev/null
+++ b/.github/workflows/ci-ifcsverchok-build.yml
@@ -0,0 +1,53 @@
+name: Publish-ifcsverchok
+
+on:
+ push:
+ paths:
+ - '.github/workflows/ci-ifcsverchok-build.yml'
+ - 'src/ifcsverchok/*'
+ branches:
+ - v0.7.0
+
+env:
+ major: 0
+ minor: 0
+ name: ifcsverchok
+
+jobs:
+ activate:
+ runs-on: ubuntu-latest
+ if: |
+ github.repository == 'IfcOpenShell/IfcOpenShell'
+ steps:
+ - name: Set env
+ run: echo ok go
+
+ build:
+ needs: activate
+ name: ifcsverchok
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions/setup-python@v2 # https://github.com/actions/setup-python
+ with:
+ architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
+ python-version: '3.11'
+ - run: echo ${{ env.DATE }}
+ - name: Get current date
+ id: date
+ run: echo "::set-output name=date::$(date +'%y%m%d')"
+ - name: Compile
+ run: |
+ cd src/ifcsverchok
+ make dist
+ - name: Upload Zip file to release
+ uses: svenstaro/upload-release-action@v2
+ with:
+ repo_token: ${{ secrets.GITHUB_TOKEN }}
+ file: src/ifcsverchok/dist/ifcsverchok-${{steps.date.outputs.date}}.zip
+ asset_name: ifcsverchok-${{steps.date.outputs.date}}.zip
+ tag: "ifcsverchok-${{steps.date.outputs.date}}"
+ overwrite: true
+ body: "ifcsverchok build for ${{steps.date.outputs.date}}"
diff --git a/.github/workflows/ci-ifctester-pypi.yml b/.github/workflows/ci-ifctester-pypi.yml
index d1302e38f3..24226acd86 100644
--- a/.github/workflows/ci-ifctester-pypi.yml
+++ b/.github/workflows/ci-ifctester-pypi.yml
@@ -9,9 +9,6 @@ on:
# │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
# * * * * *
- cron: "0 0 18 * *"
- push:
- paths:
- - '.github/workflows/ci-ifctester-pypi.yml'
workflow_dispatch:
env:
@@ -30,7 +27,6 @@ jobs:
build:
needs: activate
- name: ${{ matrix.config.name }}-${{ matrix.pyver }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
@@ -38,12 +34,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
with:
- python-version: '3.10' # Version range or exact version of a Python version to use, using SemVer's version range syntax
- architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
- - run: echo ${{ env.DATE }}
- - name: Get current date
- id: date
- run: echo "::set-output name=date::$(date +'%y%m%d')"
+ python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
- name: Compile
run: |
pip install build
diff --git a/.gitignore b/.gitignore
index 498b2e3f46..61037d93bf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -81,6 +81,9 @@ src/blenderbim/blenderbim/translations.py
# blenderbim test temp files
src/blenderbim/test/files/temp
+src/blenderbim/test/files/basic.ifc.cache.blend
+src/blenderbim/test/files/basic.ifc.cache.sqlite
+
src/blenderbim/drawings
src/blenderbim/layouts
diff --git a/README.md b/README.md
index f353a28940..a563f01470 100644
--- a/README.md
+++ b/README.md
@@ -18,14 +18,14 @@ and many other libraries, CLI apps, and more. Support is also provided for auxil
For more information, see:
* [IfcOpenShell Website](http://ifcopenshell.org)
-* [IfcOpenShell Documentation](http://blenderbim.org/docs-python)
- * [IfcOpenShell C++ Installation](https://blenderbim.org/docs-python/ifcopenshell/installation.html)
- * [IfcOpenShell Python Installation](https://blenderbim.org/docs-python/ifcopenshell-python/installation.html)
- * [IfcOpenShell Python Hello World Tutorial](https://blenderbim.org/docs-python/ifcopenshell-python/hello_world.html)
+* [IfcOpenShell Documentation](https://docs.ifcopenshell.org)
+ * [IfcOpenShell C++ Installation](https://docs.ifcopenshell.org/ifcopenshell/installation.html)
+ * [IfcOpenShell Python Installation](https://docs.ifcopenshell.org/ifcopenshell-python/installation.html)
+ * [IfcOpenShell Python Hello World Tutorial](https://docs.ifcopenshell.org/ifcopenshell-python/hello_world.html)
* [BlenderBIM Add-on Website](https://blenderbim.org)
-* [BlenderBIM Add-on Documentation](http://blenderbim.org/docs)
- * [Add-on Installation](https://blenderbim.org/docs/users/installation.html)
- * [Exploring an IFC model](https://blenderbim.org/docs/users/exploring_an_ifc_model.html)
+* [BlenderBIM Add-on Documentation](https://docs.blenderbim.org/index.html)
+ * [Add-on Installation](https://docs.blenderbim.org/users/installation.html)
+ * [Exploring an IFC model](https://docs.blenderbim.org/users/exploring_an_ifc_model.html)
| Service | Status |
| ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
diff --git a/aws/lambda/Dockerfile b/aws/lambda/Dockerfile
index fcffa1e87b..8076692e81 100644
--- a/aws/lambda/Dockerfile
+++ b/aws/lambda/Dockerfile
@@ -12,7 +12,7 @@ RUN apt-get -y update && apt-get -y install unzip curl
# Install AWS Lambda runtime interface client
RUN pip install --target ${FUNCTION_DIR} awslambdaric
-# Set the IfcOpenShell build version (check available builds at: https://blenderbim.org/docs-python/ifcopenshell-python/installation.html)
+# Set the IfcOpenShell build version (check available builds at: https://docs.ifcopenshell.org/ifcopenshell-python/installation.html)
ARG IFC_OPENSHELL_BUILD="39-v0.7.0-476ab50"
# Download and extract IfcOpenShell
diff --git a/choco/blenderbim/blenderbim.nuspec b/choco/blenderbim/blenderbim.nuspec
index d76e86a8ce..bafb86e381 100644
--- a/choco/blenderbim/blenderbim.nuspec
+++ b/choco/blenderbim/blenderbim.nuspec
@@ -15,7 +15,7 @@
https://github.com/IfcOpenShell/IfcOpenShell/blob/v0.7.0/COPYING
true
https://github.com/IfcOpenShell/IfcOpenShell
- https://blenderbim.org/docs/
+ https://docs.blenderbim.org/
https://github.com/IfcOpenShell/IfcOpenShell/issues
blender bim blenderbim ifc python opensource foss
diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt
index 85721778f0..8f7451afa2 100644
--- a/cmake/CMakeLists.txt
+++ b/cmake/CMakeLists.txt
@@ -1172,7 +1172,7 @@ endforeach(COMPONENT)
set(CPACK_DEBIAN_PACKAGE_NAME "${PROJECT_NAME}")
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "${CPACK_PACKAGE_CONTACT}")
-set(CPACK_DEBIAN_PACKAGE_DEPENDS "python3, libxml2, libocct-foundation-dev, libocct-modeling-algorithms-dev, libocct-modeling-data-dev, libocct-ocaf-dev, libocct-visualization-dev, libocct-data-exchange-dev, libhdf5-serial-dev, libpython3.8, python3-pytest ${BOOST_DEPS}")
+set(CPACK_DEBIAN_PACKAGE_DEPENDS "python3, libxml2, libocct-foundation-dev, libocct-modeling-algorithms-dev, libocct-modeling-data-dev, libocct-ocaf-dev, libocct-visualization-dev, libocct-data-exchange-dev, libhdf5-serial-dev, libpython3-dev, python3-pytest ${BOOST_DEPS}")
set(CPACK_DEBIAN_PACKAGE_DESCRIPTION_SUMMARY "${CPACK_PACKAGE_DESCRIPTION_SUMMARY}")
set(CPACK_DEBIAN_PACKAGE_DESCRIPTION "${CPACK_PACKAGE_DESCRIPTION}")
set(CPACK_DEBIAN_PACKAGE_PRIORITY "optional")
diff --git a/nix/build-all.py b/nix/build-all.py
index b9b017776e..7de9040c80 100644
--- a/nix/build-all.py
+++ b/nix/build-all.py
@@ -76,7 +76,7 @@ PROJECT_NAME = "IfcOpenShell"
USE_CURRENT_PYTHON_VERSION = os.getenv("USE_CURRENT_PYTHON_VERSION")
ADD_COMMIT_SHA = os.getenv("ADD_COMMIT_SHA")
-PYTHON_VERSIONS = ["3.7.13", "3.8.13", "3.9.11", "3.10.3", "3.11.7", "3.12.1"]
+PYTHON_VERSIONS = ["3.9.11", "3.10.3", "3.11.8", "3.12.1"]
JSON_VERSION = "v3.6.1"
OCE_VERSION = "0.18.3"
OCCT_VERSION = "7.5.3"
@@ -254,6 +254,11 @@ if not os.path.exists(LOG_FILE):
open(LOG_FILE, "w").close()
logger.info(f"using command log file '{LOG_FILE}'")
+# Causing havoc in python 3.11 build
+try:
+ del os.environ['__PYVENV_LAUNCHER__']
+except: pass
+
def run(cmds, cwd=None, can_fail=False):
"""
@@ -284,8 +289,9 @@ if platform.system() == "Darwin":
# Apparently not supported
PYTHON_VERSIONS = [pv for pv in PYTHON_VERSIONS if tuple(map(int, pv.split("."))) >= (3, 7)]
if run(["sw_vers", "-productVersion"]) < "10.16":
- # Apparently not supported
- PYTHON_VERSIONS = [pv for pv in PYTHON_VERSIONS if tuple(map(int, pv.split("."))) < (3, 11)]
+ # This is now solved with the '__PYVENV_LAUNCHER__' hack
+ # PYTHON_VERSIONS = [pv for pv in PYTHON_VERSIONS if tuple(map(int, pv.split("."))) < (3, 11)]
+ pass
BOOST_VERSION_UNDERSCORE = BOOST_VERSION.replace(".", "_")
diff --git a/src/blenderbim/Makefile b/src/blenderbim/Makefile
index 54f317465e..2bdf4b27a6 100644
--- a/src/blenderbim/Makefile
+++ b/src/blenderbim/Makefile
@@ -137,7 +137,7 @@ endif
cp -r blenderbim/* dist/blenderbim/
# Provides IfcOpenShell Python functionality
- cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-$(PYNUMBER)-v0.7.0-eaa2aa0-$(PLATFORM)64.zip
+ cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-$(PYNUMBER)-v0.7.0-f7c03db-$(PLATFORM)64.zip
cd dist/working && unzip ifcopenshell-python*
cp -r dist/working/ifcopenshell dist/blenderbim/libs/site/packages/
@@ -362,7 +362,9 @@ endif
# Provides express rule validation for ifcopenshell.validate
cd dist/working && . env/bin/activate && $(PIP) install pytest --target=./site-packages
# Provides Brickschema functionality
- cd dist/working && . env/bin/activate && $(PIP) install brickschema[persistence] --target=./site-packages
+ cd dist/working && . env/bin/activate && $(PIP) install "brickschema[persistence]==0.7.6a2" --target=./site-packages
+ # Required for SVG to DXF conversion
+ cd dist/working && . env/bin/activate && $(PIP) install "ezdxf" --target=./site-packages
cp -r dist/working/site-packages/* dist/blenderbim/libs/site/packages/
rm -rf dist/working
@@ -430,7 +432,7 @@ endif
# Required by brickschema
# This is a bit of a dodgy one, it should be provided by setuptools which Blender doesn't ship.
mkdir dist/working
- cd dist/working && wget https://blenderbim.org/builds/pkg_resources.tar.gz
+ cd dist/working && wget https://github.com/IfcOpenShell/IfcOpenShell/raw/v0.7.0/src/blenderbim/scripts/pkg_resources.tar.gz
cd dist/working && tar -xzvf pkg_resources*
cd dist/working/ && cp -r pkg_resources ../blenderbim/libs/site/packages/
rm -rf dist/working
diff --git a/src/blenderbim/blenderbim/__init__.py b/src/blenderbim/blenderbim/__init__.py
index 2f6e5481ba..67b03e5702 100644
--- a/src/blenderbim/blenderbim/__init__.py
+++ b/src/blenderbim/blenderbim/__init__.py
@@ -27,7 +27,7 @@ bl_info = {
"blender": (3, 1, 0),
"version": (0, 0, 999999),
"location": "File Menu, Scene Properties Tab. See documentation for more.",
- "doc_url": "https://blenderbim.org/docs",
+ "doc_url": "https://docs.blenderbim.org/",
"tracker_url": "https://github.com/IfcOpenShell/IfcOpenShell/issues",
"category": "System",
}
diff --git a/src/blenderbim/blenderbim/bim/__init__.py b/src/blenderbim/blenderbim/bim/__init__.py
index f8cc59abbb..1476a30bff 100644
--- a/src/blenderbim/blenderbim/bim/__init__.py
+++ b/src/blenderbim/blenderbim/bim/__init__.py
@@ -23,6 +23,7 @@ import bpy.utils.previews
import blenderbim
import importlib
from . import handler, ui, prop, operator, helper
+from typing import Callable, Union
try:
from blenderbim.translations import translations_dict
@@ -96,8 +97,10 @@ for name in modules.keys():
classes = [
operator.AddIfcFile,
operator.BIM_OT_add_section_plane,
+ operator.BIM_OT_delete_object,
operator.BIM_OT_open_webbrowser,
operator.BIM_OT_remove_section_plane,
+ operator.BIM_OT_select_object,
operator.BIM_OT_show_description,
operator.ClippingPlaneCutWithCappings,
operator.EditBlenderCollection,
@@ -130,6 +133,7 @@ classes = [
prop.BIMMeshProperties,
prop.BIMFacet,
prop.BIMFilterGroup,
+ ui.BIM_UL_clipping_plane,
ui.BIM_UL_generic,
ui.BIM_UL_topics,
ui.BIM_ADDON_preferences,
@@ -187,7 +191,7 @@ addon_keymaps = []
icons = None
is_registering = False
last_commit_hash = "8888888"
-overridden_scene_panels = dict()
+original_scene_panels_polls: dict[bpy.types.Panel, Union[Callable, None]] = dict()
def on_register(scene):
@@ -295,10 +299,10 @@ def unregister():
km.keymap_items.remove(kmi)
addon_keymaps.clear()
- for panel in tuple(overridden_scene_panels.keys()):
- original_panel, override_panel = overridden_scene_panels[panel]
- bpy.utils.unregister_class(override_panel)
- bpy.utils.register_class(original_panel)
- del overridden_scene_panels[panel]
+ import blenderbim.tool as tool
+
+ # use tuple() as method will be removing keys from dict
+ for panel in tuple(original_scene_panels_polls.keys()):
+ tool.Blender.remove_scene_panel_override(panel)
bpy.app.translations.unregister("blenderbim")
diff --git a/src/blenderbim/blenderbim/bim/data/assets/default.css b/src/blenderbim/blenderbim/bim/data/assets/default.css
index 2b4d93bc51..861b8ba3ba 100644
--- a/src/blenderbim/blenderbim/bim/data/assets/default.css
+++ b/src/blenderbim/blenderbim/bim/data/assets/default.css
@@ -10,7 +10,7 @@
* (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
+ * 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.
*
diff --git a/src/blenderbim/blenderbim/bim/data/assets/sample.css b/src/blenderbim/blenderbim/bim/data/assets/sample.css
index c0a7b0ec74..6b280509a4 100644
--- a/src/blenderbim/blenderbim/bim/data/assets/sample.css
+++ b/src/blenderbim/blenderbim/bim/data/assets/sample.css
@@ -10,7 +10,7 @@
* (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
+ * 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.
*
diff --git a/src/blenderbim/blenderbim/bim/data/libraries/IFC4 Entourage Library.ifc b/src/blenderbim/blenderbim/bim/data/libraries/IFC4 Entourage Library.ifc
new file mode 100644
index 0000000000..cd9cab0678
--- /dev/null
+++ b/src/blenderbim/blenderbim/bim/data/libraries/IFC4 Entourage Library.ifc
@@ -0,0 +1,1653 @@
+ISO-10303-21;
+HEADER;
+FILE_DESCRIPTION(('ViewDefinition[DesignTransferView]'),'2;1');
+FILE_NAME('/dev/null','2024-03-29T15:32:22+11:00',(),(),'IfcOpenShell v0.7.0-98386984f','IfcOpenShell v0.7.0-98386984f','Nobody');
+FILE_SCHEMA(('IFC4'));
+ENDSEC;
+DATA;
+#1=IFCPROJECT('3nrYk6M6z1humWj5w6BMD9',$,'Entourage Assets Library',$,$,$,$,(#10,#14),#5);
+#2=IFCPROJECTLIBRARY('3wn$cg$5LCi88GExc$we5s',$,'Entourage Assets Library',$,$,$,$,$,$);
+#3=IFCRELDECLARES('1bz83_LQjFQANpzUPaDfwx',$,$,$,#1,(#2));
+#4=IFCSIUNIT(*,.LENGTHUNIT.,.MILLI.,.METRE.);
+#5=IFCUNITASSIGNMENT((#4));
+#6=IFCCARTESIANPOINT((0.,0.,0.));
+#7=IFCDIRECTION((0.,0.,1.));
+#8=IFCDIRECTION((1.,0.,0.));
+#9=IFCAXIS2PLACEMENT3D(#6,#7,#8);
+#10=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-05,#9,$);
+#11=IFCCARTESIANPOINT((0.,0.));
+#12=IFCDIRECTION((1.,0.));
+#13=IFCAXIS2PLACEMENT2D(#11,#12);
+#14=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Plan',2,1.E-05,#13,$);
+#15=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Body','Model',*,*,*,*,#10,$,.MODEL_VIEW.,$);
+#16=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Body','Plan',*,*,*,*,#14,$,.PLAN_VIEW.,$);
+#17=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Body','Model',*,*,*,*,#10,$,.PLAN_VIEW.,$);
+#18=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Body','Model',*,*,*,*,#10,$,.SECTION_VIEW.,$);
+#19=IFCBUILDINGELEMENTPROXYTYPE('1rfzKQLFf6QeGzovoh2SJc',$,'Man Standing with Turban',$,$,$,(#545),$,'ENTOURAGE',.USERDEFINED.);
+#20=IFCINDEXEDPOLYGONALFACE((1,3,2));
+#21=IFCINDEXEDPOLYGONALFACE((2,3,4));
+#22=IFCINDEXEDPOLYGONALFACE((5,6,4));
+#23=IFCINDEXEDPOLYGONALFACE((7,9,8));
+#24=IFCINDEXEDPOLYGONALFACE((9,11,10));
+#25=IFCINDEXEDPOLYGONALFACE((9,7,12));
+#26=IFCINDEXEDPOLYGONALFACE((13,15,14));
+#27=IFCINDEXEDPOLYGONALFACE((16,18,17));
+#28=IFCINDEXEDPOLYGONALFACE((19,20,17));
+#29=IFCINDEXEDPOLYGONALFACE((20,21,17));
+#30=IFCINDEXEDPOLYGONALFACE((21,20,22));
+#31=IFCINDEXEDPOLYGONALFACE((21,22,23));
+#32=IFCINDEXEDPOLYGONALFACE((24,26,25));
+#33=IFCINDEXEDPOLYGONALFACE((24,25,27));
+#34=IFCINDEXEDPOLYGONALFACE((27,25,26));
+#35=IFCINDEXEDPOLYGONALFACE((28,29,27));
+#36=IFCINDEXEDPOLYGONALFACE((29,31,30));
+#37=IFCINDEXEDPOLYGONALFACE((32,34,33));
+#38=IFCINDEXEDPOLYGONALFACE((35,34,36));
+#39=IFCINDEXEDPOLYGONALFACE((36,34,32));
+#40=IFCINDEXEDPOLYGONALFACE((33,38,37));
+#41=IFCINDEXEDPOLYGONALFACE((39,41,40));
+#42=IFCINDEXEDPOLYGONALFACE((40,41,38));
+#43=IFCINDEXEDPOLYGONALFACE((42,41,39));
+#44=IFCINDEXEDPOLYGONALFACE((43,39,40));
+#45=IFCINDEXEDPOLYGONALFACE((43,44,39));
+#46=IFCINDEXEDPOLYGONALFACE((44,46,45));
+#47=IFCINDEXEDPOLYGONALFACE((47,49,48));
+#48=IFCINDEXEDPOLYGONALFACE((50,51,48));
+#49=IFCINDEXEDPOLYGONALFACE((51,50,52));
+#50=IFCINDEXEDPOLYGONALFACE((50,53,52));
+#51=IFCINDEXEDPOLYGONALFACE((54,56,55));
+#52=IFCINDEXEDPOLYGONALFACE((55,58,57));
+#53=IFCINDEXEDPOLYGONALFACE((59,57,60));
+#54=IFCINDEXEDPOLYGONALFACE((61,63,62));
+#55=IFCINDEXEDPOLYGONALFACE((18,64,61));
+#56=IFCINDEXEDPOLYGONALFACE((64,66,65));
+#57=IFCINDEXEDPOLYGONALFACE((18,16,64));
+#58=IFCINDEXEDPOLYGONALFACE((19,17,18));
+#59=IFCINDEXEDPOLYGONALFACE((67,29,28));
+#60=IFCINDEXEDPOLYGONALFACE((43,40,38));
+#61=IFCINDEXEDPOLYGONALFACE((61,68,18));
+#62=IFCINDEXEDPOLYGONALFACE((38,44,43));
+#63=IFCINDEXEDPOLYGONALFACE((69,23,22));
+#64=IFCINDEXEDPOLYGONALFACE((26,67,27));
+#65=IFCINDEXEDPOLYGONALFACE((32,70,36));
+#66=IFCINDEXEDPOLYGONALFACE((71,47,72));
+#67=IFCINDEXEDPOLYGONALFACE((62,60,73));
+#68=IFCINDEXEDPOLYGONALFACE((55,74,58));
+#69=IFCINDEXEDPOLYGONALFACE((74,55,56));
+#70=IFCINDEXEDPOLYGONALFACE((23,26,24));
+#71=IFCINDEXEDPOLYGONALFACE((29,35,31));
+#72=IFCINDEXEDPOLYGONALFACE((33,46,44));
+#73=IFCINDEXEDPOLYGONALFACE((46,33,75));
+#74=IFCINDEXEDPOLYGONALFACE((27,67,28));
+#75=IFCINDEXEDPOLYGONALFACE((38,33,44));
+#76=IFCINDEXEDPOLYGONALFACE((72,76,71));
+#77=IFCINDEXEDPOLYGONALFACE((18,68,19));
+#78=IFCINDEXEDPOLYGONALFACE((77,46,78));
+#79=IFCINDEXEDPOLYGONALFACE((70,32,79));
+#80=IFCINDEXEDPOLYGONALFACE((80,70,79));
+#81=IFCINDEXEDPOLYGONALFACE((80,79,30));
+#82=IFCINDEXEDPOLYGONALFACE((46,75,81));
+#83=IFCINDEXEDPOLYGONALFACE((81,75,82));
+#84=IFCINDEXEDPOLYGONALFACE((49,47,71));
+#85=IFCINDEXEDPOLYGONALFACE((57,58,12));
+#86=IFCINDEXEDPOLYGONALFACE((75,33,34));
+#87=IFCINDEXEDPOLYGONALFACE((11,73,60));
+#88=IFCINDEXEDPOLYGONALFACE((60,57,11));
+#89=IFCINDEXEDPOLYGONALFACE((11,57,83));
+#90=IFCINDEXEDPOLYGONALFACE((62,73,84));
+#91=IFCINDEXEDPOLYGONALFACE((85,74,56));
+#92=IFCINDEXEDPOLYGONALFACE((56,53,85));
+#93=IFCINDEXEDPOLYGONALFACE((22,20,86));
+#94=IFCINDEXEDPOLYGONALFACE((75,34,87));
+#95=IFCINDEXEDPOLYGONALFACE((61,84,68));
+#96=IFCINDEXEDPOLYGONALFACE((61,62,84));
+#97=IFCINDEXEDPOLYGONALFACE((30,88,80));
+#98=IFCINDEXEDPOLYGONALFACE((88,31,35));
+#99=IFCINDEXEDPOLYGONALFACE((88,30,31));
+#100=IFCINDEXEDPOLYGONALFACE((46,81,78));
+#101=IFCINDEXEDPOLYGONALFACE((78,81,76));
+#102=IFCINDEXEDPOLYGONALFACE((86,68,69));
+#103=IFCINDEXEDPOLYGONALFACE((20,19,86));
+#104=IFCINDEXEDPOLYGONALFACE((13,89,48));
+#105=IFCINDEXEDPOLYGONALFACE((77,78,76));
+#106=IFCINDEXEDPOLYGONALFACE((86,69,22));
+#107=IFCINDEXEDPOLYGONALFACE((67,35,29));
+#108=IFCINDEXEDPOLYGONALFACE((76,72,77));
+#109=IFCINDEXEDPOLYGONALFACE((48,89,50));
+#110=IFCINDEXEDPOLYGONALFACE((11,9,73));
+#111=IFCINDEXEDPOLYGONALFACE((57,12,83));
+#112=IFCINDEXEDPOLYGONALFACE((84,73,7));
+#113=IFCINDEXEDPOLYGONALFACE((19,68,86));
+#114=IFCINDEXEDPOLYGONALFACE((90,69,91));
+#115=IFCINDEXEDPOLYGONALFACE((92,34,35));
+#116=IFCINDEXEDPOLYGONALFACE((23,69,26));
+#117=IFCINDEXEDPOLYGONALFACE((7,73,83));
+#118=IFCINDEXEDPOLYGONALFACE((83,9,93));
+#119=IFCINDEXEDPOLYGONALFACE((73,9,83));
+#120=IFCINDEXEDPOLYGONALFACE((94,14,95));
+#121=IFCINDEXEDPOLYGONALFACE((14,15,95));
+#122=IFCINDEXEDPOLYGONALFACE((96,14,94));
+#123=IFCINDEXEDPOLYGONALFACE((87,92,96));
+#124=IFCINDEXEDPOLYGONALFACE((96,94,82));
+#125=IFCINDEXEDPOLYGONALFACE((82,87,96));
+#126=IFCINDEXEDPOLYGONALFACE((97,92,90));
+#127=IFCINDEXEDPOLYGONALFACE((90,92,35));
+#128=IFCINDEXEDPOLYGONALFACE((90,67,26));
+#129=IFCINDEXEDPOLYGONALFACE((35,67,90));
+#130=IFCINDEXEDPOLYGONALFACE((82,94,97));
+#131=IFCINDEXEDPOLYGONALFACE((98,94,95));
+#132=IFCINDEXEDPOLYGONALFACE((97,94,98));
+#133=IFCINDEXEDPOLYGONALFACE((75,87,82));
+#134=IFCINDEXEDPOLYGONALFACE((49,99,15));
+#135=IFCINDEXEDPOLYGONALFACE((49,81,99));
+#136=IFCINDEXEDPOLYGONALFACE((13,48,49));
+#137=IFCINDEXEDPOLYGONALFACE((74,97,12));
+#138=IFCINDEXEDPOLYGONALFACE((58,74,12));
+#139=IFCINDEXEDPOLYGONALFACE((97,74,85));
+#140=IFCINDEXEDPOLYGONALFACE((98,92,97));
+#141=IFCINDEXEDPOLYGONALFACE((90,91,97));
+#142=IFCINDEXEDPOLYGONALFACE((92,98,96));
+#143=IFCINDEXEDPOLYGONALFACE((99,14,96));
+#144=IFCINDEXEDPOLYGONALFACE((99,96,98));
+#145=IFCINDEXEDPOLYGONALFACE((81,50,89));
+#146=IFCINDEXEDPOLYGONALFACE((81,89,14));
+#147=IFCINDEXEDPOLYGONALFACE((81,14,99));
+#148=IFCINDEXEDPOLYGONALFACE((85,81,82));
+#149=IFCINDEXEDPOLYGONALFACE((81,49,76));
+#150=IFCINDEXEDPOLYGONALFACE((50,81,85));
+#151=IFCINDEXEDPOLYGONALFACE((49,71,76));
+#152=IFCINDEXEDPOLYGONALFACE((50,85,53));
+#153=IFCINDEXEDPOLYGONALFACE((89,13,14));
+#154=IFCINDEXEDPOLYGONALFACE((88,35,36));
+#155=IFCINDEXEDPOLYGONALFACE((80,36,70));
+#156=IFCINDEXEDPOLYGONALFACE((88,36,80));
+#157=IFCINDEXEDPOLYGONALFACE((16,21,3));
+#158=IFCINDEXEDPOLYGONALFACE((16,3,100));
+#159=IFCINDEXEDPOLYGONALFACE((66,63,61));
+#160=IFCINDEXEDPOLYGONALFACE((101,103,102));
+#161=IFCINDEXEDPOLYGONALFACE((102,63,66));
+#162=IFCINDEXEDPOLYGONALFACE((103,63,102));
+#163=IFCINDEXEDPOLYGONALFACE((30,32,104));
+#164=IFCINDEXEDPOLYGONALFACE((105,97,8));
+#165=IFCINDEXEDPOLYGONALFACE((8,97,91));
+#166=IFCINDEXEDPOLYGONALFACE((61,64,65));
+#167=IFCINDEXEDPOLYGONALFACE((5,4,3));
+#168=IFCINDEXEDPOLYGONALFACE((106,5,3));
+#169=IFCINDEXEDPOLYGONALFACE((3,107,106));
+#170=IFCINDEXEDPOLYGONALFACE((106,107,108));
+#171=IFCINDEXEDPOLYGONALFACE((108,109,106));
+#172=IFCINDEXEDPOLYGONALFACE((83,12,7));
+#173=IFCINDEXEDPOLYGONALFACE((103,101,63));
+#174=IFCINDEXEDPOLYGONALFACE((9,10,8));
+#175=IFCINDEXEDPOLYGONALFACE((69,90,26));
+#176=IFCINDEXEDPOLYGONALFACE((108,104,37));
+#177=IFCINDEXEDPOLYGONALFACE((104,32,37));
+#178=IFCINDEXEDPOLYGONALFACE((100,110,64));
+#179=IFCINDEXEDPOLYGONALFACE((3,110,100));
+#180=IFCINDEXEDPOLYGONALFACE((42,111,41));
+#181=IFCINDEXEDPOLYGONALFACE((37,38,111));
+#182=IFCINDEXEDPOLYGONALFACE((84,69,68));
+#183=IFCINDEXEDPOLYGONALFACE((84,91,69));
+#184=IFCINDEXEDPOLYGONALFACE((93,83,105));
+#185=IFCINDEXEDPOLYGONALFACE((12,105,83));
+#186=IFCINDEXEDPOLYGONALFACE((100,64,16));
+#187=IFCINDEXEDPOLYGONALFACE((66,64,112));
+#188=IFCINDEXEDPOLYGONALFACE((61,65,66));
+#189=IFCINDEXEDPOLYGONALFACE((108,113,42));
+#190=IFCINDEXEDPOLYGONALFACE((49,15,13));
+#191=IFCINDEXEDPOLYGONALFACE((12,97,105));
+#192=IFCINDEXEDPOLYGONALFACE((8,91,84));
+#193=IFCINDEXEDPOLYGONALFACE((34,92,87));
+#194=IFCINDEXEDPOLYGONALFACE((93,11,83));
+#195=IFCINDEXEDPOLYGONALFACE((114,60,63));
+#196=IFCINDEXEDPOLYGONALFACE((114,59,60));
+#197=IFCINDEXEDPOLYGONALFACE((72,47,115));
+#198=IFCINDEXEDPOLYGONALFACE((111,38,41));
+#199=IFCINDEXEDPOLYGONALFACE((63,60,62));
+#200=IFCINDEXEDPOLYGONALFACE((52,53,56));
+#201=IFCINDEXEDPOLYGONALFACE((54,52,56));
+#202=IFCINDEXEDPOLYGONALFACE((48,116,47));
+#203=IFCINDEXEDPOLYGONALFACE((57,59,55));
+#204=IFCINDEXEDPOLYGONALFACE((52,54,51));
+#205=IFCINDEXEDPOLYGONALFACE((116,48,51));
+#206=IFCINDEXEDPOLYGONALFACE((27,107,24));
+#207=IFCINDEXEDPOLYGONALFACE((110,112,64));
+#208=IFCINDEXEDPOLYGONALFACE((110,3,117));
+#209=IFCINDEXEDPOLYGONALFACE((117,112,110));
+#210=IFCINDEXEDPOLYGONALFACE((30,79,32));
+#211=IFCINDEXEDPOLYGONALFACE((117,102,66));
+#212=IFCINDEXEDPOLYGONALFACE((1,102,117));
+#213=IFCINDEXEDPOLYGONALFACE((102,1,101));
+#214=IFCINDEXEDPOLYGONALFACE((114,1,2));
+#215=IFCINDEXEDPOLYGONALFACE((44,45,39));
+#216=IFCINDEXEDPOLYGONALFACE((66,112,117));
+#217=IFCINDEXEDPOLYGONALFACE((55,59,54));
+#218=IFCINDEXEDPOLYGONALFACE((33,37,32));
+#219=IFCINDEXEDPOLYGONALFACE((63,101,114));
+#220=IFCINDEXEDPOLYGONALFACE((101,1,114));
+#221=IFCINDEXEDPOLYGONALFACE((118,77,72));
+#222=IFCINDEXEDPOLYGONALFACE((51,54,116));
+#223=IFCINDEXEDPOLYGONALFACE((27,29,107));
+#224=IFCINDEXEDPOLYGONALFACE((42,113,111));
+#225=IFCINDEXEDPOLYGONALFACE((111,113,37));
+#226=IFCINDEXEDPOLYGONALFACE((17,21,16));
+#227=IFCINDEXEDPOLYGONALFACE((4,59,114));
+#228=IFCINDEXEDPOLYGONALFACE((45,46,77));
+#229=IFCINDEXEDPOLYGONALFACE((45,77,118));
+#230=IFCINDEXEDPOLYGONALFACE((104,29,30));
+#231=IFCINDEXEDPOLYGONALFACE((72,115,118));
+#232=IFCINDEXEDPOLYGONALFACE((107,29,104));
+#233=IFCINDEXEDPOLYGONALFACE((59,4,54));
+#234=IFCINDEXEDPOLYGONALFACE((39,45,42));
+#235=IFCINDEXEDPOLYGONALFACE((115,47,116));
+#236=IFCINDEXEDPOLYGONALFACE((108,37,113));
+#237=IFCINDEXEDPOLYGONALFACE((109,108,42));
+#238=IFCINDEXEDPOLYGONALFACE((107,104,108));
+#239=IFCINDEXEDPOLYGONALFACE((114,2,4));
+#240=IFCINDEXEDPOLYGONALFACE((115,119,45));
+#241=IFCINDEXEDPOLYGONALFACE((42,45,119));
+#242=IFCINDEXEDPOLYGONALFACE((109,42,119));
+#243=IFCINDEXEDPOLYGONALFACE((54,6,116));
+#244=IFCINDEXEDPOLYGONALFACE((23,24,107));
+#245=IFCINDEXEDPOLYGONALFACE((23,107,21));
+#246=IFCINDEXEDPOLYGONALFACE((3,21,107));
+#247=IFCINDEXEDPOLYGONALFACE((119,121,120));
+#248=IFCINDEXEDPOLYGONALFACE((119,115,121));
+#249=IFCINDEXEDPOLYGONALFACE((118,115,45));
+#250=IFCINDEXEDPOLYGONALFACE((6,123,122));
+#251=IFCINDEXEDPOLYGONALFACE((54,4,6));
+#252=IFCINDEXEDPOLYGONALFACE((3,1,117));
+#253=IFCINDEXEDPOLYGONALFACE((124,123,6));
+#254=IFCINDEXEDPOLYGONALFACE((124,120,123));
+#255=IFCINDEXEDPOLYGONALFACE((7,8,84));
+#256=IFCINDEXEDPOLYGONALFACE((82,97,85));
+#257=IFCINDEXEDPOLYGONALFACE((125,127,126));
+#258=IFCINDEXEDPOLYGONALFACE((128,130,129));
+#259=IFCINDEXEDPOLYGONALFACE((128,125,131));
+#260=IFCINDEXEDPOLYGONALFACE((132,6,133));
+#261=IFCINDEXEDPOLYGONALFACE((133,134,132));
+#262=IFCINDEXEDPOLYGONALFACE((135,137,136));
+#263=IFCINDEXEDPOLYGONALFACE((138,137,135));
+#264=IFCINDEXEDPOLYGONALFACE((139,141,140));
+#265=IFCINDEXEDPOLYGONALFACE((134,127,139));
+#266=IFCINDEXEDPOLYGONALFACE((134,139,142));
+#267=IFCINDEXEDPOLYGONALFACE((127,125,143));
+#268=IFCINDEXEDPOLYGONALFACE((143,128,144));
+#269=IFCINDEXEDPOLYGONALFACE((128,145,144));
+#270=IFCINDEXEDPOLYGONALFACE((129,145,128));
+#271=IFCINDEXEDPOLYGONALFACE((139,140,146));
+#272=IFCINDEXEDPOLYGONALFACE((147,137,138));
+#273=IFCINDEXEDPOLYGONALFACE((148,149,137));
+#274=IFCINDEXEDPOLYGONALFACE((147,148,137));
+#275=IFCINDEXEDPOLYGONALFACE((150,149,148));
+#276=IFCINDEXEDPOLYGONALFACE((131,125,126));
+#277=IFCINDEXEDPOLYGONALFACE((133,6,5));
+#278=IFCINDEXEDPOLYGONALFACE((151,133,5));
+#279=IFCINDEXEDPOLYGONALFACE((5,106,151));
+#280=IFCINDEXEDPOLYGONALFACE((126,133,151));
+#281=IFCINDEXEDPOLYGONALFACE((147,130,131));
+#282=IFCINDEXEDPOLYGONALFACE((148,147,131));
+#283=IFCINDEXEDPOLYGONALFACE((152,149,150));
+#284=IFCINDEXEDPOLYGONALFACE((149,152,153));
+#285=IFCINDEXEDPOLYGONALFACE((153,152,154));
+#286=IFCINDEXEDPOLYGONALFACE((155,156,149));
+#287=IFCINDEXEDPOLYGONALFACE((155,149,153));
+#288=IFCINDEXEDPOLYGONALFACE((157,159,158));
+#289=IFCINDEXEDPOLYGONALFACE((159,157,160));
+#290=IFCINDEXEDPOLYGONALFACE((161,159,160));
+#291=IFCINDEXEDPOLYGONALFACE((162,161,155));
+#292=IFCINDEXEDPOLYGONALFACE((161,162,159));
+#293=IFCINDEXEDPOLYGONALFACE((163,164,158));
+#294=IFCINDEXEDPOLYGONALFACE((164,165,158));
+#295=IFCINDEXEDPOLYGONALFACE((166,157,158));
+#296=IFCINDEXEDPOLYGONALFACE((166,158,165));
+#297=IFCINDEXEDPOLYGONALFACE((162,158,159));
+#298=IFCINDEXEDPOLYGONALFACE((167,168,162));
+#299=IFCINDEXEDPOLYGONALFACE((153,154,167));
+#300=IFCINDEXEDPOLYGONALFACE((169,163,154));
+#301=IFCINDEXEDPOLYGONALFACE((154,152,169));
+#302=IFCINDEXEDPOLYGONALFACE((170,119,171));
+#303=IFCINDEXEDPOLYGONALFACE((170,173,172));
+#304=IFCINDEXEDPOLYGONALFACE((174,173,170));
+#305=IFCINDEXEDPOLYGONALFACE((173,175,172));
+#306=IFCINDEXEDPOLYGONALFACE((176,169,175));
+#307=IFCINDEXEDPOLYGONALFACE((163,176,173));
+#308=IFCINDEXEDPOLYGONALFACE((168,163,162));
+#309=IFCINDEXEDPOLYGONALFACE((163,158,162));
+#310=IFCINDEXEDPOLYGONALFACE((167,154,168));
+#311=IFCINDEXEDPOLYGONALFACE((163,168,154));
+#312=IFCINDEXEDPOLYGONALFACE((177,179,178));
+#313=IFCINDEXEDPOLYGONALFACE((177,178,180));
+#314=IFCINDEXEDPOLYGONALFACE((181,177,180));
+#315=IFCINDEXEDPOLYGONALFACE((182,179,177));
+#316=IFCINDEXEDPOLYGONALFACE((181,182,177));
+#317=IFCINDEXEDPOLYGONALFACE((183,182,184));
+#318=IFCINDEXEDPOLYGONALFACE((185,187,186));
+#319=IFCINDEXEDPOLYGONALFACE((185,188,187));
+#320=IFCINDEXEDPOLYGONALFACE((161,160,187));
+#321=IFCINDEXEDPOLYGONALFACE((188,161,187));
+#322=IFCINDEXEDPOLYGONALFACE((189,161,188));
+#323=IFCINDEXEDPOLYGONALFACE((189,155,161));
+#324=IFCINDEXEDPOLYGONALFACE((155,189,190));
+#325=IFCINDEXEDPOLYGONALFACE((156,155,190));
+#326=IFCINDEXEDPOLYGONALFACE((156,136,149));
+#327=IFCINDEXEDPOLYGONALFACE((149,136,137));
+#328=IFCINDEXEDPOLYGONALFACE((191,156,192));
+#329=IFCINDEXEDPOLYGONALFACE((192,194,193));
+#330=IFCINDEXEDPOLYGONALFACE((193,191,192));
+#331=IFCINDEXEDPOLYGONALFACE((160,157,186));
+#332=IFCINDEXEDPOLYGONALFACE((157,166,186));
+#333=IFCINDEXEDPOLYGONALFACE((195,186,166));
+#334=IFCINDEXEDPOLYGONALFACE((196,166,165));
+#335=IFCINDEXEDPOLYGONALFACE((197,198,196));
+#336=IFCINDEXEDPOLYGONALFACE((196,198,166));
+#337=IFCINDEXEDPOLYGONALFACE((199,198,197));
+#338=IFCINDEXEDPOLYGONALFACE((199,197,200));
+#339=IFCINDEXEDPOLYGONALFACE((201,199,200));
+#340=IFCINDEXEDPOLYGONALFACE((199,201,202));
+#341=IFCINDEXEDPOLYGONALFACE((203,148,131));
+#342=IFCINDEXEDPOLYGONALFACE((203,131,106));
+#343=IFCINDEXEDPOLYGONALFACE((203,150,148));
+#344=IFCINDEXEDPOLYGONALFACE((152,150,203));
+#345=IFCINDEXEDPOLYGONALFACE((188,204,192));
+#346=IFCINDEXEDPOLYGONALFACE((194,192,204));
+#347=IFCINDEXEDPOLYGONALFACE((109,119,172));
+#348=IFCINDEXEDPOLYGONALFACE((109,172,169));
+#349=IFCINDEXEDPOLYGONALFACE((152,109,169));
+#350=IFCINDEXEDPOLYGONALFACE((203,109,152));
+#351=IFCINDEXEDPOLYGONALFACE((203,106,109));
+#352=IFCINDEXEDPOLYGONALFACE((163,169,176));
+#353=IFCINDEXEDPOLYGONALFACE((140,141,205));
+#354=IFCINDEXEDPOLYGONALFACE((206,207,146));
+#355=IFCINDEXEDPOLYGONALFACE((207,208,142));
+#356=IFCINDEXEDPOLYGONALFACE((208,207,209));
+#357=IFCINDEXEDPOLYGONALFACE((134,142,208));
+#358=IFCINDEXEDPOLYGONALFACE((208,132,134));
+#359=IFCINDEXEDPOLYGONALFACE((6,132,208));
+#360=IFCINDEXEDPOLYGONALFACE((146,207,139));
+#361=IFCINDEXEDPOLYGONALFACE((139,207,142));
+#362=IFCINDEXEDPOLYGONALFACE((210,205,211));
+#363=IFCINDEXEDPOLYGONALFACE((210,212,205));
+#364=IFCINDEXEDPOLYGONALFACE((213,215,214));
+#365=IFCINDEXEDPOLYGONALFACE((216,217,213));
+#366=IFCINDEXEDPOLYGONALFACE((214,215,218));
+#367=IFCINDEXEDPOLYGONALFACE((215,217,219));
+#368=IFCINDEXEDPOLYGONALFACE((217,215,213));
+#369=IFCINDEXEDPOLYGONALFACE((138,130,147));
+#370=IFCINDEXEDPOLYGONALFACE((124,6,209));
+#371=IFCINDEXEDPOLYGONALFACE((220,222,221));
+#372=IFCINDEXEDPOLYGONALFACE((223,194,224));
+#373=IFCINDEXEDPOLYGONALFACE((193,194,223));
+#374=IFCINDEXEDPOLYGONALFACE((193,223,225));
+#375=IFCINDEXEDPOLYGONALFACE((224,226,223));
+#376=IFCINDEXEDPOLYGONALFACE((224,227,226));
+#377=IFCINDEXEDPOLYGONALFACE((228,229,145));
+#378=IFCINDEXEDPOLYGONALFACE((230,228,231));
+#379=IFCINDEXEDPOLYGONALFACE((231,232,230));
+#380=IFCINDEXEDPOLYGONALFACE((228,230,233));
+#381=IFCINDEXEDPOLYGONALFACE((144,234,143));
+#382=IFCINDEXEDPOLYGONALFACE((234,235,143));
+#383=IFCINDEXEDPOLYGONALFACE((236,219,217));
+#384=IFCINDEXEDPOLYGONALFACE((227,237,226));
+#385=IFCINDEXEDPOLYGONALFACE((226,237,238));
+#386=IFCINDEXEDPOLYGONALFACE((227,199,237));
+#387=IFCINDEXEDPOLYGONALFACE((227,239,199));
+#388=IFCINDEXEDPOLYGONALFACE((239,240,199));
+#389=IFCINDEXEDPOLYGONALFACE((195,166,240));
+#390=IFCINDEXEDPOLYGONALFACE((119,109,241));
+#391=IFCINDEXEDPOLYGONALFACE((160,186,187));
+#392=IFCINDEXEDPOLYGONALFACE((198,199,240));
+#393=IFCINDEXEDPOLYGONALFACE((198,240,166));
+#394=IFCINDEXEDPOLYGONALFACE((242,237,202));
+#395=IFCINDEXEDPOLYGONALFACE((242,238,237));
+#396=IFCINDEXEDPOLYGONALFACE((237,199,202));
+#397=IFCINDEXEDPOLYGONALFACE((200,244,243));
+#398=IFCINDEXEDPOLYGONALFACE((244,183,222));
+#399=IFCINDEXEDPOLYGONALFACE((244,197,183));
+#400=IFCINDEXEDPOLYGONALFACE((200,197,244));
+#401=IFCINDEXEDPOLYGONALFACE((201,200,243));
+#402=IFCINDEXEDPOLYGONALFACE((201,243,232));
+#403=IFCINDEXEDPOLYGONALFACE((245,246,243));
+#404=IFCINDEXEDPOLYGONALFACE((244,245,243));
+#405=IFCINDEXEDPOLYGONALFACE((247,246,245));
+#406=IFCINDEXEDPOLYGONALFACE((246,233,230));
+#407=IFCINDEXEDPOLYGONALFACE((230,232,243));
+#408=IFCINDEXEDPOLYGONALFACE((230,243,246));
+#409=IFCINDEXEDPOLYGONALFACE((245,220,247));
+#410=IFCINDEXEDPOLYGONALFACE((221,247,220));
+#411=IFCINDEXEDPOLYGONALFACE((221,218,247));
+#412=IFCINDEXEDPOLYGONALFACE((218,229,233));
+#413=IFCINDEXEDPOLYGONALFACE((218,233,248));
+#414=IFCINDEXEDPOLYGONALFACE((218,248,247));
+#415=IFCINDEXEDPOLYGONALFACE((221,214,218));
+#416=IFCINDEXEDPOLYGONALFACE((236,218,219));
+#417=IFCINDEXEDPOLYGONALFACE((219,218,215));
+#418=IFCINDEXEDPOLYGONALFACE((234,216,235));
+#419=IFCINDEXEDPOLYGONALFACE((234,217,216));
+#420=IFCINDEXEDPOLYGONALFACE((235,216,211));
+#421=IFCINDEXEDPOLYGONALFACE((205,235,211));
+#422=IFCINDEXEDPOLYGONALFACE((205,141,235));
+#423=IFCINDEXEDPOLYGONALFACE((155,167,162));
+#424=IFCINDEXEDPOLYGONALFACE((155,153,167));
+#425=IFCINDEXEDPOLYGONALFACE((249,242,232));
+#426=IFCINDEXEDPOLYGONALFACE((202,201,242));
+#427=IFCINDEXEDPOLYGONALFACE((247,248,246));
+#428=IFCINDEXEDPOLYGONALFACE((246,248,233));
+#429=IFCINDEXEDPOLYGONALFACE((218,236,229));
+#430=IFCINDEXEDPOLYGONALFACE((143,235,141));
+#431=IFCINDEXEDPOLYGONALFACE((141,127,143));
+#432=IFCINDEXEDPOLYGONALFACE((125,128,143));
+#433=IFCINDEXEDPOLYGONALFACE((139,127,141));
+#434=IFCINDEXEDPOLYGONALFACE((144,236,234));
+#435=IFCINDEXEDPOLYGONALFACE((236,217,234));
+#436=IFCINDEXEDPOLYGONALFACE((228,233,229));
+#437=IFCINDEXEDPOLYGONALFACE((250,129,138));
+#438=IFCINDEXEDPOLYGONALFACE((250,138,251));
+#439=IFCINDEXEDPOLYGONALFACE((252,251,253));
+#440=IFCINDEXEDPOLYGONALFACE((252,250,251));
+#441=IFCINDEXEDPOLYGONALFACE((252,231,250));
+#442=IFCINDEXEDPOLYGONALFACE((249,253,254));
+#443=IFCINDEXEDPOLYGONALFACE((253,249,252));
+#444=IFCINDEXEDPOLYGONALFACE((252,232,231));
+#445=IFCINDEXEDPOLYGONALFACE((129,130,138));
+#446=IFCINDEXEDPOLYGONALFACE((130,128,131));
+#447=IFCINDEXEDPOLYGONALFACE((127,134,126));
+#448=IFCINDEXEDPOLYGONALFACE((134,133,126));
+#449=IFCINDEXEDPOLYGONALFACE((255,135,191));
+#450=IFCINDEXEDPOLYGONALFACE((191,135,136));
+#451=IFCINDEXEDPOLYGONALFACE((255,138,135));
+#452=IFCINDEXEDPOLYGONALFACE((193,255,191));
+#453=IFCINDEXEDPOLYGONALFACE((225,255,193));
+#454=IFCINDEXEDPOLYGONALFACE((255,251,138));
+#455=IFCINDEXEDPOLYGONALFACE((255,253,251));
+#456=IFCINDEXEDPOLYGONALFACE((255,254,253));
+#457=IFCINDEXEDPOLYGONALFACE((225,254,255));
+#458=IFCINDEXEDPOLYGONALFACE((254,238,249));
+#459=IFCINDEXEDPOLYGONALFACE((223,254,225));
+#460=IFCINDEXEDPOLYGONALFACE((254,223,226));
+#461=IFCINDEXEDPOLYGONALFACE((232,242,201));
+#462=IFCINDEXEDPOLYGONALFACE((195,240,239));
+#463=IFCINDEXEDPOLYGONALFACE((195,227,224));
+#464=IFCINDEXEDPOLYGONALFACE((185,195,224));
+#465=IFCINDEXEDPOLYGONALFACE((186,195,185));
+#466=IFCINDEXEDPOLYGONALFACE((224,194,204));
+#467=IFCINDEXEDPOLYGONALFACE((185,224,204));
+#468=IFCINDEXEDPOLYGONALFACE((188,185,204));
+#469=IFCINDEXEDPOLYGONALFACE((242,249,238));
+#470=IFCINDEXEDPOLYGONALFACE((232,252,249));
+#471=IFCINDEXEDPOLYGONALFACE((228,129,231));
+#472=IFCINDEXEDPOLYGONALFACE((145,129,228));
+#473=IFCINDEXEDPOLYGONALFACE((250,231,129));
+#474=IFCINDEXEDPOLYGONALFACE((145,229,144));
+#475=IFCINDEXEDPOLYGONALFACE((229,236,144));
+#476=IFCINDEXEDPOLYGONALFACE((238,254,226));
+#477=IFCINDEXEDPOLYGONALFACE((195,239,227));
+#478=IFCINDEXEDPOLYGONALFACE((156,189,188));
+#479=IFCINDEXEDPOLYGONALFACE((188,192,156));
+#480=IFCINDEXEDPOLYGONALFACE((191,136,156));
+#481=IFCINDEXEDPOLYGONALFACE((190,189,156));
+#482=IFCINDEXEDPOLYGONALFACE((245,244,220));
+#483=IFCINDEXEDPOLYGONALFACE((220,244,222));
+#484=IFCINDEXEDPOLYGONALFACE((106,131,151));
+#485=IFCINDEXEDPOLYGONALFACE((131,126,151));
+#486=IFCINDEXEDPOLYGONALFACE((169,172,175));
+#487=IFCINDEXEDPOLYGONALFACE((170,172,119));
+#488=IFCINDEXEDPOLYGONALFACE((256,257,119));
+#489=IFCINDEXEDPOLYGONALFACE((256,181,180));
+#490=IFCINDEXEDPOLYGONALFACE((164,173,174));
+#491=IFCINDEXEDPOLYGONALFACE((164,174,178));
+#492=IFCINDEXEDPOLYGONALFACE((257,170,171));
+#493=IFCINDEXEDPOLYGONALFACE((257,174,170));
+#494=IFCINDEXEDPOLYGONALFACE((196,165,258));
+#495=IFCINDEXEDPOLYGONALFACE((258,183,196));
+#496=IFCINDEXEDPOLYGONALFACE((173,164,163));
+#497=IFCINDEXEDPOLYGONALFACE((178,165,164));
+#498=IFCINDEXEDPOLYGONALFACE((258,165,178));
+#499=IFCINDEXEDPOLYGONALFACE((258,178,179));
+#500=IFCINDEXEDPOLYGONALFACE((179,183,258));
+#501=IFCINDEXEDPOLYGONALFACE((259,222,183));
+#502=IFCINDEXEDPOLYGONALFACE((184,260,259));
+#503=IFCINDEXEDPOLYGONALFACE((184,210,260));
+#504=IFCINDEXEDPOLYGONALFACE((211,260,210));
+#505=IFCINDEXEDPOLYGONALFACE((211,213,260));
+#506=IFCINDEXEDPOLYGONALFACE((182,183,179));
+#507=IFCINDEXEDPOLYGONALFACE((214,259,213));
+#508=IFCINDEXEDPOLYGONALFACE((221,222,259));
+#509=IFCINDEXEDPOLYGONALFACE((214,221,259));
+#510=IFCINDEXEDPOLYGONALFACE((213,259,260));
+#511=IFCINDEXEDPOLYGONALFACE((212,146,140));
+#512=IFCINDEXEDPOLYGONALFACE((140,205,212));
+#513=IFCINDEXEDPOLYGONALFACE((6,208,209));
+#514=IFCINDEXEDPOLYGONALFACE((256,180,257));
+#515=IFCINDEXEDPOLYGONALFACE((257,171,119));
+#516=IFCINDEXEDPOLYGONALFACE((178,174,257));
+#517=IFCINDEXEDPOLYGONALFACE((178,257,180));
+#518=IFCINDEXEDPOLYGONALFACE((206,256,124));
+#519=IFCINDEXEDPOLYGONALFACE((256,120,124));
+#520=IFCINDEXEDPOLYGONALFACE((256,119,120));
+#521=IFCINDEXEDPOLYGONALFACE((212,182,181));
+#522=IFCINDEXEDPOLYGONALFACE((210,184,212));
+#523=IFCINDEXEDPOLYGONALFACE((212,184,182));
+#524=IFCINDEXEDPOLYGONALFACE((146,212,206));
+#525=IFCINDEXEDPOLYGONALFACE((124,209,206));
+#526=IFCINDEXEDPOLYGONALFACE((206,212,181));
+#527=IFCINDEXEDPOLYGONALFACE((256,206,181));
+#528=IFCINDEXEDPOLYGONALFACE((206,209,207));
+#529=IFCINDEXEDPOLYGONALFACE((184,259,183));
+#530=IFCINDEXEDPOLYGONALFACE((176,175,173));
+#531=IFCINDEXEDPOLYGONALFACE((196,183,197));
+#532=IFCINDEXEDPOLYGONALFACE((213,211,216));
+#533=IFCINDEXEDPOLYGONALFACE((122,121,115));
+#534=IFCINDEXEDPOLYGONALFACE((122,123,121));
+#535=IFCINDEXEDPOLYGONALFACE((123,120,121));
+#536=IFCINDEXEDPOLYGONALFACE((116,122,115));
+#537=IFCINDEXEDPOLYGONALFACE((116,6,122));
+#538=IFCCARTESIANPOINTLIST3D(((250.027374267578,-23.882905960083,1415.95446777344),(189.34294128418,-35.7263298034668,1403.52282714844),(237.454940795898,54.2838821411133,1518.08740234375),(126.709022521973,-52.5286827087402,1465.04675292969),(72.5036392211914,31.4245052337646,1561.49487304688),(28.7249774932861,-44.164794921875,1507.29235839844),(167.752944946289,-41.1005210876465,272.277130126953),(88.4636154174805,133.811492919922,-1.86264543344805E-07),(146.995422363281,-42.8920211791992,64.3589630126953),(146.09033203125,32.7610664367676,-1.86264543344805E-07),(175.912811279297,-101.924354553223,-1.86264543344805E-07),(87.4011001586914,-35.3959732055664,182.559417724609),(-146.337890625,-247.331100463867,84.7031478881836),(-128.706588745117,-105.762428283691,72.7251815795898),(-191.414672851562,-195.42936706543,-1.86264543344805E-07),(198.540817260742,138.32893371582,1088.42199707031),(213.262069702148,208.597732543945,814.737426757812),(223.903457641602,158.067337036133,603.092712402344),(222.216049194336,195.401000976562,419.532836914062),(168.335983276367,241.589233398438,363.422088623047),(141.263061523438,122.122032165527,1471.9970703125),(188.906753540039,155.980361938477,514.717407226562),(81.5025100708008,168.2490234375,1202.76501464844),(41.5524711608887,166.412170410156,1207.63073730469),(7.80561876296997,155.191467285156,982.802978515625),(57.9982490539551,245.809005737305,483.971405029297),(-17.6877479553223,200.386428833008,1060.72265625),(17.9774417877197,176.138122558594,732.623291015625),(-74.0164031982422,149.849548339844,1060.47814941406),(-100.488059997559,211.286590576172,989.019409179688),(-106.23828125,189.579803466797,681.878845214844),(-227.530517578125,142.242568969727,1068.16943359375),(-212.905776977539,63.2130088806152,757.55517578125),(-123.063583374023,122.40258026123,633.907653808594),(-75.0333709716797,155.949584960938,495.433868408203),(-183.794021606445,156.028854370117,501.240814208984),(-284.961975097656,171.7763671875,1249.94677734375),(-320.667572021484,49.364631652832,847.766845703125),(-317.137542724609,-116.618293762207,934.140808105469),(-315.947265625,-64.7200698852539,889.551635742188),(-396.098724365234,-5.17033386230469,1006.86987304688),(-303.506683349609,28.2569217681885,1491.77648925781),(-378.405944824219,-37.4422225952148,647.299133300781),(-291.714935302734,-62.3378295898438,757.3779296875),(-247.474243164062,-14.4445514678955,1490.72448730469),(-223.26545715332,-72.8882598876953,737.782165527344),(-163.737350463867,-117.198844909668,889.643127441406),(-128.974685668945,-195.850173950195,690.143127441406),(-181.846130371094,-171.127288818359,347.562286376953),(-100.279792785645,-137.020523071289,269.947326660156),(-84.9218444824219,-145.87825012207,946.980041503906),(-27.9072284698486,-181.795471191406,794.125915527344),(17.5650749206543,-231.546752929688,258.120178222656),(42.0146293640137,-106.533447265625,1293.30859375),(73.8282623291016,-162.069351196289,865.566528320312),(36.9884071350098,-112.385345458984,658.78515625),(137.766799926758,-117.065483093262,521.771118164062),(93.2620849609375,-193.72575378418,210.056549072266),(131.815292358398,-135.018936157227,1027.45837402344),(171.427124023438,-159.859420776367,491.391387939453),(175.360702514648,79.7155838012695,726.210083007812),(223.453506469727,-38.7604484558105,480.577392578125),(205.460113525391,-77.460334777832,677.622863769531),(286.508911132812,162.409469604492,787.532775878906),(248.989151000977,54.3829689025879,720.736633300781),(236.968444824219,38.6795692443848,842.181823730469),(-14.3888101577759,210.571716308594,546.630065917969),(187.714797973633,101.758567810059,504.419006347656),(87.2769622802734,130.830810546875,393.722137451172),(-226.19856262207,249.275756835938,401.809020996094),(-202.331390380859,-100.888618469238,487.280517578125),(-202.734512329102,-104.948028564453,862.516357421875),(254.341201782227,-75.7364654541016,170.172393798828),(47.9848747253418,-83.7818984985352,240.561096191406),(-187.932357788086,-4.74206352233887,690.693664550781),(-254.408248901367,-181.340118408203,366.982391357422),(-238.162551879883,-141.793212890625,786.39453125),(-234.73176574707,-78.0891036987305,503.983001708984),(-171.837875366211,194.545074462891,921.239379882812),(-137.170669555664,276.274780273438,319.8212890625),(-182.028137207031,-42.0894432067871,201.879730224609),(-127.360870361328,-53.1248779296875,337.412628173828),(99.0055999755859,-74.0925827026367,124.908515930176),(176.656387329102,72.9649810791016,338.603942871094),(1.96485579013824,-78.6706390380859,278.222473144531),(194.152099609375,160.744506835938,259.376800537109),(-197.659713745117,19.7222137451172,393.040374755859),(-99.9229507446289,216.252670288086,307.358856201172),(-80.8954620361328,-260.127990722656,177.856781005859),(-1.46076452732086,154.994689941406,499.547607421875),(20.0321407318115,109.741317749023,341.221801757812),(-161.034683227539,128.413375854492,225.929534912109),(82.0537338256836,-183.277267456055,-1.86264543344805E-07),(-84.9847946166992,-9.26582050323486,96.9040603637695),(-80.3021469116211,-67.6039657592773,-1.86264543344805E-07),(-170.064987182617,46.298656463623,112.053344726562),(-14.7262620925903,74.1896057128906,165.622222900391),(-96.8446731567383,118.10758972168,-1.86264543344805E-07),(-171.509857177734,-116.833053588867,-2.81568168247759E-06),(257.419982910156,134.479064941406,1180.33276367188),(216.522766113281,-95.3502807617188,929.773376464844),(263.888305664062,-61.2300872802734,796.839294433594),(270.641235351562,-120.480857849121,590.52490234375),(-50.033016204834,214.258926391602,1267.05114746094),(77.1798324584961,-30.2338027954102,-1.86264543344805E-07),(27.9293670654297,72.1410369873047,1592.03479003906),(27.5987339019775,155.599700927734,1496.07653808594),(-100.322135925293,159.092620849609,1426.15380859375),(-62.4779853820801,106.087181091309,1569.58715820312),(295.056488037109,66.8833618164062,960.128540039062),(-335.873657226562,88.4998626708984,1153.00036621094),(365.221954345703,32.4079666137695,855.487915039062),(-281.184967041016,130.421447753906,1272.55029296875),(163.060455322266,-56.9987716674805,1030.525390625),(-87.3238830566406,-71.4930953979492,1434.99816894531),(-84.5702743530273,-135.309982299805,1187.45886230469),(287.197998046875,49.4243621826172,1330.24853515625),(-184.120391845703,-50.421516418457,1373.43566894531),(-142.448364257812,35.334041595459,1565.02941894531),(-99.3179550170898,-19.288537979126,1523.37231445312),(-93.320915222168,-45.390811920166,1479.18518066406),(-29.2994594573975,-57.8289451599121,1471.14526367188),(-35.2964897155762,-31.7266635894775,1515.33227539062),(-65.5984573364258,-60.2618713378906,1526.04479980469),(58.8699798583984,-22.6804714202881,1643.52099609375),(58.794059753418,-3.24364733695984,1596.69812011719),(39.4151382446289,-44.7899703979492,1589.05676269531),(56.7482948303223,-4.49460697174072,1678.35876464844),(65.5708694458008,7.61722707748413,1683.13317871094),(47.4631729125977,22.193675994873,1655.82824707031),(56.3341026306152,13.9416971206665,1623.55346679688),(48.0009269714355,-53.2649879455566,1538.89514160156),(75.7735595703125,-2.46892857551575,1575.37060546875),(46.9823722839355,-52.3717002868652,1573.06872558594),(66.5905303955078,61.1993713378906,1696.62231445312),(46.397575378418,86.9236450195312,1689.51574707031),(35.3512573242188,71.18359375,1659.33557128906),(53.5354232788086,43.4169006347656,1686.71154785156),(21.1407566070557,-54.3571548461914,1584.15661621094),(-21.7695751190186,-61.950065612793,1578.35888671875),(21.5765476226807,-47.9868316650391,1602.47143554688),(34.5973091125488,-66.6909027099609,1576.93969726562),(29.2881603240967,-30.0027637481689,1650.26586914062),(34.3188362121582,-13.5777320861816,1671.78601074219),(35.2478332519531,-37.7936706542969,1709.56225585938),(-13.5368356704712,-62.4897956848145,1568.09484863281),(29.7789516448975,46.6104774475098,1671.64904785156),(1.82311594486237,71.6545486450195,1641.24072265625),(17.1210784912109,106.867897033691,1675.75793457031),(-16.8500270843506,97.2289428710938,1641.71923828125),(66.2260589599609,37.6655502319336,1596.92211914062),(-8.87947082519531,112.180603027344,1624.4013671875),(15.606294631958,121.90754699707,1684.44287109375),(-14.4493827819824,139.713424682617,1654.13891601562),(-3.74729537963867,105.154037475586,1724.6572265625),(36.853157043457,56.968692779541,1733.27893066406),(-107.787452697754,50.2590713500977,1734.28967285156),(-111.847526550293,72.4723052978516,1707.8095703125),(-89.0276412963867,95.1375961303711,1709.05334472656),(-73.5994644165039,79.1380844116211,1727.65319824219),(-46.1447639465332,84.1150283813477,1724.58190917969),(-56.0835151672363,113.140914916992,1691.41149902344),(-64.6567001342773,101.772010803223,1659.74243164062),(-113.400001525879,76.9580764770508,1654.50439453125),(-140.313064575195,57.271053314209,1717.62268066406),(-130.794647216797,25.171085357666,1779.181640625),(-11.6539697647095,126.992546081543,1690.99108886719),(-26.2866077423096,113.777976989746,1671.01440429688),(-58.3992042541504,128.119964599609,1603.22546386719),(-116.66121673584,72.7393646240234,1621.02319335938),(-124.835304260254,54.6162528991699,1598.45190429688),(-89.5713653564453,99.8517456054688,1586.52966308594),(-99.4943008422852,101.570877075195,1638.84228515625),(-126.053352355957,60.064826965332,1631.58569335938),(-83.4318313598633,113.620147705078,1606.25549316406),(-57.5030403137207,112.11254119873,1631.6826171875),(-109.388824462891,-7.19644451141357,1626.74584960938),(-114.661773681641,58.6825637817383,1641.99792480469),(-121.194046020508,16.4241180419922,1666.09533691406),(-118.340675354004,15.6668634414673,1623.85632324219),(-100.413032531738,-27.8646450042725,1610.89392089844),(-119.065963745117,-40.2054672241211,1641.12915039062),(-127.211250305176,-19.4964771270752,1713.14807128906),(-99.4045257568359,-70.9100494384766,1629.31860351562),(-41.655460357666,50.295581817627,1825.77868652344),(-101.226608276367,52.7529525756836,1786.65112304688),(-55.055736541748,73.1881866455078,1792.86657714844),(-5.87042760848999,80.1052017211914,1779.80688476562),(-9.98227596282959,80.9901962280273,1751.09362792969),(24.2499217987061,80.5161209106445,1732.94213867188),(72.5959777832031,53.4743347167969,1747.63439941406),(36.0746688842773,41.2442512512207,1763.26123046875),(50.6687545776367,26.4588584899902,1781.81750488281),(31.1245708465576,6.53590202331543,1796.54870605469),(-82.0447769165039,26.7043991088867,1822.99499511719),(-156.420776367188,-0.598646640777588,1722.66259765625),(-126.260612487793,-59.5931396484375,1747.97924804688),(-146.866394042969,-25.8437519073486,1763.64904785156),(-116.805503845215,-66.2452087402344,1803.30102539062),(-109.480865478516,-92.2581024169922,1763.716796875),(-68.0312042236328,-122.729965209961,1794.67236328125),(-84.1829452514648,-88.0408477783203,1805.11474609375),(23.2931175231934,93.5863723754883,1618.4228515625),(11.1880254745483,34.1745147705078,1817.01477050781),(-29.5253868103027,-78.2835006713867,1599.21813964844),(-84.3417434692383,-63.7740097045898,1571.93908691406),(-13.3443574905396,-80.5143814086914,1554.70178222656),(17.0866050720215,-70.6183166503906,1538.67492675781),(-23.4297542572021,-75.5428924560547,1530.46716308594),(-68.2336807250977,-87.9513778686523,1605.31945800781),(-20.097225189209,-99.0588150024414,1612.77429199219),(-73.4214859008789,-59.3197593688965,1591.12976074219),(-52.7204971313477,-113.585334777832,1644.90148925781),(-86.80126953125,-101.023811340332,1691.12878417969),(-43.94482421875,-130.208251953125,1692.28747558594),(-17.4796237945557,-100.564514160156,1638.8994140625),(-13.3766117095947,-113.837280273438,1658.80065917969),(-42.8742446899414,-114.699562072754,1708.47570800781),(-14.4483852386475,-119.351066589355,1695.07263183594),(-98.4373474121094,-75.3005523681641,1733.36560058594),(-96.7265319824219,-77.0361938476562,1700.3935546875),(-108.931015014648,-35.1993446350098,1718.70092773438),(22.9082126617432,-19.9589500427246,1838.35314941406),(-5.36053991317749,-11.386736869812,1835.03649902344),(52.3087768554688,-9.16755485534668,1807.44372558594),(-11.7170696258545,-48.2666549682617,1845.03369140625),(-54.2986450195312,-63.0719985961914,1842.57153320312),(31.1523685455322,-54.402099609375,1728.83129882812),(15.5857229232788,-70.3590698242188,1706.11267089844),(5.26045608520508,-92.7595977783203,1758.42651367188),(50.0484619140625,-57.3395805358887,1733.59814453125),(9.64294719696045,-107.044288635254,1782.62023925781),(19.1671657562256,-71.3778381347656,1730.2509765625),(14.7019052505493,-71.9714126586914,1648.45837402344),(13.7142124176025,-60.3307304382324,1634.32580566406),(23.7327308654785,-70.3778839111328,1694.55334472656),(-43.2096328735352,-93.7546005249023,1834.45617675781),(-11.7863674163818,-83.3447113037109,1823.212890625),(-87.4517669677734,-44.174259185791,1828.69201660156),(-115.331466674805,-24.3140258789062,1822.38879394531),(-43.3605690002441,96.572639465332,1567.35949707031),(-24.9877071380615,-91.3389358520508,1814.35375976562),(-47.7474403381348,-107.26180267334,1781.99169921875),(-106.339996337891,-82.1798400878906,1755.80249023438),(-89.2768936157227,-97.65234375,1750.74169921875),(-33.6130409240723,-113.925422668457,1759.22412109375),(-41.1620254516602,-102.617835998535,1736.90686035156),(-19.0609798431396,-86.9065551757812,1738.84204101562),(12.0251731872559,-70.5674285888672,1797.03173828125),(61.6594314575195,-32.8811149597168,1730.0009765625),(60.6472129821777,16.3021373748779,1722.08154296875),(43.3303642272949,-63.0390586853027,1766.38647460938),(43.6823692321777,-28.4500370025635,1760.86938476562),(46.8201713562012,-36.1587028503418,1789.75598144531),(69.1895446777344,25.5214042663574,1733.66735839844),(-116.517105102539,-32.7507019042969,1585.50732421875),(-131.696182250977,26.6582431793213,1614.7607421875),(-145.591796875,17.3771591186523,1684.21557617188),(-101.790901184082,-53.6442909240723,1662.22338867188),(-68.0224609375,-93.8657455444336,1625.93310546875)));
+#539=IFCPOLYGONALFACESET(#538,.F.,(#20,#21,#22,#23,#24,#25,#26,#27,#28,#29,#30,#31,#32,#33,#34,#35,#36,#37,#38,#39,#40,#41,#42,#43,#44,#45,#46,#47,#48,#49,#50,#51,#52,#53,#54,#55,#56,#57,#58,#59,#60,#61,#62,#63,#64,#65,#66,#67,#68,#69,#70,#71,#72,#73,#74,#75,#76,#77,#78,#79,#80,#81,#82,#83,#84,#85,#86,#87,#88,#89,#90,#91,#92,#93,#94,#95,#96,#97,#98,#99,#100,#101,#102,#103,#104,#105,#106,#107,#108,#109,#110,#111,#112,#113,#114,#115,#116,#117,#118,#119,#120,#121,#122,#123,#124,#125,#126,#127,#128,#129,#130,#131,#132,#133,#134,#135,#136,#137,#138,#139,#140,#141,#142,#143,#144,#145,#146,#147,#148,#149,#150,#151,#152,#153,#154,#155,#156,#157,#158,#159,#160,#161,#162,#163,#164,#165,#166,#167,#168,#169,#170,#171,#172,#173,#174,#175,#176,#177,#178,#179,#180,#181,#182,#183,#184,#185,#186,#187,#188,#189,#190,#191,#192,#193,#194,#195,#196,#197,#198,#199,#200,#201,#202,#203,#204,#205,#206,#207,#208,#209,#210,#211,#212,#213,#214,#215,#216,#217,#218,#219,#220,#221,#222,#223,#224,#225,#226,#227,#228,#229,#230,#231,#232,#233,#234,#235,#236,#237,#238,#239,#240,#241,#242,#243,#244,#245,#246,#247,#248,#249,#250,#251,#252,#253,#254,#255,#256,#257,#258,#259,#260,#261,#262,#263,#264,#265,#266,#267,#268,#269,#270,#271,#272,#273,#274,#275,#276,#277,#278,#279,#280,#281,#282,#283,#284,#285,#286,#287,#288,#289,#290,#291,#292,#293,#294,#295,#296,#297,#298,#299,#300,#301,#302,#303,#304,#305,#306,#307,#308,#309,#310,#311,#312,#313,#314,#315,#316,#317,#318,#319,#320,#321,#322,#323,#324,#325,#326,#327,#328,#329,#330,#331,#332,#333,#334,#335,#336,#337,#338,#339,#340,#341,#342,#343,#344,#345,#346,#347,#348,#349,#350,#351,#352,#353,#354,#355,#356,#357,#358,#359,#360,#361,#362,#363,#364,#365,#366,#367,#368,#369,#370,#371,#372,#373,#374,#375,#376,#377,#378,#379,#380,#381,#382,#383,#384,#385,#386,#387,#388,#389,#390,#391,#392,#393,#394,#395,#396,#397,#398,#399,#400,#401,#402,#403,#404,#405,#406,#407,#408,#409,#410,#411,#412,#413,#414,#415,#416,#417,#418,#419,#420,#421,#422,#423,#424,#425,#426,#427,#428,#429,#430,#431,#432,#433,#434,#435,#436,#437,#438,#439,#440,#441,#442,#443,#444,#445,#446,#447,#448,#449,#450,#451,#452,#453,#454,#455,#456,#457,#458,#459,#460,#461,#462,#463,#464,#465,#466,#467,#468,#469,#470,#471,#472,#473,#474,#475,#476,#477,#478,#479,#480,#481,#482,#483,#484,#485,#486,#487,#488,#489,#490,#491,#492,#493,#494,#495,#496,#497,#498,#499,#500,#501,#502,#503,#504,#505,#506,#507,#508,#509,#510,#511,#512,#513,#514,#515,#516,#517,#518,#519,#520,#521,#522,#523,#524,#525,#526,#527,#528,#529,#530,#531,#532,#533,#534,#535,#536,#537),$);
+#540=IFCSHAPEREPRESENTATION(#15,'Body','Tessellation',(#539));
+#541=IFCCARTESIANPOINT((0.,0.,0.));
+#542=IFCDIRECTION((1.,0.,0.));
+#543=IFCDIRECTION((0.,0.,1.));
+#544=IFCAXIS2PLACEMENT3D(#541,#543,#542);
+#545=IFCREPRESENTATIONMAP(#544,#540);
+#546=IFCRELDECLARES('1LAs99F5HFPxJ8dv8mbtsX',$,$,$,#2,(#547,#19));
+#547=IFCBUILDINGELEMENTPROXYTYPE('04rXMTw3j5M8LqX77nU2uH',$,'Woman Dancing',$,$,$,(#1644),$,'ENTOURAGE',.USERDEFINED.);
+#548=IFCINDEXEDPOLYGONALFACE((2,3,1));
+#549=IFCINDEXEDPOLYGONALFACE((4,1,3));
+#550=IFCINDEXEDPOLYGONALFACE((5,4,3));
+#551=IFCINDEXEDPOLYGONALFACE((1,7,6));
+#552=IFCINDEXEDPOLYGONALFACE((7,9,8));
+#553=IFCINDEXEDPOLYGONALFACE((7,10,9));
+#554=IFCINDEXEDPOLYGONALFACE((10,11,9));
+#555=IFCINDEXEDPOLYGONALFACE((9,12,8));
+#556=IFCINDEXEDPOLYGONALFACE((9,11,12));
+#557=IFCINDEXEDPOLYGONALFACE((13,15,14));
+#558=IFCINDEXEDPOLYGONALFACE((16,18,17));
+#559=IFCINDEXEDPOLYGONALFACE((18,16,19));
+#560=IFCINDEXEDPOLYGONALFACE((20,6,17));
+#561=IFCINDEXEDPOLYGONALFACE((17,6,16));
+#562=IFCINDEXEDPOLYGONALFACE((16,21,19));
+#563=IFCINDEXEDPOLYGONALFACE((7,8,6));
+#564=IFCINDEXEDPOLYGONALFACE((12,22,8));
+#565=IFCINDEXEDPOLYGONALFACE((12,23,22));
+#566=IFCINDEXEDPOLYGONALFACE((24,26,25));
+#567=IFCINDEXEDPOLYGONALFACE((28,29,27));
+#568=IFCINDEXEDPOLYGONALFACE((29,28,24));
+#569=IFCINDEXEDPOLYGONALFACE((30,31,27));
+#570=IFCINDEXEDPOLYGONALFACE((27,31,32));
+#571=IFCINDEXEDPOLYGONALFACE((28,34,33));
+#572=IFCINDEXEDPOLYGONALFACE((33,24,28));
+#573=IFCINDEXEDPOLYGONALFACE((13,34,35));
+#574=IFCINDEXEDPOLYGONALFACE((34,13,33));
+#575=IFCINDEXEDPOLYGONALFACE((26,33,13));
+#576=IFCINDEXEDPOLYGONALFACE((30,36,31));
+#577=IFCINDEXEDPOLYGONALFACE((28,27,32));
+#578=IFCINDEXEDPOLYGONALFACE((37,38,13));
+#579=IFCINDEXEDPOLYGONALFACE((23,39,22));
+#580=IFCINDEXEDPOLYGONALFACE((41,42,40));
+#581=IFCINDEXEDPOLYGONALFACE((44,45,43));
+#582=IFCINDEXEDPOLYGONALFACE((47,48,46));
+#583=IFCINDEXEDPOLYGONALFACE((49,39,23));
+#584=IFCINDEXEDPOLYGONALFACE((50,23,12));
+#585=IFCINDEXEDPOLYGONALFACE((52,53,51));
+#586=IFCINDEXEDPOLYGONALFACE((55,56,54));
+#587=IFCINDEXEDPOLYGONALFACE((57,59,58));
+#588=IFCINDEXEDPOLYGONALFACE((60,62,61));
+#589=IFCINDEXEDPOLYGONALFACE((63,65,64));
+#590=IFCINDEXEDPOLYGONALFACE((66,68,67));
+#591=IFCINDEXEDPOLYGONALFACE((67,68,63));
+#592=IFCINDEXEDPOLYGONALFACE((63,68,65));
+#593=IFCINDEXEDPOLYGONALFACE((68,69,65));
+#594=IFCINDEXEDPOLYGONALFACE((71,72,70));
+#595=IFCINDEXEDPOLYGONALFACE((71,73,72));
+#596=IFCINDEXEDPOLYGONALFACE((74,76,75));
+#597=IFCINDEXEDPOLYGONALFACE((71,77,73));
+#598=IFCINDEXEDPOLYGONALFACE((77,74,73));
+#599=IFCINDEXEDPOLYGONALFACE((77,78,74));
+#600=IFCINDEXEDPOLYGONALFACE((80,81,79));
+#601=IFCINDEXEDPOLYGONALFACE((83,84,82));
+#602=IFCINDEXEDPOLYGONALFACE((85,86,82));
+#603=IFCINDEXEDPOLYGONALFACE((84,88,87));
+#604=IFCINDEXEDPOLYGONALFACE((58,89,57));
+#605=IFCINDEXEDPOLYGONALFACE((90,58,91));
+#606=IFCINDEXEDPOLYGONALFACE((92,91,58));
+#607=IFCINDEXEDPOLYGONALFACE((92,93,91));
+#608=IFCINDEXEDPOLYGONALFACE((92,58,94));
+#609=IFCINDEXEDPOLYGONALFACE((94,58,59));
+#610=IFCINDEXEDPOLYGONALFACE((62,60,95));
+#611=IFCINDEXEDPOLYGONALFACE((97,98,96));
+#612=IFCINDEXEDPOLYGONALFACE((94,93,92));
+#613=IFCINDEXEDPOLYGONALFACE((99,93,94));
+#614=IFCINDEXEDPOLYGONALFACE((94,100,99));
+#615=IFCINDEXEDPOLYGONALFACE((94,59,101));
+#616=IFCINDEXEDPOLYGONALFACE((101,100,94));
+#617=IFCINDEXEDPOLYGONALFACE((102,104,103));
+#618=IFCINDEXEDPOLYGONALFACE((86,105,88));
+#619=IFCINDEXEDPOLYGONALFACE((83,86,88));
+#620=IFCINDEXEDPOLYGONALFACE((82,80,75));
+#621=IFCINDEXEDPOLYGONALFACE((75,80,72));
+#622=IFCINDEXEDPOLYGONALFACE((73,75,72));
+#623=IFCINDEXEDPOLYGONALFACE((82,75,85));
+#624=IFCINDEXEDPOLYGONALFACE((75,76,85));
+#625=IFCINDEXEDPOLYGONALFACE((106,108,107));
+#626=IFCINDEXEDPOLYGONALFACE((88,84,83));
+#627=IFCINDEXEDPOLYGONALFACE((109,111,110));
+#628=IFCINDEXEDPOLYGONALFACE((113,114,112));
+#629=IFCINDEXEDPOLYGONALFACE((114,116,115));
+#630=IFCINDEXEDPOLYGONALFACE((117,111,109));
+#631=IFCINDEXEDPOLYGONALFACE((101,118,100));
+#632=IFCINDEXEDPOLYGONALFACE((118,101,119));
+#633=IFCINDEXEDPOLYGONALFACE((120,122,121));
+#634=IFCINDEXEDPOLYGONALFACE((122,123,121));
+#635=IFCINDEXEDPOLYGONALFACE((122,124,123));
+#636=IFCINDEXEDPOLYGONALFACE((109,125,117));
+#637=IFCINDEXEDPOLYGONALFACE((127,125,126));
+#638=IFCINDEXEDPOLYGONALFACE((128,127,129));
+#639=IFCINDEXEDPOLYGONALFACE((130,128,129));
+#640=IFCINDEXEDPOLYGONALFACE((128,130,57));
+#641=IFCINDEXEDPOLYGONALFACE((128,131,127));
+#642=IFCINDEXEDPOLYGONALFACE((57,131,128));
+#643=IFCINDEXEDPOLYGONALFACE((88,66,60));
+#644=IFCINDEXEDPOLYGONALFACE((88,68,66));
+#645=IFCINDEXEDPOLYGONALFACE((105,69,88));
+#646=IFCINDEXEDPOLYGONALFACE((64,69,132));
+#647=IFCINDEXEDPOLYGONALFACE((133,105,134));
+#648=IFCINDEXEDPOLYGONALFACE((133,132,69));
+#649=IFCINDEXEDPOLYGONALFACE((131,57,89));
+#650=IFCINDEXEDPOLYGONALFACE((127,136,135));
+#651=IFCINDEXEDPOLYGONALFACE((127,135,125));
+#652=IFCINDEXEDPOLYGONALFACE((64,65,69));
+#653=IFCINDEXEDPOLYGONALFACE((100,137,99));
+#654=IFCINDEXEDPOLYGONALFACE((137,100,138));
+#655=IFCINDEXEDPOLYGONALFACE((139,140,138));
+#656=IFCINDEXEDPOLYGONALFACE((82,86,83));
+#657=IFCINDEXEDPOLYGONALFACE((109,126,125));
+#658=IFCINDEXEDPOLYGONALFACE((141,91,93));
+#659=IFCINDEXEDPOLYGONALFACE((142,143,141));
+#660=IFCINDEXEDPOLYGONALFACE((132,138,100));
+#661=IFCINDEXEDPOLYGONALFACE((100,118,64));
+#662=IFCINDEXEDPOLYGONALFACE((64,132,100));
+#663=IFCINDEXEDPOLYGONALFACE((69,68,88));
+#664=IFCINDEXEDPOLYGONALFACE((144,116,145));
+#665=IFCINDEXEDPOLYGONALFACE((146,144,145));
+#666=IFCINDEXEDPOLYGONALFACE((146,145,147));
+#667=IFCINDEXEDPOLYGONALFACE((74,78,148));
+#668=IFCINDEXEDPOLYGONALFACE((149,74,148));
+#669=IFCINDEXEDPOLYGONALFACE((76,74,149));
+#670=IFCINDEXEDPOLYGONALFACE((150,76,149));
+#671=IFCINDEXEDPOLYGONALFACE((85,76,150));
+#672=IFCINDEXEDPOLYGONALFACE((151,85,150));
+#673=IFCINDEXEDPOLYGONALFACE((86,85,151));
+#674=IFCINDEXEDPOLYGONALFACE((149,153,152));
+#675=IFCINDEXEDPOLYGONALFACE((149,152,150));
+#676=IFCINDEXEDPOLYGONALFACE((153,154,152));
+#677=IFCINDEXEDPOLYGONALFACE((154,155,152));
+#678=IFCINDEXEDPOLYGONALFACE((136,157,156));
+#679=IFCINDEXEDPOLYGONALFACE((148,153,149));
+#680=IFCINDEXEDPOLYGONALFACE((148,158,153));
+#681=IFCINDEXEDPOLYGONALFACE((159,153,158));
+#682=IFCINDEXEDPOLYGONALFACE((73,74,75));
+#683=IFCINDEXEDPOLYGONALFACE((136,156,135));
+#684=IFCINDEXEDPOLYGONALFACE((160,127,131));
+#685=IFCINDEXEDPOLYGONALFACE((129,127,126));
+#686=IFCINDEXEDPOLYGONALFACE((90,161,89));
+#687=IFCINDEXEDPOLYGONALFACE((161,131,89));
+#688=IFCINDEXEDPOLYGONALFACE((154,153,162));
+#689=IFCINDEXEDPOLYGONALFACE((154,162,163));
+#690=IFCINDEXEDPOLYGONALFACE((164,155,154));
+#691=IFCINDEXEDPOLYGONALFACE((165,159,166));
+#692=IFCINDEXEDPOLYGONALFACE((162,159,165));
+#693=IFCINDEXEDPOLYGONALFACE((165,163,162));
+#694=IFCINDEXEDPOLYGONALFACE((167,169,168));
+#695=IFCINDEXEDPOLYGONALFACE((167,170,169));
+#696=IFCINDEXEDPOLYGONALFACE((133,69,105));
+#697=IFCINDEXEDPOLYGONALFACE((159,162,153));
+#698=IFCINDEXEDPOLYGONALFACE((137,140,99));
+#699=IFCINDEXEDPOLYGONALFACE((171,157,136));
+#700=IFCINDEXEDPOLYGONALFACE((172,144,146));
+#701=IFCINDEXEDPOLYGONALFACE((166,173,165));
+#702=IFCINDEXEDPOLYGONALFACE((173,163,165));
+#703=IFCINDEXEDPOLYGONALFACE((174,133,175));
+#704=IFCINDEXEDPOLYGONALFACE((116,172,115));
+#705=IFCINDEXEDPOLYGONALFACE((116,144,172));
+#706=IFCINDEXEDPOLYGONALFACE((177,178,176));
+#707=IFCINDEXEDPOLYGONALFACE((179,173,177));
+#708=IFCINDEXEDPOLYGONALFACE((180,163,179));
+#709=IFCINDEXEDPOLYGONALFACE((181,163,180));
+#710=IFCINDEXEDPOLYGONALFACE((164,154,163));
+#711=IFCINDEXEDPOLYGONALFACE((181,182,163));
+#712=IFCINDEXEDPOLYGONALFACE((183,184,134));
+#713=IFCINDEXEDPOLYGONALFACE((138,132,174));
+#714=IFCINDEXEDPOLYGONALFACE((132,133,174));
+#715=IFCINDEXEDPOLYGONALFACE((138,174,175));
+#716=IFCINDEXEDPOLYGONALFACE((163,173,179));
+#717=IFCINDEXEDPOLYGONALFACE((177,185,180));
+#718=IFCINDEXEDPOLYGONALFACE((181,186,182));
+#719=IFCINDEXEDPOLYGONALFACE((187,152,155));
+#720=IFCINDEXEDPOLYGONALFACE((188,187,189));
+#721=IFCINDEXEDPOLYGONALFACE((189,187,190));
+#722=IFCINDEXEDPOLYGONALFACE((191,188,189));
+#723=IFCINDEXEDPOLYGONALFACE((192,188,191));
+#724=IFCINDEXEDPOLYGONALFACE((138,140,137));
+#725=IFCINDEXEDPOLYGONALFACE((193,178,194));
+#726=IFCINDEXEDPOLYGONALFACE((175,139,138));
+#727=IFCINDEXEDPOLYGONALFACE((187,155,190));
+#728=IFCINDEXEDPOLYGONALFACE((175,133,184));
+#729=IFCINDEXEDPOLYGONALFACE((196,197,195));
+#730=IFCINDEXEDPOLYGONALFACE((196,199,198));
+#731=IFCINDEXEDPOLYGONALFACE((196,200,199));
+#732=IFCINDEXEDPOLYGONALFACE((196,195,200));
+#733=IFCINDEXEDPOLYGONALFACE((195,197,201));
+#734=IFCINDEXEDPOLYGONALFACE((200,195,202));
+#735=IFCINDEXEDPOLYGONALFACE((203,205,204));
+#736=IFCINDEXEDPOLYGONALFACE((205,202,204));
+#737=IFCINDEXEDPOLYGONALFACE((206,205,203));
+#738=IFCINDEXEDPOLYGONALFACE((205,206,207));
+#739=IFCINDEXEDPOLYGONALFACE((208,210,209));
+#740=IFCINDEXEDPOLYGONALFACE((211,210,208));
+#741=IFCINDEXEDPOLYGONALFACE((209,210,212));
+#742=IFCINDEXEDPOLYGONALFACE((170,214,213));
+#743=IFCINDEXEDPOLYGONALFACE((215,216,211));
+#744=IFCINDEXEDPOLYGONALFACE((217,219,218));
+#745=IFCINDEXEDPOLYGONALFACE((220,219,217));
+#746=IFCINDEXEDPOLYGONALFACE((219,221,218));
+#747=IFCINDEXEDPOLYGONALFACE((221,219,222));
+#748=IFCINDEXEDPOLYGONALFACE((216,221,223));
+#749=IFCINDEXEDPOLYGONALFACE((221,222,223));
+#750=IFCINDEXEDPOLYGONALFACE((216,223,224));
+#751=IFCINDEXEDPOLYGONALFACE((211,216,224));
+#752=IFCINDEXEDPOLYGONALFACE((195,225,202));
+#753=IFCINDEXEDPOLYGONALFACE((142,226,143));
+#754=IFCINDEXEDPOLYGONALFACE((226,142,227));
+#755=IFCINDEXEDPOLYGONALFACE((225,228,227));
+#756=IFCINDEXEDPOLYGONALFACE((181,221,186));
+#757=IFCINDEXEDPOLYGONALFACE((216,215,186));
+#758=IFCINDEXEDPOLYGONALFACE((208,215,211));
+#759=IFCINDEXEDPOLYGONALFACE((201,197,229));
+#760=IFCINDEXEDPOLYGONALFACE((227,228,201));
+#761=IFCINDEXEDPOLYGONALFACE((195,201,228));
+#762=IFCINDEXEDPOLYGONALFACE((230,227,229));
+#763=IFCINDEXEDPOLYGONALFACE((229,227,201));
+#764=IFCINDEXEDPOLYGONALFACE((224,223,231));
+#765=IFCINDEXEDPOLYGONALFACE((213,222,219));
+#766=IFCINDEXEDPOLYGONALFACE((232,200,207));
+#767=IFCINDEXEDPOLYGONALFACE((200,205,207));
+#768=IFCINDEXEDPOLYGONALFACE((205,200,202));
+#769=IFCINDEXEDPOLYGONALFACE((232,199,200));
+#770=IFCINDEXEDPOLYGONALFACE((196,198,233));
+#771=IFCINDEXEDPOLYGONALFACE((198,199,234));
+#772=IFCINDEXEDPOLYGONALFACE((225,204,202));
+#773=IFCINDEXEDPOLYGONALFACE((227,204,225));
+#774=IFCINDEXEDPOLYGONALFACE((230,197,235));
+#775=IFCINDEXEDPOLYGONALFACE((236,235,237));
+#776=IFCINDEXEDPOLYGONALFACE((235,239,238));
+#777=IFCINDEXEDPOLYGONALFACE((197,196,239));
+#778=IFCINDEXEDPOLYGONALFACE((236,230,235));
+#779=IFCINDEXEDPOLYGONALFACE((230,229,197));
+#780=IFCINDEXEDPOLYGONALFACE((211,224,231));
+#781=IFCINDEXEDPOLYGONALFACE((223,214,231));
+#782=IFCINDEXEDPOLYGONALFACE((214,211,231));
+#783=IFCINDEXEDPOLYGONALFACE((221,216,186));
+#784=IFCINDEXEDPOLYGONALFACE((186,215,240));
+#785=IFCINDEXEDPOLYGONALFACE((215,241,240));
+#786=IFCINDEXEDPOLYGONALFACE((209,212,242));
+#787=IFCINDEXEDPOLYGONALFACE((242,241,209));
+#788=IFCINDEXEDPOLYGONALFACE((209,241,208));
+#789=IFCINDEXEDPOLYGONALFACE((208,241,215));
+#790=IFCINDEXEDPOLYGONALFACE((196,233,243));
+#791=IFCINDEXEDPOLYGONALFACE((239,196,243));
+#792=IFCINDEXEDPOLYGONALFACE((243,238,239));
+#793=IFCINDEXEDPOLYGONALFACE((238,243,244));
+#794=IFCINDEXEDPOLYGONALFACE((195,228,225));
+#795=IFCINDEXEDPOLYGONALFACE((245,247,246));
+#796=IFCINDEXEDPOLYGONALFACE((248,247,245));
+#797=IFCINDEXEDPOLYGONALFACE((199,232,234));
+#798=IFCINDEXEDPOLYGONALFACE((197,239,235));
+#799=IFCINDEXEDPOLYGONALFACE((230,249,226));
+#800=IFCINDEXEDPOLYGONALFACE((230,236,249));
+#801=IFCINDEXEDPOLYGONALFACE((250,187,188));
+#802=IFCINDEXEDPOLYGONALFACE((152,187,151));
+#803=IFCINDEXEDPOLYGONALFACE((190,247,189));
+#804=IFCINDEXEDPOLYGONALFACE((188,192,250));
+#805=IFCINDEXEDPOLYGONALFACE((86,251,105));
+#806=IFCINDEXEDPOLYGONALFACE((250,251,86));
+#807=IFCINDEXEDPOLYGONALFACE((150,152,151));
+#808=IFCINDEXEDPOLYGONALFACE((151,250,86));
+#809=IFCINDEXEDPOLYGONALFACE((183,134,105));
+#810=IFCINDEXEDPOLYGONALFACE((251,183,105));
+#811=IFCINDEXEDPOLYGONALFACE((250,192,251));
+#812=IFCINDEXEDPOLYGONALFACE((187,250,151));
+#813=IFCINDEXEDPOLYGONALFACE((190,155,247));
+#814=IFCINDEXEDPOLYGONALFACE((155,164,182));
+#815=IFCINDEXEDPOLYGONALFACE((247,155,246));
+#816=IFCINDEXEDPOLYGONALFACE((163,182,164));
+#817=IFCINDEXEDPOLYGONALFACE((252,198,234));
+#818=IFCINDEXEDPOLYGONALFACE((244,243,233));
+#819=IFCINDEXEDPOLYGONALFACE((252,254,253));
+#820=IFCINDEXEDPOLYGONALFACE((252,253,244));
+#821=IFCINDEXEDPOLYGONALFACE((252,244,233));
+#822=IFCINDEXEDPOLYGONALFACE((198,252,233));
+#823=IFCINDEXEDPOLYGONALFACE((230,226,227));
+#824=IFCINDEXEDPOLYGONALFACE((256,257,255));
+#825=IFCINDEXEDPOLYGONALFACE((237,235,238));
+#826=IFCINDEXEDPOLYGONALFACE((210,211,214));
+#827=IFCINDEXEDPOLYGONALFACE((214,223,213));
+#828=IFCINDEXEDPOLYGONALFACE((258,210,214));
+#829=IFCINDEXEDPOLYGONALFACE((192,191,259));
+#830=IFCINDEXEDPOLYGONALFACE((192,183,251));
+#831=IFCINDEXEDPOLYGONALFACE((183,260,184));
+#832=IFCINDEXEDPOLYGONALFACE((261,183,192));
+#833=IFCINDEXEDPOLYGONALFACE((261,262,183));
+#834=IFCINDEXEDPOLYGONALFACE((259,261,192));
+#835=IFCINDEXEDPOLYGONALFACE((248,259,191));
+#836=IFCINDEXEDPOLYGONALFACE((189,247,191));
+#837=IFCINDEXEDPOLYGONALFACE((191,247,248));
+#838=IFCINDEXEDPOLYGONALFACE((238,257,237));
+#839=IFCINDEXEDPOLYGONALFACE((255,257,238));
+#840=IFCINDEXEDPOLYGONALFACE((244,255,238));
+#841=IFCINDEXEDPOLYGONALFACE((143,226,263));
+#842=IFCINDEXEDPOLYGONALFACE((263,90,143));
+#843=IFCINDEXEDPOLYGONALFACE((226,249,160));
+#844=IFCINDEXEDPOLYGONALFACE((160,263,226));
+#845=IFCINDEXEDPOLYGONALFACE((240,246,186));
+#846=IFCINDEXEDPOLYGONALFACE((245,259,248));
+#847=IFCINDEXEDPOLYGONALFACE((186,246,182));
+#848=IFCINDEXEDPOLYGONALFACE((234,175,184));
+#849=IFCINDEXEDPOLYGONALFACE((234,184,252));
+#850=IFCINDEXEDPOLYGONALFACE((204,141,203));
+#851=IFCINDEXEDPOLYGONALFACE((90,263,161));
+#852=IFCINDEXEDPOLYGONALFACE((256,237,257));
+#853=IFCINDEXEDPOLYGONALFACE((237,256,160));
+#854=IFCINDEXEDPOLYGONALFACE((249,236,237));
+#855=IFCINDEXEDPOLYGONALFACE((241,245,240));
+#856=IFCINDEXEDPOLYGONALFACE((242,245,241));
+#857=IFCINDEXEDPOLYGONALFACE((242,261,245));
+#858=IFCINDEXEDPOLYGONALFACE((242,212,262));
+#859=IFCINDEXEDPOLYGONALFACE((262,261,242));
+#860=IFCINDEXEDPOLYGONALFACE((262,212,258));
+#861=IFCINDEXEDPOLYGONALFACE((170,213,169));
+#862=IFCINDEXEDPOLYGONALFACE((245,261,259));
+#863=IFCINDEXEDPOLYGONALFACE((240,245,246));
+#864=IFCINDEXEDPOLYGONALFACE((182,246,155));
+#865=IFCINDEXEDPOLYGONALFACE((256,255,264));
+#866=IFCINDEXEDPOLYGONALFACE((249,237,160));
+#867=IFCINDEXEDPOLYGONALFACE((264,255,253));
+#868=IFCINDEXEDPOLYGONALFACE((255,244,253));
+#869=IFCINDEXEDPOLYGONALFACE((258,212,210));
+#870=IFCINDEXEDPOLYGONALFACE((206,139,207));
+#871=IFCINDEXEDPOLYGONALFACE((139,175,207));
+#872=IFCINDEXEDPOLYGONALFACE((234,232,207));
+#873=IFCINDEXEDPOLYGONALFACE((175,234,207));
+#874=IFCINDEXEDPOLYGONALFACE((58,90,89));
+#875=IFCINDEXEDPOLYGONALFACE((90,91,143));
+#876=IFCINDEXEDPOLYGONALFACE((142,141,204));
+#877=IFCINDEXEDPOLYGONALFACE((204,227,142));
+#878=IFCINDEXEDPOLYGONALFACE((133,134,184));
+#879=IFCINDEXEDPOLYGONALFACE((265,252,184));
+#880=IFCINDEXEDPOLYGONALFACE((254,252,265));
+#881=IFCINDEXEDPOLYGONALFACE((262,253,265));
+#882=IFCINDEXEDPOLYGONALFACE((265,184,260));
+#883=IFCINDEXEDPOLYGONALFACE((260,262,265));
+#884=IFCINDEXEDPOLYGONALFACE((183,262,260));
+#885=IFCINDEXEDPOLYGONALFACE((213,223,222));
+#886=IFCINDEXEDPOLYGONALFACE((93,203,141));
+#887=IFCINDEXEDPOLYGONALFACE((203,93,206));
+#888=IFCINDEXEDPOLYGONALFACE((93,140,206));
+#889=IFCINDEXEDPOLYGONALFACE((206,140,139));
+#890=IFCINDEXEDPOLYGONALFACE((99,140,93));
+#891=IFCINDEXEDPOLYGONALFACE((143,91,141));
+#892=IFCINDEXEDPOLYGONALFACE((253,254,265));
+#893=IFCINDEXEDPOLYGONALFACE((253,258,264));
+#894=IFCINDEXEDPOLYGONALFACE((258,253,262));
+#895=IFCINDEXEDPOLYGONALFACE((161,263,160));
+#896=IFCINDEXEDPOLYGONALFACE((160,131,161));
+#897=IFCINDEXEDPOLYGONALFACE((171,167,157));
+#898=IFCINDEXEDPOLYGONALFACE((127,171,136));
+#899=IFCINDEXEDPOLYGONALFACE((264,258,170));
+#900=IFCINDEXEDPOLYGONALFACE((258,214,170));
+#901=IFCINDEXEDPOLYGONALFACE((266,219,220));
+#902=IFCINDEXEDPOLYGONALFACE((169,219,266));
+#903=IFCINDEXEDPOLYGONALFACE((169,213,219));
+#904=IFCINDEXEDPOLYGONALFACE((168,266,103));
+#905=IFCINDEXEDPOLYGONALFACE((168,169,266));
+#906=IFCINDEXEDPOLYGONALFACE((179,177,180));
+#907=IFCINDEXEDPOLYGONALFACE((185,181,180));
+#908=IFCINDEXEDPOLYGONALFACE((185,221,181));
+#909=IFCINDEXEDPOLYGONALFACE((177,176,185));
+#910=IFCINDEXEDPOLYGONALFACE((193,221,185));
+#911=IFCINDEXEDPOLYGONALFACE((193,176,178));
+#912=IFCINDEXEDPOLYGONALFACE((193,194,267));
+#913=IFCINDEXEDPOLYGONALFACE((267,268,193));
+#914=IFCINDEXEDPOLYGONALFACE((185,176,193));
+#915=IFCINDEXEDPOLYGONALFACE((193,268,221));
+#916=IFCINDEXEDPOLYGONALFACE((218,221,268));
+#917=IFCINDEXEDPOLYGONALFACE((171,160,256));
+#918=IFCINDEXEDPOLYGONALFACE((160,171,127));
+#919=IFCINDEXEDPOLYGONALFACE((256,264,171));
+#920=IFCINDEXEDPOLYGONALFACE((171,170,167));
+#921=IFCINDEXEDPOLYGONALFACE((264,170,171));
+#922=IFCINDEXEDPOLYGONALFACE((108,269,107));
+#923=IFCINDEXEDPOLYGONALFACE((270,107,271));
+#924=IFCINDEXEDPOLYGONALFACE((25,271,272));
+#925=IFCINDEXEDPOLYGONALFACE((26,24,33));
+#926=IFCINDEXEDPOLYGONALFACE((273,108,106));
+#927=IFCINDEXEDPOLYGONALFACE((274,108,273));
+#928=IFCINDEXEDPOLYGONALFACE((273,275,274));
+#929=IFCINDEXEDPOLYGONALFACE((275,273,276));
+#930=IFCINDEXEDPOLYGONALFACE((278,279,277));
+#931=IFCINDEXEDPOLYGONALFACE((281,282,280));
+#932=IFCINDEXEDPOLYGONALFACE((280,282,87));
+#933=IFCINDEXEDPOLYGONALFACE((84,87,279));
+#934=IFCINDEXEDPOLYGONALFACE((283,279,284));
+#935=IFCINDEXEDPOLYGONALFACE((279,283,84));
+#936=IFCINDEXEDPOLYGONALFACE((84,80,82));
+#937=IFCINDEXEDPOLYGONALFACE((284,285,283));
+#938=IFCINDEXEDPOLYGONALFACE((283,285,286));
+#939=IFCINDEXEDPOLYGONALFACE((13,14,26));
+#940=IFCINDEXEDPOLYGONALFACE((37,13,35));
+#941=IFCINDEXEDPOLYGONALFACE((37,287,38));
+#942=IFCINDEXEDPOLYGONALFACE((15,288,106));
+#943=IFCINDEXEDPOLYGONALFACE((107,270,106));
+#944=IFCINDEXEDPOLYGONALFACE((288,273,106));
+#945=IFCINDEXEDPOLYGONALFACE((15,270,14));
+#946=IFCINDEXEDPOLYGONALFACE((14,270,271));
+#947=IFCINDEXEDPOLYGONALFACE((25,14,271));
+#948=IFCINDEXEDPOLYGONALFACE((272,271,289));
+#949=IFCINDEXEDPOLYGONALFACE((25,26,14));
+#950=IFCINDEXEDPOLYGONALFACE((290,291,102));
+#951=IFCINDEXEDPOLYGONALFACE((272,102,291));
+#952=IFCINDEXEDPOLYGONALFACE((104,102,272));
+#953=IFCINDEXEDPOLYGONALFACE((104,168,103));
+#954=IFCINDEXEDPOLYGONALFACE((289,104,272));
+#955=IFCINDEXEDPOLYGONALFACE((168,104,289));
+#956=IFCINDEXEDPOLYGONALFACE((289,271,157));
+#957=IFCINDEXEDPOLYGONALFACE((168,289,157));
+#958=IFCINDEXEDPOLYGONALFACE((157,167,168));
+#959=IFCINDEXEDPOLYGONALFACE((157,271,107));
+#960=IFCINDEXEDPOLYGONALFACE((290,292,291));
+#961=IFCINDEXEDPOLYGONALFACE((293,295,294));
+#962=IFCINDEXEDPOLYGONALFACE((290,295,293));
+#963=IFCINDEXEDPOLYGONALFACE((293,292,290));
+#964=IFCINDEXEDPOLYGONALFACE((102,296,290));
+#965=IFCINDEXEDPOLYGONALFACE((283,286,81));
+#966=IFCINDEXEDPOLYGONALFACE((79,81,286));
+#967=IFCINDEXEDPOLYGONALFACE((70,80,79));
+#968=IFCINDEXEDPOLYGONALFACE((72,80,70));
+#969=IFCINDEXEDPOLYGONALFACE((81,84,283));
+#970=IFCINDEXEDPOLYGONALFACE((81,80,84));
+#971=IFCINDEXEDPOLYGONALFACE((124,122,297));
+#972=IFCINDEXEDPOLYGONALFACE((297,122,298));
+#973=IFCINDEXEDPOLYGONALFACE((267,194,120));
+#974=IFCINDEXEDPOLYGONALFACE((120,121,267));
+#975=IFCINDEXEDPOLYGONALFACE((267,123,268));
+#976=IFCINDEXEDPOLYGONALFACE((121,123,267));
+#977=IFCINDEXEDPOLYGONALFACE((299,124,297));
+#978=IFCINDEXEDPOLYGONALFACE((268,123,218));
+#979=IFCINDEXEDPOLYGONALFACE((299,123,124));
+#980=IFCINDEXEDPOLYGONALFACE((300,297,298));
+#981=IFCINDEXEDPOLYGONALFACE((217,299,297));
+#982=IFCINDEXEDPOLYGONALFACE((218,123,299));
+#983=IFCINDEXEDPOLYGONALFACE((217,297,300));
+#984=IFCINDEXEDPOLYGONALFACE((220,217,300));
+#985=IFCINDEXEDPOLYGONALFACE((217,218,299));
+#986=IFCINDEXEDPOLYGONALFACE((266,220,295));
+#987=IFCINDEXEDPOLYGONALFACE((103,266,295));
+#988=IFCINDEXEDPOLYGONALFACE((102,103,296));
+#989=IFCINDEXEDPOLYGONALFACE((103,295,296));
+#990=IFCINDEXEDPOLYGONALFACE((295,220,301));
+#991=IFCINDEXEDPOLYGONALFACE((295,290,296));
+#992=IFCINDEXEDPOLYGONALFACE((294,302,293));
+#993=IFCINDEXEDPOLYGONALFACE((302,304,303));
+#994=IFCINDEXEDPOLYGONALFACE((301,294,295));
+#995=IFCINDEXEDPOLYGONALFACE((301,305,294));
+#996=IFCINDEXEDPOLYGONALFACE((294,304,302));
+#997=IFCINDEXEDPOLYGONALFACE((305,304,294));
+#998=IFCINDEXEDPOLYGONALFACE((306,304,305));
+#999=IFCINDEXEDPOLYGONALFACE((301,306,305));
+#1000=IFCINDEXEDPOLYGONALFACE((301,220,306));
+#1001=IFCINDEXEDPOLYGONALFACE((304,306,298));
+#1002=IFCINDEXEDPOLYGONALFACE((300,298,306));
+#1003=IFCINDEXEDPOLYGONALFACE((300,306,220));
+#1004=IFCINDEXEDPOLYGONALFACE((70,79,307));
+#1005=IFCINDEXEDPOLYGONALFACE((28,32,31));
+#1006=IFCINDEXEDPOLYGONALFACE((36,308,31));
+#1007=IFCINDEXEDPOLYGONALFACE((309,56,18));
+#1008=IFCINDEXEDPOLYGONALFACE((19,309,18));
+#1009=IFCINDEXEDPOLYGONALFACE((56,55,18));
+#1010=IFCINDEXEDPOLYGONALFACE((145,54,56));
+#1011=IFCINDEXEDPOLYGONALFACE((56,113,145));
+#1012=IFCINDEXEDPOLYGONALFACE((147,145,112));
+#1013=IFCINDEXEDPOLYGONALFACE((112,145,113));
+#1014=IFCINDEXEDPOLYGONALFACE((310,113,56));
+#1015=IFCINDEXEDPOLYGONALFACE((311,312,54));
+#1016=IFCINDEXEDPOLYGONALFACE((116,311,54));
+#1017=IFCINDEXEDPOLYGONALFACE((145,116,54));
+#1018=IFCINDEXEDPOLYGONALFACE((310,116,113));
+#1019=IFCINDEXEDPOLYGONALFACE((116,114,113));
+#1020=IFCINDEXEDPOLYGONALFACE((311,116,310));
+#1021=IFCINDEXEDPOLYGONALFACE((313,311,310));
+#1022=IFCINDEXEDPOLYGONALFACE((314,311,313));
+#1023=IFCINDEXEDPOLYGONALFACE((98,313,315));
+#1024=IFCINDEXEDPOLYGONALFACE((98,314,313));
+#1025=IFCINDEXEDPOLYGONALFACE((311,314,97));
+#1026=IFCINDEXEDPOLYGONALFACE((97,316,311));
+#1027=IFCINDEXEDPOLYGONALFACE((311,316,312));
+#1028=IFCINDEXEDPOLYGONALFACE((60,66,95));
+#1029=IFCINDEXEDPOLYGONALFACE((316,62,317));
+#1030=IFCINDEXEDPOLYGONALFACE((62,95,317));
+#1031=IFCINDEXEDPOLYGONALFACE((317,95,66));
+#1032=IFCINDEXEDPOLYGONALFACE((318,66,67));
+#1033=IFCINDEXEDPOLYGONALFACE((319,321,320));
+#1034=IFCINDEXEDPOLYGONALFACE((320,321,66));
+#1035=IFCINDEXEDPOLYGONALFACE((319,322,321));
+#1036=IFCINDEXEDPOLYGONALFACE((321,322,323));
+#1037=IFCINDEXEDPOLYGONALFACE((321,323,317));
+#1038=IFCINDEXEDPOLYGONALFACE((323,322,324));
+#1039=IFCINDEXEDPOLYGONALFACE((317,323,312));
+#1040=IFCINDEXEDPOLYGONALFACE((323,324,55));
+#1041=IFCINDEXEDPOLYGONALFACE((320,325,319));
+#1042=IFCINDEXEDPOLYGONALFACE((318,320,66));
+#1043=IFCINDEXEDPOLYGONALFACE((97,96,326));
+#1044=IFCINDEXEDPOLYGONALFACE((314,98,97));
+#1045=IFCINDEXEDPOLYGONALFACE((316,326,327));
+#1046=IFCINDEXEDPOLYGONALFACE((326,316,97));
+#1047=IFCINDEXEDPOLYGONALFACE((328,98,315));
+#1048=IFCINDEXEDPOLYGONALFACE((98,328,329));
+#1049=IFCINDEXEDPOLYGONALFACE((330,328,49));
+#1050=IFCINDEXEDPOLYGONALFACE((329,328,330));
+#1051=IFCINDEXEDPOLYGONALFACE((50,331,49));
+#1052=IFCINDEXEDPOLYGONALFACE((331,330,49));
+#1053=IFCINDEXEDPOLYGONALFACE((50,12,332));
+#1054=IFCINDEXEDPOLYGONALFACE((333,334,61));
+#1055=IFCINDEXEDPOLYGONALFACE((333,10,334));
+#1056=IFCINDEXEDPOLYGONALFACE((333,61,327));
+#1057=IFCINDEXEDPOLYGONALFACE((60,61,334));
+#1058=IFCINDEXEDPOLYGONALFACE((61,62,327));
+#1059=IFCINDEXEDPOLYGONALFACE((334,10,335));
+#1060=IFCINDEXEDPOLYGONALFACE((10,280,335));
+#1061=IFCINDEXEDPOLYGONALFACE((10,4,280));
+#1062=IFCINDEXEDPOLYGONALFACE((4,336,280));
+#1063=IFCINDEXEDPOLYGONALFACE((337,335,280));
+#1064=IFCINDEXEDPOLYGONALFACE((335,60,334));
+#1065=IFCINDEXEDPOLYGONALFACE((60,335,337));
+#1066=IFCINDEXEDPOLYGONALFACE((62,316,327));
+#1067=IFCINDEXEDPOLYGONALFACE((329,96,98));
+#1068=IFCINDEXEDPOLYGONALFACE((96,329,326));
+#1069=IFCINDEXEDPOLYGONALFACE((57,338,5));
+#1070=IFCINDEXEDPOLYGONALFACE((59,5,339));
+#1071=IFCINDEXEDPOLYGONALFACE((339,5,340));
+#1072=IFCINDEXEDPOLYGONALFACE((326,329,330));
+#1073=IFCINDEXEDPOLYGONALFACE((337,280,87));
+#1074=IFCINDEXEDPOLYGONALFACE((88,60,337));
+#1075=IFCINDEXEDPOLYGONALFACE((88,337,87));
+#1076=IFCINDEXEDPOLYGONALFACE((330,331,326));
+#1077=IFCINDEXEDPOLYGONALFACE((12,11,332));
+#1078=IFCINDEXEDPOLYGONALFACE((332,331,50));
+#1079=IFCINDEXEDPOLYGONALFACE((333,332,11));
+#1080=IFCINDEXEDPOLYGONALFACE((326,331,333));
+#1081=IFCINDEXEDPOLYGONALFACE((341,110,342));
+#1082=IFCINDEXEDPOLYGONALFACE((57,130,338));
+#1083=IFCINDEXEDPOLYGONALFACE((343,110,338));
+#1084=IFCINDEXEDPOLYGONALFACE((5,338,281));
+#1085=IFCINDEXEDPOLYGONALFACE((280,5,281));
+#1086=IFCINDEXEDPOLYGONALFACE((345,346,344));
+#1087=IFCINDEXEDPOLYGONALFACE((281,346,345));
+#1088=IFCINDEXEDPOLYGONALFACE((345,282,281));
+#1089=IFCINDEXEDPOLYGONALFACE((269,348,347));
+#1090=IFCINDEXEDPOLYGONALFACE((107,269,156));
+#1091=IFCINDEXEDPOLYGONALFACE((157,107,156));
+#1092=IFCINDEXEDPOLYGONALFACE((156,269,347));
+#1093=IFCINDEXEDPOLYGONALFACE((349,156,347));
+#1094=IFCINDEXEDPOLYGONALFACE((156,349,135));
+#1095=IFCINDEXEDPOLYGONALFACE((349,117,135));
+#1096=IFCINDEXEDPOLYGONALFACE((125,135,117));
+#1097=IFCINDEXEDPOLYGONALFACE((347,350,349));
+#1098=IFCINDEXEDPOLYGONALFACE((349,350,117));
+#1099=IFCINDEXEDPOLYGONALFACE((109,110,341));
+#1100=IFCINDEXEDPOLYGONALFACE((126,341,129));
+#1101=IFCINDEXEDPOLYGONALFACE((341,126,109));
+#1102=IFCINDEXEDPOLYGONALFACE((129,341,342));
+#1103=IFCINDEXEDPOLYGONALFACE((117,350,111));
+#1104=IFCINDEXEDPOLYGONALFACE((347,348,350));
+#1105=IFCINDEXEDPOLYGONALFACE((111,338,110));
+#1106=IFCINDEXEDPOLYGONALFACE((338,111,350));
+#1107=IFCINDEXEDPOLYGONALFACE((108,274,269));
+#1108=IFCINDEXEDPOLYGONALFACE((269,274,348));
+#1109=IFCINDEXEDPOLYGONALFACE((348,274,275));
+#1110=IFCINDEXEDPOLYGONALFACE((350,348,281));
+#1111=IFCINDEXEDPOLYGONALFACE((348,275,276));
+#1112=IFCINDEXEDPOLYGONALFACE((276,346,348));
+#1113=IFCINDEXEDPOLYGONALFACE((345,279,282));
+#1114=IFCINDEXEDPOLYGONALFACE((281,348,346));
+#1115=IFCINDEXEDPOLYGONALFACE((350,281,338));
+#1116=IFCINDEXEDPOLYGONALFACE((87,282,279));
+#1117=IFCINDEXEDPOLYGONALFACE((270,15,106));
+#1118=IFCINDEXEDPOLYGONALFACE((15,13,38));
+#1119=IFCINDEXEDPOLYGONALFACE((287,351,38));
+#1120=IFCINDEXEDPOLYGONALFACE((352,79,286));
+#1121=IFCINDEXEDPOLYGONALFACE((79,352,307));
+#1122=IFCINDEXEDPOLYGONALFACE((17,324,353));
+#1123=IFCINDEXEDPOLYGONALFACE((55,17,18));
+#1124=IFCINDEXEDPOLYGONALFACE((324,17,55));
+#1125=IFCINDEXEDPOLYGONALFACE((316,317,312));
+#1126=IFCINDEXEDPOLYGONALFACE((66,321,317));
+#1127=IFCINDEXEDPOLYGONALFACE((59,57,5));
+#1128=IFCINDEXEDPOLYGONALFACE((354,279,345));
+#1129=IFCINDEXEDPOLYGONALFACE((345,355,354));
+#1130=IFCINDEXEDPOLYGONALFACE((355,345,344));
+#1131=IFCINDEXEDPOLYGONALFACE((354,277,279));
+#1132=IFCINDEXEDPOLYGONALFACE((354,355,356));
+#1133=IFCINDEXEDPOLYGONALFACE((357,359,358));
+#1134=IFCINDEXEDPOLYGONALFACE((357,358,355));
+#1135=IFCINDEXEDPOLYGONALFACE((357,355,344));
+#1136=IFCINDEXEDPOLYGONALFACE((285,360,286));
+#1137=IFCINDEXEDPOLYGONALFACE((361,351,287));
+#1138=IFCINDEXEDPOLYGONALFACE((38,288,15));
+#1139=IFCINDEXEDPOLYGONALFACE((38,351,53));
+#1140=IFCINDEXEDPOLYGONALFACE((361,51,351));
+#1141=IFCINDEXEDPOLYGONALFACE((358,53,52));
+#1142=IFCINDEXEDPOLYGONALFACE((53,351,51));
+#1143=IFCINDEXEDPOLYGONALFACE((352,286,360));
+#1144=IFCINDEXEDPOLYGONALFACE((360,44,352));
+#1145=IFCINDEXEDPOLYGONALFACE((352,44,362));
+#1146=IFCINDEXEDPOLYGONALFACE((313,48,315));
+#1147=IFCINDEXEDPOLYGONALFACE((315,48,47));
+#1148=IFCINDEXEDPOLYGONALFACE((315,47,328));
+#1149=IFCINDEXEDPOLYGONALFACE((331,332,333));
+#1150=IFCINDEXEDPOLYGONALFACE((288,276,273));
+#1151=IFCINDEXEDPOLYGONALFACE((276,288,359));
+#1152=IFCINDEXEDPOLYGONALFACE((363,359,288));
+#1153=IFCINDEXEDPOLYGONALFACE((276,359,357));
+#1154=IFCINDEXEDPOLYGONALFACE((356,277,354));
+#1155=IFCINDEXEDPOLYGONALFACE((55,312,323));
+#1156=IFCINDEXEDPOLYGONALFACE((55,54,312));
+#1157=IFCINDEXEDPOLYGONALFACE((310,48,313));
+#1158=IFCINDEXEDPOLYGONALFACE((364,48,310));
+#1159=IFCINDEXEDPOLYGONALFACE((310,56,364));
+#1160=IFCINDEXEDPOLYGONALFACE((5,318,340));
+#1161=IFCINDEXEDPOLYGONALFACE((2,5,3));
+#1162=IFCINDEXEDPOLYGONALFACE((318,5,320));
+#1163=IFCINDEXEDPOLYGONALFACE((320,5,2));
+#1164=IFCINDEXEDPOLYGONALFACE((320,2,365));
+#1165=IFCINDEXEDPOLYGONALFACE((327,326,333));
+#1166=IFCINDEXEDPOLYGONALFACE((346,276,344));
+#1167=IFCINDEXEDPOLYGONALFACE((344,276,357));
+#1168=IFCINDEXEDPOLYGONALFACE((366,277,356));
+#1169=IFCINDEXEDPOLYGONALFACE((366,278,277));
+#1170=IFCINDEXEDPOLYGONALFACE((278,45,44));
+#1171=IFCINDEXEDPOLYGONALFACE((51,367,43));
+#1172=IFCINDEXEDPOLYGONALFACE((368,43,367));
+#1173=IFCINDEXEDPOLYGONALFACE((43,368,44));
+#1174=IFCINDEXEDPOLYGONALFACE((368,362,44));
+#1175=IFCINDEXEDPOLYGONALFACE((278,44,285));
+#1176=IFCINDEXEDPOLYGONALFACE((285,44,360));
+#1177=IFCINDEXEDPOLYGONALFACE((358,359,53));
+#1178=IFCINDEXEDPOLYGONALFACE((363,38,53));
+#1179=IFCINDEXEDPOLYGONALFACE((359,363,53));
+#1180=IFCINDEXEDPOLYGONALFACE((51,361,367));
+#1181=IFCINDEXEDPOLYGONALFACE((280,336,5));
+#1182=IFCINDEXEDPOLYGONALFACE((278,284,279));
+#1183=IFCINDEXEDPOLYGONALFACE((355,358,356));
+#1184=IFCINDEXEDPOLYGONALFACE((356,358,52));
+#1185=IFCINDEXEDPOLYGONALFACE((356,52,366));
+#1186=IFCINDEXEDPOLYGONALFACE((40,302,303));
+#1187=IFCINDEXEDPOLYGONALFACE((36,30,369));
+#1188=IFCINDEXEDPOLYGONALFACE((42,308,36));
+#1189=IFCINDEXEDPOLYGONALFACE((42,41,308));
+#1190=IFCINDEXEDPOLYGONALFACE((36,369,42));
+#1191=IFCINDEXEDPOLYGONALFACE((369,292,42));
+#1192=IFCINDEXEDPOLYGONALFACE((42,292,40));
+#1193=IFCINDEXEDPOLYGONALFACE((302,40,292));
+#1194=IFCINDEXEDPOLYGONALFACE((352,70,307));
+#1195=IFCINDEXEDPOLYGONALFACE((328,47,39));
+#1196=IFCINDEXEDPOLYGONALFACE((328,39,49));
+#1197=IFCINDEXEDPOLYGONALFACE((49,23,50));
+#1198=IFCINDEXEDPOLYGONALFACE((5,336,4));
+#1199=IFCINDEXEDPOLYGONALFACE((284,278,285));
+#1200=IFCINDEXEDPOLYGONALFACE((45,278,366));
+#1201=IFCINDEXEDPOLYGONALFACE((291,292,369));
+#1202=IFCINDEXEDPOLYGONALFACE((30,291,369));
+#1203=IFCINDEXEDPOLYGONALFACE((291,30,27));
+#1204=IFCINDEXEDPOLYGONALFACE((291,25,272));
+#1205=IFCINDEXEDPOLYGONALFACE((25,291,27));
+#1206=IFCINDEXEDPOLYGONALFACE((45,51,43));
+#1207=IFCINDEXEDPOLYGONALFACE((51,45,52));
+#1208=IFCINDEXEDPOLYGONALFACE((45,366,52));
+#1209=IFCINDEXEDPOLYGONALFACE((38,363,288));
+#1210=IFCINDEXEDPOLYGONALFACE((22,39,46));
+#1211=IFCINDEXEDPOLYGONALFACE((22,371,370));
+#1212=IFCINDEXEDPOLYGONALFACE((22,46,371));
+#1213=IFCINDEXEDPOLYGONALFACE((371,364,309));
+#1214=IFCINDEXEDPOLYGONALFACE((371,46,364));
+#1215=IFCINDEXEDPOLYGONALFACE((309,364,56));
+#1216=IFCINDEXEDPOLYGONALFACE((46,48,364));
+#1217=IFCINDEXEDPOLYGONALFACE((39,47,46));
+#1218=IFCINDEXEDPOLYGONALFACE((333,11,10));
+#1219=IFCINDEXEDPOLYGONALFACE((29,24,25));
+#1220=IFCINDEXEDPOLYGONALFACE((29,25,27));
+#1221=IFCINDEXEDPOLYGONALFACE((302,292,293));
+#1222=IFCINDEXEDPOLYGONALFACE((309,19,371));
+#1223=IFCINDEXEDPOLYGONALFACE((19,21,370));
+#1224=IFCINDEXEDPOLYGONALFACE((371,19,370));
+#1225=IFCINDEXEDPOLYGONALFACE((8,370,21));
+#1226=IFCINDEXEDPOLYGONALFACE((365,325,320));
+#1227=IFCINDEXEDPOLYGONALFACE((353,325,365));
+#1228=IFCINDEXEDPOLYGONALFACE((325,322,319));
+#1229=IFCINDEXEDPOLYGONALFACE((322,325,324));
+#1230=IFCINDEXEDPOLYGONALFACE((353,324,325));
+#1231=IFCINDEXEDPOLYGONALFACE((2,1,20));
+#1232=IFCINDEXEDPOLYGONALFACE((20,365,2));
+#1233=IFCINDEXEDPOLYGONALFACE((353,365,20));
+#1234=IFCINDEXEDPOLYGONALFACE((20,17,353));
+#1235=IFCINDEXEDPOLYGONALFACE((1,6,20));
+#1236=IFCINDEXEDPOLYGONALFACE((16,8,21));
+#1237=IFCINDEXEDPOLYGONALFACE((6,8,16));
+#1238=IFCINDEXEDPOLYGONALFACE((8,22,370));
+#1239=IFCINDEXEDPOLYGONALFACE((4,7,1));
+#1240=IFCINDEXEDPOLYGONALFACE((4,10,7));
+#1241=IFCINDEXEDPOLYGONALFACE((146,115,172));
+#1242=IFCINDEXEDPOLYGONALFACE((146,147,115));
+#1243=IFCINDEXEDPOLYGONALFACE((115,112,114));
+#1244=IFCINDEXEDPOLYGONALFACE((373,374,372));
+#1245=IFCINDEXEDPOLYGONALFACE((373,375,374));
+#1246=IFCINDEXEDPOLYGONALFACE((376,374,375));
+#1247=IFCINDEXEDPOLYGONALFACE((374,376,377));
+#1248=IFCINDEXEDPOLYGONALFACE((378,380,379));
+#1249=IFCINDEXEDPOLYGONALFACE((382,378,381));
+#1250=IFCINDEXEDPOLYGONALFACE((382,383,378));
+#1251=IFCINDEXEDPOLYGONALFACE((383,382,384));
+#1252=IFCINDEXEDPOLYGONALFACE((382,381,385));
+#1253=IFCINDEXEDPOLYGONALFACE((378,383,380));
+#1254=IFCINDEXEDPOLYGONALFACE((384,382,386));
+#1255=IFCINDEXEDPOLYGONALFACE((382,387,386));
+#1256=IFCINDEXEDPOLYGONALFACE((383,388,380));
+#1257=IFCINDEXEDPOLYGONALFACE((318,67,389));
+#1258=IFCINDEXEDPOLYGONALFACE((390,392,391));
+#1259=IFCINDEXEDPOLYGONALFACE((392,390,393));
+#1260=IFCINDEXEDPOLYGONALFACE((390,394,393));
+#1261=IFCINDEXEDPOLYGONALFACE((389,395,390));
+#1262=IFCINDEXEDPOLYGONALFACE((394,397,396));
+#1263=IFCINDEXEDPOLYGONALFACE((395,397,394));
+#1264=IFCINDEXEDPOLYGONALFACE((397,398,396));
+#1265=IFCINDEXEDPOLYGONALFACE((395,399,397));
+#1266=IFCINDEXEDPOLYGONALFACE((395,118,399));
+#1267=IFCINDEXEDPOLYGONALFACE((395,64,118));
+#1268=IFCINDEXEDPOLYGONALFACE((395,394,390));
+#1269=IFCINDEXEDPOLYGONALFACE((63,64,395));
+#1270=IFCINDEXEDPOLYGONALFACE((63,389,67));
+#1271=IFCINDEXEDPOLYGONALFACE((389,63,395));
+#1272=IFCINDEXEDPOLYGONALFACE((400,402,401));
+#1273=IFCINDEXEDPOLYGONALFACE((402,400,403));
+#1274=IFCINDEXEDPOLYGONALFACE((400,401,404));
+#1275=IFCINDEXEDPOLYGONALFACE((405,400,406));
+#1276=IFCINDEXEDPOLYGONALFACE((407,400,408));
+#1277=IFCINDEXEDPOLYGONALFACE((404,408,400));
+#1278=IFCINDEXEDPOLYGONALFACE((407,406,400));
+#1279=IFCINDEXEDPOLYGONALFACE((410,411,409));
+#1280=IFCINDEXEDPOLYGONALFACE((412,414,413));
+#1281=IFCINDEXEDPOLYGONALFACE((415,417,416));
+#1282=IFCINDEXEDPOLYGONALFACE((419,411,418));
+#1283=IFCINDEXEDPOLYGONALFACE((420,398,397));
+#1284=IFCINDEXEDPOLYGONALFACE((412,421,414));
+#1285=IFCINDEXEDPOLYGONALFACE((422,423,421));
+#1286=IFCINDEXEDPOLYGONALFACE((412,424,421));
+#1287=IFCINDEXEDPOLYGONALFACE((421,424,422));
+#1288=IFCINDEXEDPOLYGONALFACE((425,427,426));
+#1289=IFCINDEXEDPOLYGONALFACE((429,430,428));
+#1290=IFCINDEXEDPOLYGONALFACE((431,428,430));
+#1291=IFCINDEXEDPOLYGONALFACE((432,428,433));
+#1292=IFCINDEXEDPOLYGONALFACE((433,428,431));
+#1293=IFCINDEXEDPOLYGONALFACE((433,431,434));
+#1294=IFCINDEXEDPOLYGONALFACE((425,432,435));
+#1295=IFCINDEXEDPOLYGONALFACE((435,432,433));
+#1296=IFCINDEXEDPOLYGONALFACE((436,435,433));
+#1297=IFCINDEXEDPOLYGONALFACE((437,439,438));
+#1298=IFCINDEXEDPOLYGONALFACE((438,439,440));
+#1299=IFCINDEXEDPOLYGONALFACE((440,436,438));
+#1300=IFCINDEXEDPOLYGONALFACE((441,436,440));
+#1301=IFCINDEXEDPOLYGONALFACE((435,436,441));
+#1302=IFCINDEXEDPOLYGONALFACE((435,427,425));
+#1303=IFCINDEXEDPOLYGONALFACE((427,442,426));
+#1304=IFCINDEXEDPOLYGONALFACE((443,422,424));
+#1305=IFCINDEXEDPOLYGONALFACE((442,423,422));
+#1306=IFCINDEXEDPOLYGONALFACE((443,442,422));
+#1307=IFCINDEXEDPOLYGONALFACE((444,445,443));
+#1308=IFCINDEXEDPOLYGONALFACE((445,429,428));
+#1309=IFCINDEXEDPOLYGONALFACE((445,426,443));
+#1310=IFCINDEXEDPOLYGONALFACE((428,425,445));
+#1311=IFCINDEXEDPOLYGONALFACE((425,428,432));
+#1312=IFCINDEXEDPOLYGONALFACE((445,425,426));
+#1313=IFCINDEXEDPOLYGONALFACE((447,448,446));
+#1314=IFCINDEXEDPOLYGONALFACE((449,450,447));
+#1315=IFCINDEXEDPOLYGONALFACE((451,450,449));
+#1316=IFCINDEXEDPOLYGONALFACE((447,446,449));
+#1317=IFCINDEXEDPOLYGONALFACE((449,446,452));
+#1318=IFCINDEXEDPOLYGONALFACE((449,452,453));
+#1319=IFCINDEXEDPOLYGONALFACE((452,454,453));
+#1320=IFCINDEXEDPOLYGONALFACE((449,453,451));
+#1321=IFCINDEXEDPOLYGONALFACE((451,453,455));
+#1322=IFCINDEXEDPOLYGONALFACE((455,424,412));
+#1323=IFCINDEXEDPOLYGONALFACE((453,456,455));
+#1324=IFCINDEXEDPOLYGONALFACE((454,456,453));
+#1325=IFCINDEXEDPOLYGONALFACE((455,456,424));
+#1326=IFCINDEXEDPOLYGONALFACE((412,418,455));
+#1327=IFCINDEXEDPOLYGONALFACE((411,457,409));
+#1328=IFCINDEXEDPOLYGONALFACE((419,458,457));
+#1329=IFCINDEXEDPOLYGONALFACE((419,457,411));
+#1330=IFCINDEXEDPOLYGONALFACE((418,414,419));
+#1331=IFCINDEXEDPOLYGONALFACE((414,418,412));
+#1332=IFCINDEXEDPOLYGONALFACE((455,418,459));
+#1333=IFCINDEXEDPOLYGONALFACE((455,459,451));
+#1334=IFCINDEXEDPOLYGONALFACE((418,411,459));
+#1335=IFCINDEXEDPOLYGONALFACE((459,410,460));
+#1336=IFCINDEXEDPOLYGONALFACE((459,460,450));
+#1337=IFCINDEXEDPOLYGONALFACE((451,459,450));
+#1338=IFCINDEXEDPOLYGONALFACE((461,462,450));
+#1339=IFCINDEXEDPOLYGONALFACE((463,462,461));
+#1340=IFCINDEXEDPOLYGONALFACE((464,463,461));
+#1341=IFCINDEXEDPOLYGONALFACE((465,466,463));
+#1342=IFCINDEXEDPOLYGONALFACE((467,463,466));
+#1343=IFCINDEXEDPOLYGONALFACE((467,388,463));
+#1344=IFCINDEXEDPOLYGONALFACE((465,464,466));
+#1345=IFCINDEXEDPOLYGONALFACE((461,468,464));
+#1346=IFCINDEXEDPOLYGONALFACE((469,470,461));
+#1347=IFCINDEXEDPOLYGONALFACE((463,464,465));
+#1348=IFCINDEXEDPOLYGONALFACE((461,450,469));
+#1349=IFCINDEXEDPOLYGONALFACE((435,471,427));
+#1350=IFCINDEXEDPOLYGONALFACE((393,471,435));
+#1351=IFCINDEXEDPOLYGONALFACE((393,435,441));
+#1352=IFCINDEXEDPOLYGONALFACE((472,427,471));
+#1353=IFCINDEXEDPOLYGONALFACE((398,471,396));
+#1354=IFCINDEXEDPOLYGONALFACE((471,398,472));
+#1355=IFCINDEXEDPOLYGONALFACE((472,423,427));
+#1356=IFCINDEXEDPOLYGONALFACE((423,442,427));
+#1357=IFCINDEXEDPOLYGONALFACE((473,419,414));
+#1358=IFCINDEXEDPOLYGONALFACE((420,474,473));
+#1359=IFCINDEXEDPOLYGONALFACE((475,474,420));
+#1360=IFCINDEXEDPOLYGONALFACE((472,414,423));
+#1361=IFCINDEXEDPOLYGONALFACE((421,423,414));
+#1362=IFCINDEXEDPOLYGONALFACE((472,473,414));
+#1363=IFCINDEXEDPOLYGONALFACE((420,473,472));
+#1364=IFCINDEXEDPOLYGONALFACE((472,398,420));
+#1365=IFCINDEXEDPOLYGONALFACE((476,441,440));
+#1366=IFCINDEXEDPOLYGONALFACE((476,440,439));
+#1367=IFCINDEXEDPOLYGONALFACE((411,410,459));
+#1368=IFCINDEXEDPOLYGONALFACE((460,410,477));
+#1369=IFCINDEXEDPOLYGONALFACE((469,478,470));
+#1370=IFCINDEXEDPOLYGONALFACE((480,481,479));
+#1371=IFCINDEXEDPOLYGONALFACE((480,482,481));
+#1372=IFCINDEXEDPOLYGONALFACE((483,485,484));
+#1373=IFCINDEXEDPOLYGONALFACE((483,486,485));
+#1374=IFCINDEXEDPOLYGONALFACE((485,486,487));
+#1375=IFCINDEXEDPOLYGONALFACE((401,488,404));
+#1376=IFCINDEXEDPOLYGONALFACE((489,415,416));
+#1377=IFCINDEXEDPOLYGONALFACE((490,119,405));
+#1378=IFCINDEXEDPOLYGONALFACE((119,399,118));
+#1379=IFCINDEXEDPOLYGONALFACE((490,399,119));
+#1380=IFCINDEXEDPOLYGONALFACE((397,399,490));
+#1381=IFCINDEXEDPOLYGONALFACE((489,405,415));
+#1382=IFCINDEXEDPOLYGONALFACE((400,489,403));
+#1383=IFCINDEXEDPOLYGONALFACE((400,405,489));
+#1384=IFCINDEXEDPOLYGONALFACE((406,490,405));
+#1385=IFCINDEXEDPOLYGONALFACE((490,406,407));
+#1386=IFCINDEXEDPOLYGONALFACE((490,407,491));
+#1387=IFCINDEXEDPOLYGONALFACE((490,491,492));
+#1388=IFCINDEXEDPOLYGONALFACE((475,490,492));
+#1389=IFCINDEXEDPOLYGONALFACE((475,397,490));
+#1390=IFCINDEXEDPOLYGONALFACE((420,397,475));
+#1391=IFCINDEXEDPOLYGONALFACE((474,475,492));
+#1392=IFCINDEXEDPOLYGONALFACE((486,493,404));
+#1393=IFCINDEXEDPOLYGONALFACE((493,494,404));
+#1394=IFCINDEXEDPOLYGONALFACE((494,408,404));
+#1395=IFCINDEXEDPOLYGONALFACE((491,407,408));
+#1396=IFCINDEXEDPOLYGONALFACE((491,474,492));
+#1397=IFCINDEXEDPOLYGONALFACE((458,474,408));
+#1398=IFCINDEXEDPOLYGONALFACE((408,474,491));
+#1399=IFCINDEXEDPOLYGONALFACE((408,494,458));
+#1400=IFCINDEXEDPOLYGONALFACE((458,473,474));
+#1401=IFCINDEXEDPOLYGONALFACE((457,458,494));
+#1402=IFCINDEXEDPOLYGONALFACE((494,493,409));
+#1403=IFCINDEXEDPOLYGONALFACE((493,483,477));
+#1404=IFCINDEXEDPOLYGONALFACE((483,493,486));
+#1405=IFCINDEXEDPOLYGONALFACE((495,483,484));
+#1406=IFCINDEXEDPOLYGONALFACE((495,484,496));
+#1407=IFCINDEXEDPOLYGONALFACE((409,493,477));
+#1408=IFCINDEXEDPOLYGONALFACE((410,409,477));
+#1409=IFCINDEXEDPOLYGONALFACE((409,457,494));
+#1410=IFCINDEXEDPOLYGONALFACE((419,473,458));
+#1411=IFCINDEXEDPOLYGONALFACE((393,441,392));
+#1412=IFCINDEXEDPOLYGONALFACE((441,497,392));
+#1413=IFCINDEXEDPOLYGONALFACE((393,396,471));
+#1414=IFCINDEXEDPOLYGONALFACE((393,394,396));
+#1415=IFCINDEXEDPOLYGONALFACE((476,497,441));
+#1416=IFCINDEXEDPOLYGONALFACE((391,392,497));
+#1417=IFCINDEXEDPOLYGONALFACE((498,499,391));
+#1418=IFCINDEXEDPOLYGONALFACE((498,376,375));
+#1419=IFCINDEXEDPOLYGONALFACE((500,502,501));
+#1420=IFCINDEXEDPOLYGONALFACE((374,501,503));
+#1421=IFCINDEXEDPOLYGONALFACE((374,504,501));
+#1422=IFCINDEXEDPOLYGONALFACE((504,500,501));
+#1423=IFCINDEXEDPOLYGONALFACE((372,374,503));
+#1424=IFCINDEXEDPOLYGONALFACE((377,504,374));
+#1425=IFCINDEXEDPOLYGONALFACE((505,500,504));
+#1426=IFCINDEXEDPOLYGONALFACE((505,506,500));
+#1427=IFCINDEXEDPOLYGONALFACE((506,507,500));
+#1428=IFCINDEXEDPOLYGONALFACE((506,508,507));
+#1429=IFCINDEXEDPOLYGONALFACE((431,509,508));
+#1430=IFCINDEXEDPOLYGONALFACE((434,508,506));
+#1431=IFCINDEXEDPOLYGONALFACE((434,431,508));
+#1432=IFCINDEXEDPOLYGONALFACE((431,430,509));
+#1433=IFCINDEXEDPOLYGONALFACE((434,506,505));
+#1434=IFCINDEXEDPOLYGONALFACE((377,505,504));
+#1435=IFCINDEXEDPOLYGONALFACE((434,505,377));
+#1436=IFCINDEXEDPOLYGONALFACE((434,377,437));
+#1437=IFCINDEXEDPOLYGONALFACE((434,438,433));
+#1438=IFCINDEXEDPOLYGONALFACE((433,438,436));
+#1439=IFCINDEXEDPOLYGONALFACE((377,510,437));
+#1440=IFCINDEXEDPOLYGONALFACE((510,377,376));
+#1441=IFCINDEXEDPOLYGONALFACE((510,376,498));
+#1442=IFCINDEXEDPOLYGONALFACE((511,437,510));
+#1443=IFCINDEXEDPOLYGONALFACE((511,510,498));
+#1444=IFCINDEXEDPOLYGONALFACE((439,437,511));
+#1445=IFCINDEXEDPOLYGONALFACE((498,476,511));
+#1446=IFCINDEXEDPOLYGONALFACE((511,476,439));
+#1447=IFCINDEXEDPOLYGONALFACE((391,476,498));
+#1448=IFCINDEXEDPOLYGONALFACE((476,391,497));
+#1449=IFCINDEXEDPOLYGONALFACE((375,499,498));
+#1450=IFCINDEXEDPOLYGONALFACE((373,372,512));
+#1451=IFCINDEXEDPOLYGONALFACE((512,513,373));
+#1452=IFCINDEXEDPOLYGONALFACE((373,514,375));
+#1453=IFCINDEXEDPOLYGONALFACE((513,514,373));
+#1454=IFCINDEXEDPOLYGONALFACE((391,515,390));
+#1455=IFCINDEXEDPOLYGONALFACE((390,515,389));
+#1456=IFCINDEXEDPOLYGONALFACE((389,515,318));
+#1457=IFCINDEXEDPOLYGONALFACE((499,515,391));
+#1458=IFCINDEXEDPOLYGONALFACE((517,518,516));
+#1459=IFCINDEXEDPOLYGONALFACE((519,520,516));
+#1460=IFCINDEXEDPOLYGONALFACE((512,517,513));
+#1461=IFCINDEXEDPOLYGONALFACE((513,517,521));
+#1462=IFCINDEXEDPOLYGONALFACE((513,521,522));
+#1463=IFCINDEXEDPOLYGONALFACE((375,514,499));
+#1464=IFCINDEXEDPOLYGONALFACE((523,514,522));
+#1465=IFCINDEXEDPOLYGONALFACE((514,523,499));
+#1466=IFCINDEXEDPOLYGONALFACE((499,523,515));
+#1467=IFCINDEXEDPOLYGONALFACE((524,525,522));
+#1468=IFCINDEXEDPOLYGONALFACE((522,521,524));
+#1469=IFCINDEXEDPOLYGONALFACE((520,526,521));
+#1470=IFCINDEXEDPOLYGONALFACE((521,527,524));
+#1471=IFCINDEXEDPOLYGONALFACE((526,520,519));
+#1472=IFCINDEXEDPOLYGONALFACE((519,488,526));
+#1473=IFCINDEXEDPOLYGONALFACE((521,526,527));
+#1474=IFCINDEXEDPOLYGONALFACE((524,527,416));
+#1475=IFCINDEXEDPOLYGONALFACE((416,417,524));
+#1476=IFCINDEXEDPOLYGONALFACE((526,401,527));
+#1477=IFCINDEXEDPOLYGONALFACE((401,526,488));
+#1478=IFCINDEXEDPOLYGONALFACE((527,403,416));
+#1479=IFCINDEXEDPOLYGONALFACE((527,402,403));
+#1480=IFCINDEXEDPOLYGONALFACE((401,402,527));
+#1481=IFCINDEXEDPOLYGONALFACE((518,528,516));
+#1482=IFCINDEXEDPOLYGONALFACE((516,528,519));
+#1483=IFCINDEXEDPOLYGONALFACE((529,519,528));
+#1484=IFCINDEXEDPOLYGONALFACE((487,519,529));
+#1485=IFCINDEXEDPOLYGONALFACE((519,487,488));
+#1486=IFCINDEXEDPOLYGONALFACE((487,404,488));
+#1487=IFCINDEXEDPOLYGONALFACE((523,522,525));
+#1488=IFCINDEXEDPOLYGONALFACE((417,59,339));
+#1489=IFCINDEXEDPOLYGONALFACE((417,339,525));
+#1490=IFCINDEXEDPOLYGONALFACE((339,340,525));
+#1491=IFCINDEXEDPOLYGONALFACE((524,417,525));
+#1492=IFCINDEXEDPOLYGONALFACE((415,59,417));
+#1493=IFCINDEXEDPOLYGONALFACE((101,59,415));
+#1494=IFCINDEXEDPOLYGONALFACE((101,405,119));
+#1495=IFCINDEXEDPOLYGONALFACE((101,415,405));
+#1496=IFCINDEXEDPOLYGONALFACE((443,426,442));
+#1497=IFCINDEXEDPOLYGONALFACE((480,479,530));
+#1498=IFCINDEXEDPOLYGONALFACE((496,481,495));
+#1499=IFCINDEXEDPOLYGONALFACE((481,482,495));
+#1500=IFCINDEXEDPOLYGONALFACE((483,482,480));
+#1501=IFCINDEXEDPOLYGONALFACE((480,477,483));
+#1502=IFCINDEXEDPOLYGONALFACE((477,480,460));
+#1503=IFCINDEXEDPOLYGONALFACE((460,480,531));
+#1504=IFCINDEXEDPOLYGONALFACE((531,478,460));
+#1505=IFCINDEXEDPOLYGONALFACE((460,478,450));
+#1506=IFCINDEXEDPOLYGONALFACE((450,478,469));
+#1507=IFCINDEXEDPOLYGONALFACE((495,482,483));
+#1508=IFCINDEXEDPOLYGONALFACE((532,533,485));
+#1509=IFCINDEXEDPOLYGONALFACE((534,532,485));
+#1510=IFCINDEXEDPOLYGONALFACE((485,533,484));
+#1511=IFCINDEXEDPOLYGONALFACE((533,532,535));
+#1512=IFCINDEXEDPOLYGONALFACE((535,532,536));
+#1513=IFCINDEXEDPOLYGONALFACE((487,486,404));
+#1514=IFCINDEXEDPOLYGONALFACE((515,523,318));
+#1515=IFCINDEXEDPOLYGONALFACE((525,340,318));
+#1516=IFCINDEXEDPOLYGONALFACE((318,523,525));
+#1517=IFCINDEXEDPOLYGONALFACE((537,470,478));
+#1518=IFCINDEXEDPOLYGONALFACE((537,468,470));
+#1519=IFCINDEXEDPOLYGONALFACE((538,468,537));
+#1520=IFCINDEXEDPOLYGONALFACE((538,385,539));
+#1521=IFCINDEXEDPOLYGONALFACE((386,387,385));
+#1522=IFCINDEXEDPOLYGONALFACE((385,387,382));
+#1523=IFCINDEXEDPOLYGONALFACE((538,386,385));
+#1524=IFCINDEXEDPOLYGONALFACE((536,386,535));
+#1525=IFCINDEXEDPOLYGONALFACE((540,535,386));
+#1526=IFCINDEXEDPOLYGONALFACE((541,535,540));
+#1527=IFCINDEXEDPOLYGONALFACE((541,533,535));
+#1528=IFCINDEXEDPOLYGONALFACE((533,496,484));
+#1529=IFCINDEXEDPOLYGONALFACE((496,533,541));
+#1530=IFCINDEXEDPOLYGONALFACE((496,541,479));
+#1531=IFCINDEXEDPOLYGONALFACE((481,496,479));
+#1532=IFCINDEXEDPOLYGONALFACE((530,542,531));
+#1533=IFCINDEXEDPOLYGONALFACE((537,478,531));
+#1534=IFCINDEXEDPOLYGONALFACE((542,541,537));
+#1535=IFCINDEXEDPOLYGONALFACE((540,386,538));
+#1536=IFCINDEXEDPOLYGONALFACE((537,541,540));
+#1537=IFCINDEXEDPOLYGONALFACE((530,479,541));
+#1538=IFCINDEXEDPOLYGONALFACE((530,541,542));
+#1539=IFCINDEXEDPOLYGONALFACE((542,537,531));
+#1540=IFCINDEXEDPOLYGONALFACE((537,540,538));
+#1541=IFCINDEXEDPOLYGONALFACE((531,480,530));
+#1542=IFCINDEXEDPOLYGONALFACE((529,528,543));
+#1543=IFCINDEXEDPOLYGONALFACE((544,534,543));
+#1544=IFCINDEXEDPOLYGONALFACE((543,534,529));
+#1545=IFCINDEXEDPOLYGONALFACE((534,485,487));
+#1546=IFCINDEXEDPOLYGONALFACE((529,534,487));
+#1547=IFCINDEXEDPOLYGONALFACE((536,532,545));
+#1548=IFCINDEXEDPOLYGONALFACE((544,543,546));
+#1549=IFCINDEXEDPOLYGONALFACE((534,544,545));
+#1550=IFCINDEXEDPOLYGONALFACE((386,536,545));
+#1551=IFCINDEXEDPOLYGONALFACE((545,532,534));
+#1552=IFCINDEXEDPOLYGONALFACE((384,545,544));
+#1553=IFCINDEXEDPOLYGONALFACE((545,384,386));
+#1554=IFCINDEXEDPOLYGONALFACE((388,547,380));
+#1555=IFCINDEXEDPOLYGONALFACE((544,548,384));
+#1556=IFCINDEXEDPOLYGONALFACE((544,448,548));
+#1557=IFCINDEXEDPOLYGONALFACE((548,388,383));
+#1558=IFCINDEXEDPOLYGONALFACE((548,463,388));
+#1559=IFCINDEXEDPOLYGONALFACE((548,447,463));
+#1560=IFCINDEXEDPOLYGONALFACE((447,548,448));
+#1561=IFCINDEXEDPOLYGONALFACE((383,384,548));
+#1562=IFCINDEXEDPOLYGONALFACE((463,450,462));
+#1563=IFCINDEXEDPOLYGONALFACE((463,447,450));
+#1564=IFCINDEXEDPOLYGONALFACE((461,470,468));
+#1565=IFCINDEXEDPOLYGONALFACE((468,549,466));
+#1566=IFCINDEXEDPOLYGONALFACE((379,539,378));
+#1567=IFCINDEXEDPOLYGONALFACE((385,381,378));
+#1568=IFCINDEXEDPOLYGONALFACE((539,385,378));
+#1569=IFCINDEXEDPOLYGONALFACE((379,549,539));
+#1570=IFCINDEXEDPOLYGONALFACE((538,549,468));
+#1571=IFCINDEXEDPOLYGONALFACE((549,538,539));
+#1572=IFCINDEXEDPOLYGONALFACE((466,464,468));
+#1573=IFCINDEXEDPOLYGONALFACE((517,520,521));
+#1574=IFCINDEXEDPOLYGONALFACE((550,456,454));
+#1575=IFCINDEXEDPOLYGONALFACE((454,452,551));
+#1576=IFCINDEXEDPOLYGONALFACE((551,552,454));
+#1577=IFCINDEXEDPOLYGONALFACE((553,552,551));
+#1578=IFCINDEXEDPOLYGONALFACE((554,550,454));
+#1579=IFCINDEXEDPOLYGONALFACE((454,552,554));
+#1580=IFCINDEXEDPOLYGONALFACE((552,553,555));
+#1581=IFCINDEXEDPOLYGONALFACE((502,553,501));
+#1582=IFCINDEXEDPOLYGONALFACE((502,555,553));
+#1583=IFCINDEXEDPOLYGONALFACE((556,552,555));
+#1584=IFCINDEXEDPOLYGONALFACE((556,554,552));
+#1585=IFCINDEXEDPOLYGONALFACE((456,550,443));
+#1586=IFCINDEXEDPOLYGONALFACE((550,556,557));
+#1587=IFCINDEXEDPOLYGONALFACE((554,556,550));
+#1588=IFCINDEXEDPOLYGONALFACE((555,502,508));
+#1589=IFCINDEXEDPOLYGONALFACE((507,502,500));
+#1590=IFCINDEXEDPOLYGONALFACE((502,507,508));
+#1591=IFCINDEXEDPOLYGONALFACE((556,444,557));
+#1592=IFCINDEXEDPOLYGONALFACE((557,444,443));
+#1593=IFCINDEXEDPOLYGONALFACE((429,444,556));
+#1594=IFCINDEXEDPOLYGONALFACE((509,429,556));
+#1595=IFCINDEXEDPOLYGONALFACE((509,556,555));
+#1596=IFCINDEXEDPOLYGONALFACE((509,555,508));
+#1597=IFCINDEXEDPOLYGONALFACE((430,429,509));
+#1598=IFCINDEXEDPOLYGONALFACE((429,445,444));
+#1599=IFCINDEXEDPOLYGONALFACE((456,443,424));
+#1600=IFCINDEXEDPOLYGONALFACE((437,438,434));
+#1601=IFCINDEXEDPOLYGONALFACE((467,466,549));
+#1602=IFCINDEXEDPOLYGONALFACE((467,547,388));
+#1603=IFCINDEXEDPOLYGONALFACE((380,549,379));
+#1604=IFCINDEXEDPOLYGONALFACE((467,549,380));
+#1605=IFCINDEXEDPOLYGONALFACE((467,380,547));
+#1606=IFCINDEXEDPOLYGONALFACE((522,514,513));
+#1607=IFCINDEXEDPOLYGONALFACE((550,557,443));
+#1608=IFCINDEXEDPOLYGONALFACE((516,520,517));
+#1609=IFCINDEXEDPOLYGONALFACE((543,528,518));
+#1610=IFCINDEXEDPOLYGONALFACE((543,518,558));
+#1611=IFCINDEXEDPOLYGONALFACE((546,543,558));
+#1612=IFCINDEXEDPOLYGONALFACE((559,544,546));
+#1613=IFCINDEXEDPOLYGONALFACE((448,544,559));
+#1614=IFCINDEXEDPOLYGONALFACE((448,559,446));
+#1615=IFCINDEXEDPOLYGONALFACE((446,559,452));
+#1616=IFCINDEXEDPOLYGONALFACE((452,559,551));
+#1617=IFCINDEXEDPOLYGONALFACE((546,560,559));
+#1618=IFCINDEXEDPOLYGONALFACE((559,561,551));
+#1619=IFCINDEXEDPOLYGONALFACE((559,560,561));
+#1620=IFCINDEXEDPOLYGONALFACE((561,553,551));
+#1621=IFCINDEXEDPOLYGONALFACE((560,503,501));
+#1622=IFCINDEXEDPOLYGONALFACE((561,501,553));
+#1623=IFCINDEXEDPOLYGONALFACE((560,501,561));
+#1624=IFCINDEXEDPOLYGONALFACE((546,558,560));
+#1625=IFCINDEXEDPOLYGONALFACE((503,512,372));
+#1626=IFCINDEXEDPOLYGONALFACE((512,503,562));
+#1627=IFCINDEXEDPOLYGONALFACE((518,512,562));
+#1628=IFCINDEXEDPOLYGONALFACE((518,562,558));
+#1629=IFCINDEXEDPOLYGONALFACE((512,518,517));
+#1630=IFCINDEXEDPOLYGONALFACE((558,503,560));
+#1631=IFCINDEXEDPOLYGONALFACE((558,562,503));
+#1632=IFCINDEXEDPOLYGONALFACE((112,115,147));
+#1633=IFCINDEXEDPOLYGONALFACE((129,342,130));
+#1634=IFCINDEXEDPOLYGONALFACE((130,343,338));
+#1635=IFCINDEXEDPOLYGONALFACE((342,343,130));
+#1636=IFCINDEXEDPOLYGONALFACE((342,110,343));
+#1637=IFCCARTESIANPOINTLIST3D(((-234.259872436523,26.0206451416016,1268.17810058594),(-100.622589111328,89.1703338623047,1358.38659667969),(-174.998931884766,84.4214172363281,1289.44897460938),(-196.189651489258,26.9298496246338,1213.30908203125),(-72.8485260009766,73.3184814453125,1323.95959472656),(-345.774108886719,-49.3664436340332,1298.96325683594),(-326.359466552734,-37.9339141845703,1239.11169433594),(-455.8876953125,-59.9907608032227,1240.53430175781),(-361.961212158203,-40.2571678161621,1210.66223144531),(-294.290893554688,-52.8562927246094,1164.40112304688),(-360.114013671875,-77.8896331787109,1158.00598144531),(-420.540679931641,-112.721755981445,1106.28112792969),(-174.020874023438,182.901596069336,125.607543945312),(-30.1420288085938,192.876953125,334.829833984375),(-32.1650161743164,144.114334106445,287.031280517578),(-376.397827148438,-63.5164642333984,1395.50573730469),(-343.002655029297,-18.6739673614502,1456.07995605469),(-384.862976074219,-40.3055877685547,1492.75451660156),(-429.320373535156,-51.7892723083496,1412.16223144531),(-249.589797973633,23.7823734283447,1320.48657226562),(-436.293060302734,-28.2356185913086,1331.52783203125),(-389.614166259766,-99.2635650634766,1263.74230957031),(-352.909545898438,-88.9738235473633,1197.65832519531),(-127.19367980957,187.262359619141,91.7172393798828),(0.550186991691589,184.988464355469,271.917114257812),(-109.129829406738,208.982406616211,214.271453857422),(-53.3709259033203,292.429107666016,102.230285644531),(-109.36164855957,279.544677734375,10.0952625274658),(-58.5215110778809,218.701721191406,111.573677062988),(-1.16115438938141,306.455474853516,97.5913391113281),(-42.5377960205078,333.894561767578,13.5360069274902),(-77.3127822875977,341.250793457031,32.6841278076172),(-182.928314208984,244.205703735352,47.796760559082),(-196.406845092773,277.48388671875,11.2913303375244),(-260.438049316406,231.842819213867,8.3146333694458),(48.1291275024414,339.953186035156,44.5714645385742),(-281.729095458984,187.189514160156,8.92827892303467),(-130.312393188477,136.459121704102,138.386489868164),(-323.713745117188,-86.0534439086914,1288.24206542969),(172.426818847656,279.070587158203,6.77609443664551),(130.639266967773,244.967727661133,9.28795719146729),(57.8884468078613,316.890777587891,88.9064865112305),(-205.648010253906,56.7607955932617,148.7763671875),(-244.74821472168,-38.6621055603027,54.3230133056641),(-172.18278503418,29.0755653381348,256.801666259766),(-354.347503662109,-86.4489898681641,1363.07922363281),(-289.320922851562,-91.2859344482422,1351.06079101562),(-262.40087890625,-60.1678237915039,1390.63830566406),(-359.798614501953,-168.317199707031,1270.29931640625),(-371.118041992188,-201.981323242188,1146.25),(-162.621124267578,58.8279685974121,127.04443359375),(-105.6953125,35.7283096313477,349.046142578125),(-100.901763916016,88.7468490600586,283.423614501953),(-259.459259033203,-151.988891601562,1597.00500488281),(-325.426116943359,-79.227409362793,1552.19775390625),(-354.647064208984,-109.775039672852,1577.22155761719),(105.394004821777,47.0450134277344,1218.7939453125),(143.695266723633,95.4449157714844,1310.88317871094),(53.1794776916504,88.2259674072266,1411.14013671875),(-93.6701202392578,-102.186378479004,1350.81909179688),(-206.495086669922,-108.140785217285,1309.5009765625),(-166.171630859375,-129.207290649414,1375.42114257812),(8.2303991317749,-45.5022201538086,1443.43676757812),(67.4000854492188,-52.1921157836914,1453.99145507812),(32.3846015930176,-81.7621688842773,1425.6611328125),(-65.1914978027344,-40.7295989990234,1440.2666015625),(-27.6138095855713,-22.5396099090576,1451.76342773438),(-22.4503383636475,-90.6965103149414,1425.47521972656),(45.200626373291,-108.750053405762,1411.40100097656),(-253.74870300293,-173.990127563477,2.80772709846497),(-188.811676025391,-197.434387207031,1.62604331970215),(-141.394714355469,-154.38639831543,84.325080871582),(-144.390380859375,-206.764877319336,46.6824226379395),(-67.0683288574219,-231.445053100586,75.1596755981445),(-106.494743347168,-205.124084472656,227.876815795898),(-69.1273651123047,-227.695098876953,210.034652709961),(-132.834976196289,-221.483734130859,2.70664954185486),(-59.9483108520508,-251.556686401367,0.433635205030441),(-238.044021606445,-158.393890380859,82.6289749145508),(-145.278305053711,-122.224197387695,236.698623657227),(-168.42936706543,-143.536834716797,230.725936889648),(-111.595741271973,-190.565505981445,621.587341308594),(-111.56485748291,-168.792770385742,1014.46014404297),(-161.771865844727,-131.279891967773,781.861999511719),(-82.9896545410156,-235.451568603516,508.337219238281),(-59.3738021850586,-218.05924987793,984.255981445312),(-109.838088989258,-75.3973236083984,1174.73388671875),(-58.3983879089355,-137.72998046875,1348.484375),(169.098037719727,2.49960660934448,1199.70971679688),(268.213012695312,28.7850322723389,1155.13781738281),(212.121215820312,62.7963714599609,1281.23571777344),(211.473937988281,102.82096862793,1365.90966796875),(221.213317871094,67.7997436523438,1390.20971679688),(149.427856445312,75.5331573486328,1434.66577148438),(-133.805435180664,-96.4271621704102,1434.79077148438),(-296.830108642578,-273.018890380859,1347.32861328125),(-314.549163818359,-224.977249145508,1438.04370117188),(-338.938873291016,-283.220733642578,1426.24877929688),(234.905609130859,18.5421848297119,1443.41723632812),(134.689010620117,-7.08544301986694,1468.84875488281),(86.4454803466797,55.6466102600098,1456.04138183594),(170.499710083008,196.93928527832,367.807922363281),(223.427978515625,47.2690734863281,505.998291015625),(144.529571533203,132.24153137207,480.139312744141),(-4.70727396011353,-171.973083496094,1254.0078125),(57.016429901123,77.0934143066406,557.01171875),(114.453987121582,47.4405632019043,725.84326171875),(61.3384208679199,42.6156234741211,595.980163574219),(130.760726928711,59.0489387512207,1004.70300292969),(56.1174049377441,61.3588256835938,1078.35754394531),(26.7942752838135,29.3527793884277,1021.92547607422),(-172.117828369141,-293.532836914062,1774.51904296875),(-304.544769287109,-234.783432006836,1692.11389160156),(-185.501373291016,-328.179321289062,1730.30700683594),(-87.196403503418,-337.01123046875,1739.81225585938),(-198.697662353516,-296.360534667969,1701.09802246094),(105.671966552734,29.1725769042969,917.033935546875),(87.0264358520508,-22.3563709259033,1459.66491699219),(76.4012222290039,29.8333110809326,1467.81311035156),(282.479675292969,39.4475975036621,5.19572496414185),(266.129943847656,60.9157752990723,31.7507934570312),(222.847778320312,98.9792251586914,9.44344806671143),(209.856155395508,-55.9840049743652,111.861015319824),(194.558685302734,-8.1816291809082,83.5947036743164),(192.744766235352,-30.3785724639893,1010.16217041016),(190.087020874023,-58.6640357971191,1051.10400390625),(214.105239868164,-109.607299804688,1119.47082519531),(179.220672607422,-46.2212982177734,1139.26123046875),(169.943771362305,-14.1302680969238,1103.96520996094),(100.925979614258,33.8412933349609,1185.6650390625),(209.844131469727,-79.9096145629883,1174.33752441406),(147.470733642578,-146.801666259766,1353.03466796875),(148.167739868164,-159.253921508789,1260.08312988281),(71.6631164550781,-188.811111450195,1225.95178222656),(189.147521972656,26.4641056060791,939.224365234375),(221.66455078125,-35.0480766296387,969.51953125),(236.265380859375,-77.077262878418,1407.22290039062),(182.103866577148,-79.6638793945312,1352.71252441406),(262.556671142578,-77.5801849365234,1323.5341796875),(240.757186889648,-52.3165016174316,1378.26892089844),(341.992980957031,-4.5083475112915,1283.97143554688),(368.147918701172,-18.4431190490723,1164.26440429688),(280.039947509766,23.3627166748047,1210.63989257812),(-64.0344543457031,-289.496734619141,1717.87084960938),(-195.189376831055,-270.101776123047,1744.28186035156),(-79.1865615844727,-270.032684326172,1757.63195800781),(-97.2441253662109,-273.117431640625,1768.56274414062),(-44.3893508911133,-212.602279663086,1.46154367923737),(-40.7168769836426,-180.231002807617,97.5091171264648),(-53.893611907959,-179.648147583008,295.200317382812),(-54.0461082458496,-212.207458496094,581.433898925781),(14.6313772201538,-208.832733154297,245.684219360352),(-6.76602649688721,-189.713928222656,124.969528198242),(49.1640930175781,-242.114410400391,187.770812988281),(61.3618621826172,-249.659164428711,318.480438232422),(148.024612426758,21.3876552581787,832.409240722656),(200.182968139648,9.91830348968506,866.363342285156),(0.506551861763,-227.3544921875,14.836483001709),(50.2909622192383,-209.27067565918,20.0473861694336),(279.526977539062,-96.9463882446289,1058.3662109375),(266.403381347656,-7.66716909408569,1100.16198730469),(36.8900566101074,-197.712738037109,199.115936279297),(78.588737487793,-183.978469848633,216.088439941406),(78.0460357666016,-208.343688964844,276.298858642578),(52.7577018737793,-166.980392456055,154.516387939453),(56.6364250183105,-176.391799926758,2.56371259689331),(250.200347900391,-60.670539855957,885.808044433594),(241.896118164062,17.7724590301514,594.098205566406),(228.815887451172,-78.8302917480469,654.730285644531),(219.107696533203,-152.897079467773,822.713989257812),(230.230560302734,-104.30167388916,1017.83447265625),(-69.2549896240234,-322.792938232422,1743.02453613281),(113.45849609375,-221.281433105469,3.2618362903595),(185.49674987793,-61.1086311340332,1258.38232421875),(212.140441894531,-112.815856933594,1266.68200683594),(212.231842041016,-150.177886962891,52.884090423584),(193.43310546875,-144.418380737305,6.11850070953369),(241.940994262695,-107.766044616699,2.80753087997437),(135.199066162109,-151.250183105469,48.6296844482422),(107.207931518555,-147.557800292969,237.379272460938),(96.7631912231445,-181.811370849609,330.823852539062),(125.37393951416,-234.321640014648,272.505432128906),(58.7994117736816,-215.066452026367,1160.4033203125),(127.874649047852,-206.078918457031,1266.04150390625),(154.300064086914,-157.079208374023,260.234039306641),(127.502777099609,-215.131774902344,330.523193359375),(34.1889533996582,-217.497528076172,378.504669189453),(34.7865982055664,-243.546188354492,594.079650878906),(12.0976400375366,-284.996856689453,461.390014648438),(-15.8745355606079,-292.565551757812,294.191802978516),(27.2890815734863,-280.032379150391,679.055358886719),(67.6122741699219,-245.154312133789,1034.18212890625),(192.588134765625,-109.331016540527,139.856369018555),(214.282150268555,-65.5875244140625,4.3946967124939),(485.333129882812,-101.890800476074,1143.18615722656),(364.089996337891,-164.960968017578,1112.13928222656),(436.542053222656,-113.766792297363,1073.89306640625),(363.7236328125,-175.23046875,1209.75769042969),(347.211181640625,-154.081100463867,1253.51989746094),(403.778869628906,-139.319763183594,1255.37756347656),(481.695831298828,-56.3661956787109,1070.92749023438),(445.47021484375,-92.0810852050781,1223.81237792969),(358.431976318359,-60.4159889221191,1287.80786132812),(408.411254882812,-56.1850814819336,1259.23962402344),(397.688201904297,-116.629638671875,1303.21032714844),(315.505462646484,-78.9872436523438,1315.50268554688),(276.939117431641,-119.453987121582,1321.89636230469),(188.465042114258,-304.243865966797,397.150054931641),(160.809936523438,-262.796142578125,805.354858398438),(203.332580566406,-246.257904052734,773.599060058594),(259.857757568359,-298.334381103516,426.012054443359),(147.664993286133,-238.491973876953,1008.68743896484),(192.343307495117,-157.946182250977,659.137756347656),(185.160919189453,-201.867858886719,720.268798828125),(186.643859863281,-264.314544677734,377.798126220703),(205.294372558594,-237.602523803711,407.926666259766),(151.504852294922,-39.1747512817383,280.009460449219),(177.406814575195,-102.663719177246,326.255584716797),(202.127624511719,-69.6266632080078,326.591583251953),(193.145324707031,33.1915512084961,263.857574462891),(164.464447021484,-163.047897338867,357.746185302734),(247.165740966797,-133.986694335938,363.527313232422),(227.365463256836,-193.647537231445,376.384826660156),(283.534729003906,-242.950988769531,419.483917236328),(414.337524414062,-74.2443008422852,1167.27038574219),(369.537170410156,-85.9891204833984,1051.84948730469),(404.827026367188,-72.8606414794922,1112.72814941406),(407.421478271484,-96.4484329223633,1130.64721679688),(473.46533203125,-86.1440963745117,1012.70166015625),(437.606140136719,-65.8767929077148,965.449462890625),(236.806304931641,-225.523635864258,481.408416748047),(297.629730224609,-120.587661743164,1290.78527832031),(323.314422607422,-176.81721496582,1192.16748046875),(260.157592773438,-135.264190673828,1259.5029296875),(336.322357177734,-122.433921813965,1037.03234863281),(364.126342773438,-130.807113647461,935.928405761719),(309.030181884766,-135.372375488281,950.143920898438),(278.101501464844,-122.49227142334,1091.39782714844),(330.940185546875,-156.066329956055,1084.29479980469),(129.381759643555,-236.528305053711,518.649047851562),(132.750106811523,-234.21305847168,653.487670898438),(120.984184265137,-254.061706542969,931.172180175781),(332.416015625,-88.7421646118164,1147.03820800781),(245.219161987305,-122.538154602051,1185.7099609375),(131.642654418945,-291.287719726562,427.089477539062),(116.164497375488,-269.177581787109,333.953704833984),(100.750999450684,-365.325897216797,355.390106201172),(92.9562530517578,-334.828826904297,541.520385742188),(345.203125,-105.10066986084,1027.06286621094),(-0.282331973314285,-245.40217590332,850.967407226562),(-9.91721343994141,-234.005096435547,1051.09155273438),(192.275253295898,-196.170043945312,1228.09899902344),(203.226577758789,-139.625366210938,1195.75073242188),(181.33903503418,-172.821807861328,1214.49816894531),(255.358200073242,-158.387359619141,1142.03308105469),(245.205429077148,-141.107284545898,1008.36901855469),(288.378143310547,-177.676177978516,1005.04571533203),(168.612915039062,-191.296936035156,1035.94677734375),(80.7072219848633,-301.066925048828,828.076171875),(96.1312789916992,-239.572052001953,1189.82104492188),(111.940330505371,-278.002319335938,928.733032226562),(122.164833068848,-209.147583007812,1169.44458007812),(336.85986328125,-42.5956192016602,1048.13122558594),(200.684204101562,-148.02458190918,1075.6298828125),(137.877807617188,-244.42985534668,1186.93579101562),(199.932220458984,-8.53413581848145,421.528869628906),(194.687835693359,-48.5160675048828,45.7340278625488),(159.225692749023,-96.2861099243164,169.432495117188),(37.2209625244141,72.1331558227539,712.920104980469),(91.0833969116211,80.5275115966797,569.639038085938),(129.217086791992,84.2431335449219,639.683471679688),(94.2891540527344,144.876831054688,446.311126708984),(-5.78799533843994,86.9931869506836,642.360778808594),(-7.31255722045898,50.6945991516113,702.014709472656),(-38.825927734375,25.3310928344727,846.389221191406),(-45.0165061950684,67.8793563842773,767.40966796875),(-145.277099609375,-8.27912998199463,748.223449707031),(-192.577621459961,-0.633410274982452,375.178894042969),(-129.685485839844,-33.8545417785645,970.439880371094),(-107.642707824707,-20.1257991790771,1246.58081054688),(-44.2391204833984,14.8375968933105,1181.673828125),(-91.1340255737305,-29.4557914733887,1151.98876953125),(-191.03337097168,-127.531745910645,506.393707275391),(-162.196136474609,-66.7209243774414,633.170349121094),(-176.999206542969,-50.8013381958008,248.601715087891),(-230.035675048828,-133.382827758789,112.767387390137),(-246.184555053711,152.172241210938,7.54552841186523),(-14.1170129776001,86.548942565918,372.274261474609),(167.21533203125,72.3524017333984,589.4775390625),(136.8125,269.382659912109,191.416610717773),(71.5297470092773,287.880767822266,216.711898803711),(74.540397644043,370.947418212891,119.111824035645),(133.028625488281,327.585021972656,100.417167663574),(148.794662475586,269.060699462891,104.780456542969),(169.967681884766,109.48486328125,250.85823059082),(189.553146362305,168.804489135742,228.852828979492),(185.619110107422,59.0821151733398,88.5192642211914),(221.865356445312,162.241439819336,24.1779232025146),(187.571685791016,-50.6560325622559,204.6435546875),(211.225204467773,82.2998962402344,122.356285095215),(155.21842956543,86.5802383422852,236.424011230469),(189.487121582031,309.881408691406,45.5172882080078),(210.763137817383,239.834243774414,9.35835647583008),(169.113067626953,204.443557739258,8.62976837158203),(149.133163452148,215.826812744141,83.5866546630859),(170.582443237305,148.817901611328,53.1442337036133),(-300.863861083984,-157.656265258789,35.9106750488281),(86.1821212768555,300.579681396484,8.44492149353027),(-404.658203125,-120.59839630127,1496.64660644531),(-373.734344482422,-185.118179321289,1552.60070800781),(-277.911987304688,-219.467437744141,1570.08642578125),(-270.670257568359,-140.886962890625,1558.55395507812),(-307.949645996094,-179.936721801758,1524.38720703125),(-312.341827392578,-265.590270996094,1502.47021484375),(-343.112060546875,-190.961761474609,1396.91052246094),(-257.206237792969,-165.078460693359,1398.27990722656),(-169.326187133789,-83.5106811523438,1468.94580078125),(-45.2263603210449,12.5155572891235,1453.42370605469),(-163.127563476562,50.7695579528809,1443.33801269531),(-97.1828384399414,42.8138046264648,1425.12939453125),(-176.406143188477,-33.1263465881348,1480.37634277344),(-224.613388061523,-1.62007975578308,1429.32214355469),(-261.904083251953,-65.6184997558594,1519.5126953125),(-302.266204833984,-20.0826473236084,1409.99060058594),(-194.050537109375,44.9293060302734,1370.322265625),(-330.362335205078,-185.16618347168,1320.09375),(-302.137329101562,-143.216705322266,1326.15930175781),(-380.224884033203,-178.806503295898,1319.92529296875),(-320.069366455078,-280.262023925781,1310.17724609375),(-325.509002685547,-264.641235351562,1251.47583007812),(-306.571380615234,-236.216003417969,1208.86840820312),(-348.646392822266,-193.627899169922,1128.93627929688),(-322.764282226562,-122.944648742676,1206.00463867188),(-227.334060668945,-100.000473022461,1237.63952636719),(-150.030471801758,-71.1294479370117,1249.9541015625),(-128.373641967773,21.5559558868408,1239.54614257812),(-109.857025146484,-62.3633460998535,1306.44323730469),(-5.45178365707397,34.3603973388672,1208.82348632812),(15.6776657104492,86.8136444091797,1391.94812011719),(-37.118968963623,61.9902839660645,1429.99987792969),(104.297897338867,26.6234397888184,1074.28002929688),(103.164924621582,26.3919353485107,1127.47253417969),(57.7263488769531,36.3444366455078,1140.66833496094),(-71.4559936523438,14.3564548492432,840.276000976562),(-76.6244735717773,-18.6751823425293,1019.06353759766),(-65.8723754882812,23.820764541626,1032.32604980469),(75.5481338500977,88.368896484375,777.67236328125),(-32.2922782897949,18.0561809539795,942.27783203125),(95.6580810546875,81.6739349365234,886.651245117188),(15.7327737808228,57.837272644043,974.274597167969),(-201.080551147461,101.168640136719,98.570686340332),(-289.260803222656,-104.399887084961,11.3370065689087),(-297.5048828125,38.9086532592773,1373.87524414062),(-111.359497070312,-34.5642509460449,783.250732421875),(-98.4062347412109,10.7842063903809,698.300109863281),(-109.949310302734,-14.509256362915,551.925598144531),(-51.639347076416,18.9487743377686,658.59619140625),(-78.1960754394531,46.6424751281738,499.489807128906),(-43.3707008361816,57.8422889709473,425.090484619141),(-195.434448242188,-88.1886901855469,88.7719879150391),(-256.575958251953,106.748611450195,6.2224268913269),(-282.607940673828,-5.43050765991211,4.25207567214966),(-53.7902679443359,84.2270812988281,278.401153564453),(-353.248901367188,-101.966873168945,1493.28869628906),(-196.578109741211,88.9360427856445,1343.17919921875),(-151.250717163086,20.8457908630371,417.927612304688),(-304.972930908203,72.6408538818359,11.6079778671265),(-329.454376220703,15.0221586227417,5.15277338027954),(10.3479175567627,356.6474609375,97.2546234130859),(-424.256958007812,-105.741661071777,1317.17004394531),(-414.061920166016,-125.973098754883,1359.53125),(-113.575721740723,125.182006835938,1524.3681640625),(-86.1567230224609,115.965057373047,1492.20886230469),(-117.240768432617,113.329612731934,1541.01147460938),(-75.0297775268555,103.098014831543,1496.85998535156),(-100.981216430664,68.8193511962891,1516.89892578125),(-116.712928771973,81.3248825073242,1534.5673828125),(40.1087112426758,224.180389404297,1554.21228027344),(55.2424850463867,228.096069335938,1573.20739746094),(52.0294380187988,220.650115966797,1583.29736328125),(27.6150588989258,224.404037475586,1541.00561523438),(20.6359710693359,209.799728393555,1528.2841796875),(27.3676090240479,215.940902709961,1560.34216308594),(5.76483488082886,192.338027954102,1526.03015136719),(37.1759567260742,212.91032409668,1535.86608886719),(29.1523342132568,191.362365722656,1525.49096679688),(28.6984882354736,204.555282592773,1523.72692871094),(56.4874649047852,209.349868774414,1597.42041015625),(-14.4212265014648,-23.0675487518311,1458.56921386719),(-29.7597770690918,-17.0394020080566,1496.51965332031),(-70.9299850463867,-17.2349987030029,1486.20959472656),(-73.7109985351562,-27.5204887390137,1502.36779785156),(-30.5149631500244,-23.5454502105713,1548.98669433594),(1.83288025856018,-13.3799915313721,1521.00183105469),(37.8389167785645,-32.7231025695801,1461.29956054688),(10.6368379592896,5.48521661758423,1557.26794433594),(30.5814113616943,1.57328248023987,1526.83947753906),(33.9724655151367,7.76734781265259,1555.83056640625),(70.7004013061523,-2.17202520370483,1467.79528808594),(78.6741256713867,69.1588897705078,1492.65014648438),(78.4659042358398,92.3239822387695,1484.82751464844),(75.4610977172852,86.7758636474609,1461.76843261719),(61.4135665893555,72.1802597045898,1473.77014160156),(70.9747772216797,96.4617004394531,1495.83312988281),(53.6593399047852,51.6954727172852,1480.53002929688),(69.4760818481445,41.8450355529785,1492.98400878906),(76.0567169189453,42.6811103820801,1508.2802734375),(73.8561325073242,69.4737091064453,1515.13977050781),(84.6694717407227,101.128875732422,1540.47277832031),(85.3650665283203,107.239677429199,1568.13330078125),(77.8931427001953,75.1578598022461,1591.20690917969),(33.0704002380371,70.1776885986328,1646.32592773438),(33.8830299377441,51.5049438476562,1627.7587890625),(40.7202606201172,29.0004825592041,1626.66320800781),(53.6601524353027,61.0881690979004,1464.50427246094),(59.0355911254883,71.6298294067383,1472.91589355469),(39.7420387268066,70.1308975219727,1451.48754882812),(59.6833724975586,66.7699966430664,1611.15856933594),(69.2930221557617,37.436466217041,1603.04833984375),(38.6539077758789,14.9436511993408,1550.74841308594),(25.1376686096191,40.7397270202637,1641.21105957031),(-5.45751905441284,36.7394676208496,1653.55419921875),(18.1719779968262,22.5732383728027,1634.83386230469),(-8.5576057434082,63.4988441467285,1662.55908203125),(-65.9412689208984,23.3862171173096,1625.67163085938),(-47.3745193481445,18.7052669525146,1641.37646484375),(-10.1549577713013,4.91736841201782,1615.02917480469),(-103.409973144531,55.2447814941406,1615.44970703125),(-119.151214599609,74.6872634887695,1623.66650390625),(-126.155563354492,64.5326309204102,1618.49731445312),(-111.926651000977,55.8391647338867,1601.84106445312),(-74.5309600830078,35.6530990600586,1620.134765625),(-95.9652404785156,34.5729103088379,1599.60876464844),(-116.207382202148,51.1680450439453,1580.06506347656),(-49.3067893981934,16.6121482849121,1607.46398925781),(-88.136116027832,22.3731842041016,1579.61120605469),(-113.943626403809,46.2731628417969,1562.29650878906),(-92.5713272094727,36.8668937683105,1567.96252441406),(-115.383071899414,7.92506122589111,1538.07019042969),(-91.192138671875,9.4000883102417,1552.15454101562),(-67.2494049072266,0.555085420608521,1579.39318847656),(-25.9960899353027,20.0776863098145,1644.13879394531),(-62.8531837463379,52.8868827819824,1656.671875),(-95.7298736572266,72.4900131225586,1653.73791503906),(-102.556533813477,59.7004547119141,1639.59118652344),(36.7050819396973,181.189712524414,1586.69104003906),(56.0937271118164,168.654113769531,1588.27905273438),(28.3847389221191,187.388137817383,1559.20568847656),(48.278564453125,163.58805847168,1613.85107421875),(70.4153900146484,131.734756469727,1594.84875488281),(50.0555686950684,125.047988891602,1636.34692382812),(-22.0689868927002,182.913635253906,1629.01123046875),(11.0097780227661,157.756408691406,1647.26171875),(-36.2250595092773,148.731796264648,1655.99987792969),(39.4887962341309,107.31908416748,1646.9609375),(-24.6603813171387,103.131538391113,1667.595703125),(91.6101226806641,70.2689590454102,1563.38977050781),(81.4488372802734,48.0304908752441,1569.1357421875),(70.3326721191406,102.810134887695,1601.3212890625),(72.9910049438477,128.569854736328,1557.46740722656),(89.2945251464844,156.26123046875,1597.82055664062),(78.739372253418,151.29411315918,1603.04052734375),(69.0121688842773,186.269470214844,1603.41333007812),(89.9434204101562,177.107223510742,1597.65087890625),(85.4082565307617,193.738403320312,1601.94750976562),(81.8152465820312,198.763748168945,1589.65185546875),(67.6442947387695,220.045913696289,1593.25),(88.9900131225586,174.113494873047,1585.31018066406),(94.1403045654297,144.667526245117,1597.46496582031),(95.1149444580078,154.339080810547,1587.76696777344),(-15.900899887085,-0.691527128219604,1578.92895507812),(20.5557975769043,4.02943420410156,1598.52685546875),(58.7821578979492,21.0654945373535,1575.85229492188),(63.8384246826172,31.5590400695801,1548.03344726562),(43.1311569213867,21.1730651855469,1526.13916015625),(-99.3362121582031,5.88167762756348,1530.93798828125),(78.2768402099609,122.414169311523,1541.7421875),(87.981071472168,142.011825561523,1578.30712890625),(92.2791748046875,173.672668457031,1552.27185058594),(96.0646057128906,139.523681640625,1556.39721679688),(96.1841583251953,167.597518920898,1539.00537109375),(99.31201171875,143.991088867188,1539.31384277344),(86.7889022827148,133.999969482422,1529.29833984375),(79.0168380737305,165.672454833984,1513.74719238281),(63.1840705871582,148.564270019531,1501.51110839844),(84.8034362792969,127.631164550781,1510.87744140625),(63.073371887207,127.283325195312,1490.67297363281),(53.7515182495117,104.988243103027,1485.16870117188),(62.3159370422363,68.2941513061523,1476.35620117188),(53.5460205078125,31.7911033630371,1500.64770507812),(63.9988594055176,43.2823829650879,1523.74719238281),(48.0260696411133,36.8978881835938,1523.22729492188),(78.4457092285156,101.313835144043,1529.45031738281),(87.712028503418,72.1794509887695,1531.53845214844),(82.9435348510742,150.257598876953,1530.06225585938),(84.0949325561523,172.855438232422,1532.66528320312),(-88.2408981323242,-5.67919301986694,1518.27795410156),(-93.551872253418,61.0295181274414,1499.48852539062),(-64.3972473144531,41.2935981750488,1474.70520019531),(-129.675582885742,115.201606750488,1574.70422363281),(-129.302963256836,130.147277832031,1579.95959472656),(-124.743827819824,111.590309143066,1599.02111816406),(-118.581703186035,137.487686157227,1544.07019042969),(-121.445251464844,109.26685333252,1557.3046875),(-130.845718383789,82.4912719726562,1559.37475585938),(-128.739303588867,86.3893280029297,1568.41345214844),(-137.022109985352,98.8915100097656,1586.59057617188),(-120.534721374512,94.0420150756836,1597.99853515625),(-128.516677856445,80.6572647094727,1611.14758300781),(-100.700874328613,52.9861488342285,1543.31884765625),(-100.370506286621,28.7165565490723,1523.56140136719),(-71.6600799560547,134.653198242188,1483.3564453125),(-47.6845359802246,107.31665802002,1461.91711425781),(-50.5442695617676,95.0276947021484,1479.14929199219),(-44.3628234863281,12.8352375030518,1470.51416015625),(-9.59514045715332,150.318466186523,1477.25354003906),(-31.1618804931641,141.470016479492,1466.62060546875),(-37.2183074951172,165.292907714844,1489.1923828125),(23.2937202453613,131.654922485352,1474.15405273438),(-0.149351730942726,135.890197753906,1455.57189941406),(10.8297147750854,106.050392150879,1457.66564941406),(-4.73065519332886,82.7443008422852,1473.79431152344),(-39.2331466674805,45.2719841003418,1467.78759765625),(43.6294136047363,77.7739639282227,1473.76306152344),(-5.54138803482056,70.0845108032227,1448.72180175781),(40.9911003112793,107.879447937012,1464.13415527344),(62.083683013916,89.6187515258789,1473.49768066406),(9.83149433135986,153.214431762695,1481.05261230469),(32.5141372680664,134.653503417969,1486.85607910156),(95.1986236572266,164.832229614258,1567.01672363281),(92.5120849609375,138.931411743164,1571.63415527344),(43.814323425293,170.36799621582,1503.23315429688),(69.8880233764648,188.286117553711,1526.00830078125),(33.6955795288086,149.046524047852,1500.26477050781),(55.507999420166,189.1328125,1528.93835449219),(45.8849182128906,190.43017578125,1514.07092285156),(69.0374221801758,171.354080200195,1572.42602539062),(47.8584289550781,202.034088134766,1557.28100585938),(56.2005386352539,216.99951171875,1559.67297363281),(49.6362800598145,186.180221557617,1547.59497070312),(67.570671081543,186.152053833008,1546.78051757812),(81.5277633666992,175.332061767578,1567.64343261719),(-1.24849903583527,161.438919067383,1498.05908203125),(5.40095329284668,184.023300170898,1526.90966796875),(24.1932201385498,166.245971679688,1516.11413574219),(-28.9154834747314,191.175491333008,1539.17370605469),(48.3305816650391,228.22917175293,1597.3486328125),(32.2272415161133,201.510314941406,1555.77709960938),(66.5850601196289,209.041641235352,1577.99890136719),(-81.1425170898438,113.942237854004,1655.3447265625),(-54.0028457641602,178.061798095703,1616.86779785156),(-79.9074401855469,141.797698974609,1627.0205078125),(-100.54029083252,151.466613769531,1614.01025390625),(-85.1366271972656,132.896179199219,1641.85217285156),(-107.957695007324,118.715560913086,1615.83630371094),(-112.850852966309,100.448387145996,1632.87072753906),(-88.2589721679688,88.7780227661133,1649.50280761719),(-57.0232620239258,166.791427612305,1515.27026367188),(-16.370569229126,197.421020507812,1589.68139648438),(-71.4700393676758,183.09260559082,1557.46264648438),(-76.7884292602539,179.287841796875,1576.80920410156),(-81.445671081543,152.388381958008,1510.34204101562)));
+#1638=IFCPOLYGONALFACESET(#1637,.F.,(#548,#549,#550,#551,#552,#553,#554,#555,#556,#557,#558,#559,#560,#561,#562,#563,#564,#565,#566,#567,#568,#569,#570,#571,#572,#573,#574,#575,#576,#577,#578,#579,#580,#581,#582,#583,#584,#585,#586,#587,#588,#589,#590,#591,#592,#593,#594,#595,#596,#597,#598,#599,#600,#601,#602,#603,#604,#605,#606,#607,#608,#609,#610,#611,#612,#613,#614,#615,#616,#617,#618,#619,#620,#621,#622,#623,#624,#625,#626,#627,#628,#629,#630,#631,#632,#633,#634,#635,#636,#637,#638,#639,#640,#641,#642,#643,#644,#645,#646,#647,#648,#649,#650,#651,#652,#653,#654,#655,#656,#657,#658,#659,#660,#661,#662,#663,#664,#665,#666,#667,#668,#669,#670,#671,#672,#673,#674,#675,#676,#677,#678,#679,#680,#681,#682,#683,#684,#685,#686,#687,#688,#689,#690,#691,#692,#693,#694,#695,#696,#697,#698,#699,#700,#701,#702,#703,#704,#705,#706,#707,#708,#709,#710,#711,#712,#713,#714,#715,#716,#717,#718,#719,#720,#721,#722,#723,#724,#725,#726,#727,#728,#729,#730,#731,#732,#733,#734,#735,#736,#737,#738,#739,#740,#741,#742,#743,#744,#745,#746,#747,#748,#749,#750,#751,#752,#753,#754,#755,#756,#757,#758,#759,#760,#761,#762,#763,#764,#765,#766,#767,#768,#769,#770,#771,#772,#773,#774,#775,#776,#777,#778,#779,#780,#781,#782,#783,#784,#785,#786,#787,#788,#789,#790,#791,#792,#793,#794,#795,#796,#797,#798,#799,#800,#801,#802,#803,#804,#805,#806,#807,#808,#809,#810,#811,#812,#813,#814,#815,#816,#817,#818,#819,#820,#821,#822,#823,#824,#825,#826,#827,#828,#829,#830,#831,#832,#833,#834,#835,#836,#837,#838,#839,#840,#841,#842,#843,#844,#845,#846,#847,#848,#849,#850,#851,#852,#853,#854,#855,#856,#857,#858,#859,#860,#861,#862,#863,#864,#865,#866,#867,#868,#869,#870,#871,#872,#873,#874,#875,#876,#877,#878,#879,#880,#881,#882,#883,#884,#885,#886,#887,#888,#889,#890,#891,#892,#893,#894,#895,#896,#897,#898,#899,#900,#901,#902,#903,#904,#905,#906,#907,#908,#909,#910,#911,#912,#913,#914,#915,#916,#917,#918,#919,#920,#921,#922,#923,#924,#925,#926,#927,#928,#929,#930,#931,#932,#933,#934,#935,#936,#937,#938,#939,#940,#941,#942,#943,#944,#945,#946,#947,#948,#949,#950,#951,#952,#953,#954,#955,#956,#957,#958,#959,#960,#961,#962,#963,#964,#965,#966,#967,#968,#969,#970,#971,#972,#973,#974,#975,#976,#977,#978,#979,#980,#981,#982,#983,#984,#985,#986,#987,#988,#989,#990,#991,#992,#993,#994,#995,#996,#997,#998,#999,#1000,#1001,#1002,#1003,#1004,#1005,#1006,#1007,#1008,#1009,#1010,#1011,#1012,#1013,#1014,#1015,#1016,#1017,#1018,#1019,#1020,#1021,#1022,#1023,#1024,#1025,#1026,#1027,#1028,#1029,#1030,#1031,#1032,#1033,#1034,#1035,#1036,#1037,#1038,#1039,#1040,#1041,#1042,#1043,#1044,#1045,#1046,#1047,#1048,#1049,#1050,#1051,#1052,#1053,#1054,#1055,#1056,#1057,#1058,#1059,#1060,#1061,#1062,#1063,#1064,#1065,#1066,#1067,#1068,#1069,#1070,#1071,#1072,#1073,#1074,#1075,#1076,#1077,#1078,#1079,#1080,#1081,#1082,#1083,#1084,#1085,#1086,#1087,#1088,#1089,#1090,#1091,#1092,#1093,#1094,#1095,#1096,#1097,#1098,#1099,#1100,#1101,#1102,#1103,#1104,#1105,#1106,#1107,#1108,#1109,#1110,#1111,#1112,#1113,#1114,#1115,#1116,#1117,#1118,#1119,#1120,#1121,#1122,#1123,#1124,#1125,#1126,#1127,#1128,#1129,#1130,#1131,#1132,#1133,#1134,#1135,#1136,#1137,#1138,#1139,#1140,#1141,#1142,#1143,#1144,#1145,#1146,#1147,#1148,#1149,#1150,#1151,#1152,#1153,#1154,#1155,#1156,#1157,#1158,#1159,#1160,#1161,#1162,#1163,#1164,#1165,#1166,#1167,#1168,#1169,#1170,#1171,#1172,#1173,#1174,#1175,#1176,#1177,#1178,#1179,#1180,#1181,#1182,#1183,#1184,#1185,#1186,#1187,#1188,#1189,#1190,#1191,#1192,#1193,#1194,#1195,#1196,#1197,#1198,#1199,#1200,#1201,#1202,#1203,#1204,#1205,#1206,#1207,#1208,#1209,#1210,#1211,#1212,#1213,#1214,#1215,#1216,#1217,#1218,#1219,#1220,#1221,#1222,#1223,#1224,#1225,#1226,#1227,#1228,#1229,#1230,#1231,#1232,#1233,#1234,#1235,#1236,#1237,#1238,#1239,#1240,#1241,#1242,#1243,#1244,#1245,#1246,#1247,#1248,#1249,#1250,#1251,#1252,#1253,#1254,#1255,#1256,#1257,#1258,#1259,#1260,#1261,#1262,#1263,#1264,#1265,#1266,#1267,#1268,#1269,#1270,#1271,#1272,#1273,#1274,#1275,#1276,#1277,#1278,#1279,#1280,#1281,#1282,#1283,#1284,#1285,#1286,#1287,#1288,#1289,#1290,#1291,#1292,#1293,#1294,#1295,#1296,#1297,#1298,#1299,#1300,#1301,#1302,#1303,#1304,#1305,#1306,#1307,#1308,#1309,#1310,#1311,#1312,#1313,#1314,#1315,#1316,#1317,#1318,#1319,#1320,#1321,#1322,#1323,#1324,#1325,#1326,#1327,#1328,#1329,#1330,#1331,#1332,#1333,#1334,#1335,#1336,#1337,#1338,#1339,#1340,#1341,#1342,#1343,#1344,#1345,#1346,#1347,#1348,#1349,#1350,#1351,#1352,#1353,#1354,#1355,#1356,#1357,#1358,#1359,#1360,#1361,#1362,#1363,#1364,#1365,#1366,#1367,#1368,#1369,#1370,#1371,#1372,#1373,#1374,#1375,#1376,#1377,#1378,#1379,#1380,#1381,#1382,#1383,#1384,#1385,#1386,#1387,#1388,#1389,#1390,#1391,#1392,#1393,#1394,#1395,#1396,#1397,#1398,#1399,#1400,#1401,#1402,#1403,#1404,#1405,#1406,#1407,#1408,#1409,#1410,#1411,#1412,#1413,#1414,#1415,#1416,#1417,#1418,#1419,#1420,#1421,#1422,#1423,#1424,#1425,#1426,#1427,#1428,#1429,#1430,#1431,#1432,#1433,#1434,#1435,#1436,#1437,#1438,#1439,#1440,#1441,#1442,#1443,#1444,#1445,#1446,#1447,#1448,#1449,#1450,#1451,#1452,#1453,#1454,#1455,#1456,#1457,#1458,#1459,#1460,#1461,#1462,#1463,#1464,#1465,#1466,#1467,#1468,#1469,#1470,#1471,#1472,#1473,#1474,#1475,#1476,#1477,#1478,#1479,#1480,#1481,#1482,#1483,#1484,#1485,#1486,#1487,#1488,#1489,#1490,#1491,#1492,#1493,#1494,#1495,#1496,#1497,#1498,#1499,#1500,#1501,#1502,#1503,#1504,#1505,#1506,#1507,#1508,#1509,#1510,#1511,#1512,#1513,#1514,#1515,#1516,#1517,#1518,#1519,#1520,#1521,#1522,#1523,#1524,#1525,#1526,#1527,#1528,#1529,#1530,#1531,#1532,#1533,#1534,#1535,#1536,#1537,#1538,#1539,#1540,#1541,#1542,#1543,#1544,#1545,#1546,#1547,#1548,#1549,#1550,#1551,#1552,#1553,#1554,#1555,#1556,#1557,#1558,#1559,#1560,#1561,#1562,#1563,#1564,#1565,#1566,#1567,#1568,#1569,#1570,#1571,#1572,#1573,#1574,#1575,#1576,#1577,#1578,#1579,#1580,#1581,#1582,#1583,#1584,#1585,#1586,#1587,#1588,#1589,#1590,#1591,#1592,#1593,#1594,#1595,#1596,#1597,#1598,#1599,#1600,#1601,#1602,#1603,#1604,#1605,#1606,#1607,#1608,#1609,#1610,#1611,#1612,#1613,#1614,#1615,#1616,#1617,#1618,#1619,#1620,#1621,#1622,#1623,#1624,#1625,#1626,#1627,#1628,#1629,#1630,#1631,#1632,#1633,#1634,#1635,#1636),$);
+#1639=IFCSHAPEREPRESENTATION(#15,'Body','Tessellation',(#1638));
+#1640=IFCCARTESIANPOINT((0.,0.,0.));
+#1641=IFCDIRECTION((1.,0.,0.));
+#1642=IFCDIRECTION((0.,0.,1.));
+#1643=IFCAXIS2PLACEMENT3D(#1640,#1642,#1641);
+#1644=IFCREPRESENTATIONMAP(#1643,#1639);
+ENDSEC;
+END-ISO-10303-21;
diff --git a/src/blenderbim/blenderbim/bim/data/pset/EPset_Drawing.ifc b/src/blenderbim/blenderbim/bim/data/pset/EPset_Drawing.ifc
index 5a2227b90e..13e2c4e629 100644
--- a/src/blenderbim/blenderbim/bim/data/pset/EPset_Drawing.ifc
+++ b/src/blenderbim/blenderbim/bim/data/pset/EPset_Drawing.ifc
@@ -5,7 +5,7 @@ FILE_NAME('EPset_Drawing.ifc','2020-01-01T00:00:00',(),(),'EPset_Drawing','EPset
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
-#1=IFCPROPERTYSETTEMPLATE('2JhNIvqZrFnAgxfhK0XVQX',$,'EPset_Drawing','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation',(#2,#3,#4,#5,#6,#7,#8,#9,#10,#11,#12,#13,#14,#15,#16,#17,#18,#19,#20,#21,#22));
+#1=IFCPROPERTYSETTEMPLATE('2JhNIvqZrFnAgxfhK0XVQX',$,'EPset_Drawing','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation',(#2,#3,#4,#5,#6,#7,#8,#9,#10,#11,#12,#13,#14,#15,#16,#17,#18,#19,#20,#21,#22,#23));
#2=IFCSIMPLEPROPERTYTEMPLATE('23JavTMk98ZxXhrUEnjAcf',$,'TargetView','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#3=IFCSIMPLEPROPERTYTEMPLATE('1yVWUt5H9DAOuu0OaMMLpe',$,'Scale','The scale of this drawing represented as a numerator and denominator, such as 1/100',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#4=IFCSIMPLEPROPERTYTEMPLATE('3gsuPBtU93b8f0gg1pjkq6',$,'HumanScale','The scale of this drawing in human readable format, such as 1:100',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
@@ -27,5 +27,6 @@ DATA;
#20=IFCSIMPLEPROPERTYTEMPLATE('0joEq0Rd10cxweEh0NeHT6',$,'JoinCriteria','Comma separated selection keys which determine what cut objects are to be joined.',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#21=IFCSIMPLEPROPERTYTEMPLATE('0nYMT3OSj5gArVniCWZRtv',$,'ShadingStyles','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#22=IFCSIMPLEPROPERTYTEMPLATE('3VWG22eZXBdQwdKlzMeVQH',$,'CurrentShadingStyle','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
+#23=IFCSIMPLEPROPERTYTEMPLATE('28mJ$GDxb7kBbKFH_rACMQ',$,'AngleDecimalPlaces','',.P_SINGLEVALUE.,'IfcInteger',$,$,$,$,$,.READWRITE.);
ENDSEC;
END-ISO-10303-21;
diff --git a/src/blenderbim/blenderbim/bim/export_ifc.py b/src/blenderbim/blenderbim/bim/export_ifc.py
index 77ed7eaa50..5efaefa4b1 100644
--- a/src/blenderbim/blenderbim/bim/export_ifc.py
+++ b/src/blenderbim/blenderbim/bim/export_ifc.py
@@ -27,6 +27,7 @@ import addon_utils
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.placement
+import ifcopenshell.util.unit
import blenderbim.tool as tool
import blenderbim.core.geometry
import blenderbim.core.aggregate
@@ -34,6 +35,7 @@ import blenderbim.core.spatial
import blenderbim.core.style
from blenderbim.bim.ifc import IfcStore
from mathutils import Vector
+from typing import Union
class IfcExporter:
@@ -85,8 +87,8 @@ class IfcExporter:
self.get_application_name(), tool.Blender.get_blenderbim_version()
)
- def sync_all_objects(self):
- results = []
+ def sync_all_objects(self, skip_unlinking=False) -> list[ifcopenshell.entity_instance]:
+ results: list[ifcopenshell.entity_instance] = []
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file)
for ifc_definition_id in list(IfcStore.id_map.keys()):
obj = IfcStore.id_map[ifc_definition_id]
@@ -95,19 +97,21 @@ class IfcExporter:
continue
if obj.library:
continue
- tool.Collector.sync(obj)
+ tool.Collector.sync(obj, skip_unlinking)
result = self.sync_object_placement(obj)
if result:
results.append(result)
result = self.sync_object_material(obj)
+ # TODO: sync_object_material always returns None
+ # so it's never really appended
if result:
results.append(result)
except ReferenceError:
pass # The object is likely deleted
return results
- def sync_edited_objects(self):
- results = []
+ def sync_edited_objects(self) -> list[ifcopenshell.entity_instance]:
+ results: list[ifcopenshell.entity_instance] = []
for obj in IfcStore.edited_objs.copy():
if not obj:
continue
@@ -125,7 +129,7 @@ class IfcExporter:
IfcStore.edited_objs.clear()
return results
- def sync_object_material(self, obj):
+ def sync_object_material(self, obj: bpy.types.Object) -> None:
if not obj.data or not isinstance(obj.data, bpy.types.Mesh):
return
if not self.has_changed_materials(obj):
@@ -136,11 +140,10 @@ class IfcExporter:
checksum = obj.data.BIMMeshProperties.material_checksum
return checksum != str([s.id() for s in tool.Geometry.get_styles(obj) if s])
- def sync_object_placement(self, obj):
+ def sync_object_placement(self, obj: bpy.types.Object) -> Union[ifcopenshell.entity_instance, None]:
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
if not tool.Ifc.is_moved(obj):
return
- blender_matrix = np.array(obj.matrix_world)
if (obj.scale - Vector((1.0, 1.0, 1.0))).length > 1e-4:
bpy.ops.bim.update_representation(obj=obj.name)
return element
@@ -151,7 +154,7 @@ class IfcExporter:
blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
return element
- def sync_grid_axis_object_placement(self, obj, element):
+ def sync_grid_axis_object_placement(self, obj: bpy.types.Object, element: ifcopenshell.entity_instance) -> None:
grid = (element.PartOfU or element.PartOfV or element.PartOfW)[0]
grid_obj = tool.Ifc.get_object(grid)
if grid_obj:
diff --git a/src/blenderbim/blenderbim/bim/handler.py b/src/blenderbim/blenderbim/bim/handler.py
index bf07cc9fc6..28306671de 100644
--- a/src/blenderbim/blenderbim/bim/handler.py
+++ b/src/blenderbim/blenderbim/bim/handler.py
@@ -31,6 +31,7 @@ from blenderbim.bim.module.model.data import AuthoringData
from blenderbim.bim.module.model.workspace import LIST_OF_TOOLS, TOOLS_TO_CLASSES_MAP
from mathutils import Vector
from math import cos, degrees
+from typing import Union
cwd = os.path.dirname(os.path.realpath(__file__))
@@ -191,6 +192,8 @@ def refresh_ui_data():
if isinstance(tool.Ifc.get(), ifcopenshell.sqlite):
tool.Ifc.get().clear_cache()
+ bpy.context.scene.DocProperties.should_draw_decorations = bpy.context.scene.DocProperties.should_draw_decorations
+
@persistent
def loadIfcStore(scene):
@@ -254,60 +257,6 @@ def viewport_shading_changed_callback(area):
bpy.context.scene.BIMStylesProperties.active_style_type = "External"
-@classmethod
-def poll_check_blender_tab(cls, context):
- return tool.Blender.is_tab(context, "BLENDER")
-
-
-def get_override_scene_panel(panel_name):
- original_panel = getattr(bpy.types, panel_name)
-
- override_panel = type(f"Override_{panel_name}", (original_panel,), {})
- override_panel.bl_idname = f"{panel_name}_override"
-
- # some blender panel depend on other and register_class will throw an error
- # if we won't override their `bl_parent_id`
- bl_parent_id = getattr(override_panel, "bl_parent_id", None)
- if bl_parent_id is not None:
- override_panel.bl_parent_id = f"{bl_parent_id}_override"
-
- # override poll method
- poll = getattr(override_panel, "poll", None)
- if poll is None:
- override_panel.poll = poll_check_blender_tab
- else:
-
- @classmethod
- def wrapped_poll(cls, context):
- return super(override_panel, cls).poll(context) and poll_check_blender_tab.__func__(cls, context)
-
- override_panel.poll = wrapped_poll
-
- return override_panel
-
-
-# TODO: possibly override scene panels from other addons too?
-# list can be updated from
-# https://projects.blender.org/blender/blender/src/branch/main/scripts/startup/bl_ui/properties_scene.py#L421
-OVERRIDE_SCENE_PANELS = (
- "SCENE_PT_scene",
- "SCENE_PT_unit",
- "SCENE_PT_physics",
- "SCENE_PT_rigid_body_world",
- "SCENE_PT_rigid_body_world_settings",
- "SCENE_PT_rigid_body_cache",
- "SCENE_PT_rigid_body_field_weights",
- "SCENE_PT_audio",
- "SCENE_PT_keying_sets",
- "SCENE_PT_custom_props",
- # after SCENE_PT_keying_sets
- "SCENE_PT_keying_set_paths",
- "SCENE_PT_keyframing_settings",
-)
-if bpy.app.version >= (4, 0):
- OVERRIDE_SCENE_PANELS += ("SCENE_PT_simulation",)
-
-
@persistent
def load_post(scene):
global global_subscription_owner
@@ -343,15 +292,11 @@ def load_post(scene):
else:
bpy.ops.workspace.append_activate(idname="BIM", filepath=os.path.join(cwd, "data", "workspace.blend"))
- # To improve usability for new users, we hijack the scene properties
- # tab. We override default scene properties panels with our own poll
- # to hide them unless the user has chosen to view Blender properties.
- for panel in OVERRIDE_SCENE_PANELS:
- if panel in blenderbim.bim.overridden_scene_panels:
- continue
- override_panel = get_override_scene_panel(panel)
- original_panel = getattr(bpy.types, panel)
- bpy.utils.register_class(override_panel)
- bpy.utils.unregister_class(original_panel)
- blenderbim.bim.overridden_scene_panels[panel] = (original_panel, override_panel)
+ # To improve usability for new users, we hijack the scene properties
+ # tab. We override default scene properties panels with our own poll
+ # to hide them unless the user has chosen to view Blender properties.
+ for panel in tool.Blender.get_scene_panels_list():
+ if panel in blenderbim.bim.original_scene_panels_polls:
+ continue
+ tool.Blender.override_scene_panel(panel)
tool.Blender.setup_tabs()
diff --git a/src/blenderbim/blenderbim/bim/helper.py b/src/blenderbim/blenderbim/bim/helper.py
index 48bc67a294..a1bb904f74 100644
--- a/src/blenderbim/blenderbim/bim/helper.py
+++ b/src/blenderbim/blenderbim/bim/helper.py
@@ -29,6 +29,7 @@ from mathutils import geometry
from mathutils import Vector
import blenderbim.tool as tool
from blenderbim.bim.ifc import IfcStore
+from typing import Optional, Callable, Any
def draw_attributes(props, layout, copy_operator=None, popup_active_attribute=None):
@@ -75,6 +76,10 @@ def draw_attribute(attribute, layout, copy_operator=None):
op.data_path = attribute.path_from_id("string_value")
if attribute.is_optional:
layout.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
+
+ if attribute.name == "GlobalId":
+ layout.operator("bim.generate_global_id", icon="FILE_REFRESH", text="")
+
if copy_operator:
op = layout.operator(f"{copy_operator}", text="", icon="COPYDOWN")
op.name = attribute.name
@@ -116,7 +121,7 @@ def import_attribute(attribute, props, data, callback=None):
elif is_handled_by_callback is False:
props.remove(len(props) - 1)
elif data_type == "string":
- new.string_value = "" if new.is_null else str(data[attribute.name()])
+ new.string_value = "" if new.is_null else str(data[attribute.name()]).replace("\n", "\\n")
if attribute.type_of_attribute().declared_type().name() == "IfcURIReference":
new.is_uri = True
elif data_type == "boolean":
@@ -178,7 +183,7 @@ def add_attribute_description(attribute_blender, attribute_ifc=None):
attribute_blender.description = description
-def export_attributes(props, callback=None):
+def export_attributes(props, callback: Optional[Callable] = None) -> dict[str, Any]:
attributes = {}
for prop in props:
is_handled_by_callback = callback(attributes, prop) if callback else False
@@ -216,39 +221,6 @@ def get_enum_items(data, prop_name, context=None):
return items
-def get_obj_ifc_definition_id(context, obj, obj_type):
- if obj_type == "Object":
- return bpy.data.objects.get(obj).BIMObjectProperties.ifc_definition_id
- elif obj_type == "Material":
- return bpy.data.materials.get(obj).BIMObjectProperties.ifc_definition_id
- elif obj_type == "MaterialSet":
- return ifcopenshell.util.element.get_material(
- tool.Ifc.get_entity(bpy.data.objects.get(obj)), should_skip_usage=True
- ).id()
- elif obj_type == "MaterialSetItem":
- return bpy.data.objects.get(obj).BIMObjectMaterialProperties.active_material_set_item_id
- elif obj_type == "Task":
- return context.scene.BIMTaskTreeProperties.tasks[
- context.scene.BIMWorkScheduleProperties.active_task_index
- ].ifc_definition_id
- elif obj_type == "Cost":
- return context.scene.BIMCostProperties.cost_items[
- context.scene.BIMCostProperties.active_cost_item_index
- ].ifc_definition_id
- elif obj_type == "Resource":
- return context.scene.BIMResourceTreeProperties.resources[
- context.scene.BIMResourceProperties.active_resource_index
- ].ifc_definition_id
- elif obj_type == "Profile":
- return context.scene.BIMProfileProperties.profiles[
- context.scene.BIMProfileProperties.active_profile_index
- ].ifc_definition_id
- elif obj_type == "WorkSchedule":
- return context.scene.BIMWorkScheduleProperties.active_work_schedule_id
- elif obj_type == "Group":
- prop = context.scene.BIMGroupProperties
- return prop.groups[prop.active_group_index].ifc_definition_id
-
# hack to close popup
# https://blender.stackexchange.com/a/202576/130742
def close_operator_panel(event):
@@ -304,6 +276,9 @@ def draw_filter(layout, filter_groups, data, module):
op.type = sprops.facet
op.index = i
op.module = module
+ op = row.operator("bim.remove_filter_group", text="", icon="X")
+ op.index = i
+ op.module = module
for j, ifc_filter in enumerate(filter_group.filters):
if ifc_filter.type == "entity":
@@ -350,11 +325,6 @@ def draw_filter(layout, filter_groups, data, module):
op.index = j
op.module = module
- row = box.row()
- op = row.operator("bim.remove_filter_group", icon="X")
- op.index = i
- op.module = module
-
# TODO this should move into ifcopenshell.util
diff --git a/src/blenderbim/blenderbim/bim/ifc.py b/src/blenderbim/blenderbim/bim/ifc.py
index f58cd080bd..93014e6cac 100644
--- a/src/blenderbim/blenderbim/bim/ifc.py
+++ b/src/blenderbim/blenderbim/bim/ifc.py
@@ -23,33 +23,39 @@ import hashlib
import zipfile
import tempfile
import ifcopenshell
+import ifcopenshell.geom
+import ifcopenshell.ifcopenshell_wrapper
import blenderbim.bim.handler
import blenderbim.tool as tool
from pathlib import Path
from blenderbim.tool.brick import BrickStore
+from typing import Set, Union, Optional
+
+
+IFC_CONNECTED_TYPE = Union[bpy.types.Material, bpy.types.Object]
class IfcStore:
- path = ""
- file = None
- schema = None
- cache = None
- cache_path = None
- id_map = {}
- guid_map = {}
- edited_objs = set()
- pset_template_path = ""
- pset_template_file = None
- classification_path = ""
- classification_file = None
- library_path = ""
- library_file = None
+ path: str = ""
+ file: ifcopenshell.file = None
+ schema: ifcopenshell.ifcopenshell_wrapper.schema_definition = None
+ cache: ifcopenshell.ifcopenshell_wrapper.HdfSerializer = None
+ cache_path: str = None
+ id_map: dict[int, IFC_CONNECTED_TYPE] = {}
+ guid_map: dict[str, IFC_CONNECTED_TYPE] = {}
+ edited_objs: Set[bpy.types.Object] = set()
+ pset_template_path: str = ""
+ pset_template_file: ifcopenshell.file = None
+ classification_path: str = ""
+ classification_file: ifcopenshell.file = None
+ library_path: str = ""
+ library_file: ifcopenshell.file = None
current_transaction = ""
last_transaction = ""
history = []
future = []
schema_identifiers = ["IFC4", "IFC2X3", "IFC4X3"]
- session_files = {}
+ session_files: dict[str, ifcopenshell.file] = {}
@staticmethod
def purge():
@@ -117,7 +123,7 @@ class IfcStore:
IfcStore.get_cache()
@staticmethod
- def load_file(path):
+ def load_file(path) -> None:
if not os.path.isfile(path):
return
extension = path.split(".")[-1]
@@ -146,7 +152,7 @@ class IfcStore:
return IfcStore.schema
@staticmethod
- def get_element(id_or_guid):
+ def get_element(id_or_guid: Union[int, str]) -> IFC_CONNECTED_TYPE:
if isinstance(id_or_guid, int):
map_object = IfcStore.id_map
else:
@@ -159,7 +165,7 @@ class IfcStore:
return obj
@staticmethod
- def relink_all_objects():
+ def relink_all_objects() -> None:
if not IfcStore.get_file():
return
for obj in bpy.data.objects:
@@ -172,7 +178,7 @@ class IfcStore:
IfcStore.relink_object(obj)
@staticmethod
- def relink_object(obj):
+ def relink_object(obj: IFC_CONNECTED_TYPE) -> None:
if not obj:
return
if obj.BIMObjectProperties.ifc_definition_id:
@@ -193,7 +199,7 @@ class IfcStore:
IfcStore.commit_link_element(data)
@staticmethod
- def link_element(element, obj):
+ def link_element(element: ifcopenshell.entity_instance, obj: IFC_CONNECTED_TYPE) -> None:
# Please use tool.Ifc.link() instead of this method. We want to
# refactor this class and deprecate usage of IfcStore in favour of
# tools.
@@ -259,7 +265,7 @@ class IfcStore:
# TODO We're handling id_map and guid_map, but what about edited_objs? This might cause big problems.
@staticmethod
- def rollback_unlink_element(data):
+ def rollback_unlink_element(data) -> None:
if "id" not in data or "obj" not in data:
return
obj = bpy.data.objects.get(data["obj"])
@@ -268,13 +274,15 @@ class IfcStore:
IfcStore.guid_map[data["guid"]] = obj
@staticmethod
- def commit_unlink_element(data):
+ def commit_unlink_element(data) -> None:
del IfcStore.id_map[data["id"]]
if data["guid"]:
del IfcStore.guid_map[data["guid"]]
@staticmethod
- def unlink_element(element=None, obj=None):
+ def unlink_element(
+ element: Optional[ifcopenshell.entity_instance] = None, obj: Optional[IFC_CONNECTED_TYPE] = None
+ ) -> None:
if element is None:
try:
element = tool.Ifc.get_entity(obj)
@@ -320,7 +328,7 @@ class IfcStore:
)
@staticmethod
- def execute_ifc_operator(operator, context, is_invoke=False):
+ def execute_ifc_operator(operator: bpy.types.Operator, context: bpy.types.Context, is_invoke=False):
bpy.context.scene.BIMProperties.is_dirty = True
is_top_level_operator = not bool(IfcStore.current_transaction)
@@ -354,17 +362,17 @@ class IfcStore:
return result
@staticmethod
- def begin_transaction(operator):
+ def begin_transaction(operator: bpy.types.Operator) -> None:
IfcStore.current_transaction = str(uuid.uuid4())
operator.transaction_key = IfcStore.current_transaction
@staticmethod
- def end_transaction(operator):
+ def end_transaction(operator: bpy.types.Operator) -> None:
IfcStore.current_transaction = ""
operator.transaction_key = ""
@staticmethod
- def add_transaction_operation(operator, rollback=None, commit=None):
+ def add_transaction_operation(operator: bpy.types.Operator, rollback=None, commit=None) -> None:
key = getattr(operator, "transaction_key", None)
data = getattr(operator, "transaction_data", None)
bpy.context.scene.BIMProperties.last_transaction = key
@@ -380,7 +388,7 @@ class IfcStore:
IfcStore.future = []
@staticmethod
- def undo(until_key=None):
+ def undo(until_key=None) -> None:
BrickStore.undo()
if not IfcStore.history:
return
@@ -395,7 +403,7 @@ class IfcStore:
IfcStore.future.append(event)
@staticmethod
- def redo(until_key=None):
+ def redo(until_key=None) -> None:
BrickStore.redo()
if not IfcStore.future:
diff --git a/src/blenderbim/blenderbim/bim/import_ifc.py b/src/blenderbim/blenderbim/bim/import_ifc.py
index 970c4b9cf2..02173e2681 100644
--- a/src/blenderbim/blenderbim/bim/import_ifc.py
+++ b/src/blenderbim/blenderbim/bim/import_ifc.py
@@ -32,24 +32,28 @@ import ifcopenshell.geom
import ifcopenshell.util.unit
import ifcopenshell.util.element
import ifcopenshell.util.geolocation
+import ifcopenshell.util.placement
+import ifcopenshell.util.representation
import blenderbim.tool as tool
+import ifcopenshell.ifcopenshell_wrapper as ifcopenshell_wrapper
from itertools import chain, accumulate
-from blenderbim.bim.ifc import IfcStore
+from blenderbim.bim.ifc import IfcStore, IFC_CONNECTED_TYPE
+from blenderbim.tool.loader import OBJECT_DATA_TYPE
from blenderbim.bim.module.drawing.prop import ANNOTATION_TYPES_DATA
-from typing import Dict
+from typing import Dict, Union, Optional, Any
class MaterialCreator:
def __init__(self, ifc_import_settings: IfcImportSettings, ifc_importer: IfcImporter):
- self.mesh = None
- self.obj = None
+ self.mesh: bpy.types.Mesh = None
+ self.obj: bpy.types.Object = None
self.materials: Dict[int, bpy.types.Material] = {}
self.styles: Dict[int, bpy.types.Material] = {}
- self.parsed_meshes = set()
+ self.parsed_meshes: set[str] = set()
self.ifc_import_settings = ifc_import_settings
self.ifc_importer = ifc_importer
- def create(self, element, obj, mesh):
+ def create(self, element: ifcopenshell.entity_instance, obj: bpy.types.Object, mesh: bpy.types.Mesh) -> None:
self.mesh = mesh
# as ifcopenshell triangulates the mesh, we need to merge it to quads again
self.obj = obj
@@ -65,7 +69,7 @@ class MaterialCreator:
self.assign_material_slots_to_faces()
tool.Geometry.record_object_materials(obj)
- def add_default_material(self, element):
+ def add_default_material(self, element: ifcopenshell.entity_instance) -> None:
element_material = ifcopenshell.util.element.get_material(element)
if not element_material:
return
@@ -83,14 +87,17 @@ class MaterialCreator:
self.mesh.materials.append(self.materials[material.id()])
return
- def load_existing_materials(self):
+ def load_existing_materials(self) -> None:
for material in bpy.data.materials:
if material.BIMObjectProperties.ifc_definition_id:
self.materials[material.BIMObjectProperties.ifc_definition_id] = material
if material.BIMMaterialProperties.ifc_style_id:
self.styles[material.BIMMaterialProperties.ifc_style_id] = material
- def parse_representations(self, element):
+ def parse_representations(self, element: ifcopenshell.entity_instance) -> bool:
+ """Search for styles in in all `element`'s representation items
+ and adds them to `self.mesh.materials`.\n
+ Returns `True` if any styles were found and added, returns `False` otherwise."""
has_parsed = False
if hasattr(element, "Representation"):
for representation in element.Representation.Representations:
@@ -104,7 +111,7 @@ class MaterialCreator:
has_parsed = True
return has_parsed
- def parse_representation(self, representation):
+ def parse_representation(self, representation: ifcopenshell.entity_instance) -> bool:
has_parsed = False
representation_items = self.resolve_all_stylable_representation_items(representation)
for item in representation_items:
@@ -112,9 +119,9 @@ class MaterialCreator:
has_parsed = True
return has_parsed
- def parse_representation_item(self, item):
+ def parse_representation_item(self, item: ifcopenshell.entity_instance) -> bool:
if not item.StyledByItem:
- return
+ return False
style_ids = []
styles = list(item.StyledByItem[0].Styles)
while styles:
@@ -124,11 +131,12 @@ class MaterialCreator:
elif style.is_a("IfcPresentationStyleAssignment"):
styles.extend(style.Styles)
if not style_ids:
- return
+ return False
for style_id in style_ids:
material = self.styles[style_id]
- def get_ifc_coordinate(material):
+ def get_ifc_coordinate(material: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]:
+ """returns IfcTextureCoordinate"""
texture_style = tool.Style.get_texture_style(material)
if not texture_style:
return
@@ -149,7 +157,7 @@ class MaterialCreator:
self.mesh.materials.append(material)
return True
- def assign_material_slots_to_faces(self):
+ def assign_material_slots_to_faces(self) -> None:
if "ios_materials" not in self.mesh or not self.mesh["ios_materials"]:
return
if len(self.mesh.materials) == 1:
@@ -157,8 +165,12 @@ class MaterialCreator:
material_to_slot = {}
if len(self.mesh.polygons) == len(self.mesh["ios_material_ids"]):
- for i, style_id in enumerate(self.mesh["ios_materials"]):
- slot_index = self.mesh.materials.find(self.styles[style_id].name)
+ for i, style_or_material_id in enumerate(self.mesh["ios_materials"]):
+ if style_or_material_id in self.styles:
+ blender_material = self.styles[style_or_material_id]
+ else:
+ blender_material = self.materials[style_or_material_id]
+ slot_index = self.mesh.materials.find(blender_material.name)
material_to_slot[i] = slot_index
material_index = [
@@ -166,7 +178,10 @@ class MaterialCreator:
]
self.mesh.polygons.foreach_set("material_index", material_index)
- def resolve_all_stylable_representation_items(self, representation):
+ def resolve_all_stylable_representation_items(
+ self, representation: ifcopenshell.entity_instance
+ ) -> list[ifcopenshell.entity_instance]:
+ """returns list of resolved IfcRepresentationItems"""
items = []
for item in representation.Items:
if item.is_a("IfcMappedItem"):
@@ -187,21 +202,27 @@ class IfcImporter:
def __init__(self, ifc_import_settings):
self.ifc_import_settings = ifc_import_settings
self.diff = None
- self.file = None
- self.context_settings = []
- self.gross_context_settings = []
+ self.file: ifcopenshell.file = None
+ self.context_settings: list[ifcopenshell.geom.main.settings] = []
+ self.gross_context_settings: list[ifcopenshell.geom.main.settings] = []
self.contexts = []
self.project = None
self.has_existing_project = False
- self.collections = {}
- self.elements = set()
- self.type_collection = None
+ # element guids to blender collections mapping
+ self.collections: dict[str, bpy.types.Collection] = {}
+ self.elements: set[ifcopenshell.entity_instance] = set()
+ self.annotations: set[ifcopenshell.entity_instance] = set()
+ self.gross_elements: set[ifcopenshell.entity_instance] = set()
+ self.element_types: set[ifcopenshell.entity_instance] = set()
+ self.spatial_elements: set[ifcopenshell.entity_instance] = set()
+ self.type_collection: bpy.types.Collection = None
self.type_products = {}
self.meshes = {}
self.mesh_shapes = {}
self.time = 0
self.unit_scale = 1
- self.added_data = {}
+ # ifc definition ids to blender elements mapping
+ self.added_data: dict[int, IFC_CONNECTED_TYPE] = {}
self.native_elements = set()
self.native_data = {}
self.progress = 0
@@ -440,7 +461,9 @@ class IfcImporter:
for element in self.gross_elements:
print(element)
- def get_spatial_elements_filtered_by_elements(self, elements):
+ def get_spatial_elements_filtered_by_elements(
+ self, elements: set[ifcopenshell.entity_instance]
+ ) -> set[ifcopenshell.entity_instance]:
leaf_spatial_elements = set([ifcopenshell.util.element.get_container(e) for e in elements])
results = set()
for spatial_element in leaf_spatial_elements:
@@ -776,7 +799,7 @@ class IfcImporter:
continue
self.create_element_type(element_type)
- def create_element_type(self, element):
+ def create_element_type(self, element: ifcopenshell.entity_instance) -> None:
self.ifc_import_settings.logger.info("Creating object %s", element)
mesh = None
if self.ifc_import_settings.should_load_geometry:
@@ -833,17 +856,23 @@ class IfcImporter:
self.create_product(element, mesh=mesh)
print("Done creating geometry")
- def create_spatial_elements(self):
- self.create_generic_elements(self.spatial_elements)
+ def create_spatial_elements(self) -> None:
+ if bpy.context.preferences.addons["blenderbim"].preferences.spatial_elements_unselectable:
+ self.create_generic_elements(self.spatial_elements, unselectable=True)
+ else:
+ self.create_generic_elements(self.spatial_elements, unselectable=False)
- def create_elements(self):
+
+ def create_elements(self) -> None:
self.create_generic_elements(self.elements)
tmp = self.context_settings
self.context_settings = self.gross_context_settings
self.create_generic_elements(self.gross_elements)
self.context_settings = tmp
- def create_generic_shape(self, element):
+ def create_generic_shape(
+ self, element: ifcopenshell.entity_instance
+ ) -> Union[ifcopenshell_wrapper.TriangulationElement, None]:
for settings in self.context_settings:
try:
result = ifcopenshell.geom.create_shape(settings, element)
@@ -852,7 +881,7 @@ class IfcImporter:
except:
pass
- def create_generic_elements(self, elements):
+ def create_generic_elements(self, elements: set[ifcopenshell.entity_instance], unselectable=False) -> None:
if isinstance(self.file, ifcopenshell.sqlite):
return self.create_generic_sqlite_elements(elements)
@@ -866,12 +895,17 @@ class IfcImporter:
elements -= products
total = len(elements)
+ objects = set()
for i, element in enumerate(elements):
if i % 250 == 0:
print("{} / {} elements processed ...".format(i, total))
- self.create_product(element)
+ objects.add(self.create_product(element))
- def create_generic_sqlite_elements(self, elements):
+ if unselectable:
+ for obj in objects:
+ obj.hide_select = True
+
+ def create_generic_sqlite_elements(self, elements: set[ifcopenshell.entity_instance]) -> None:
self.geometry_cache = self.file.get_geometry([e.id() for e in elements])
for geometry_id, geometry in self.geometry_cache["geometry"].items():
mesh_name = tool.Loader.get_mesh_name(type("Geometry", (), {"id": geometry_id}))
@@ -918,7 +952,9 @@ class IfcImporter:
mesh = self.meshes.get(mesh_name)
self.create_product(element, mesh=mesh)
- def create_products(self, products, settings=None):
+ def create_products(
+ self, products, settings: Optional[ifcopenshell.geom.main.settings] = None
+ ) -> set[ifcopenshell.entity_instance]:
results = set()
if not products:
return results
@@ -1041,7 +1077,7 @@ class IfcImporter:
return mapped_representation
return None
- def create_pointclouds(self, products):
+ def create_pointclouds(self, products: set[ifcopenshell.entity_instance]) -> set[ifcopenshell.entity_instance]:
result = set()
for product in products:
representation = self.get_pointcloud_representation(product)
@@ -1052,7 +1088,9 @@ class IfcImporter:
return result
- def create_pointcloud(self, product, representation):
+ def create_pointcloud(
+ self, product: ifcopenshell.entity_instance, representation: ifcopenshell.entity_instance
+ ) -> Union[ifcopenshell.entity_instance, None]:
placement_matrix = self.get_element_matrix(product)
vertex_list = []
for item in representation.Items:
@@ -1080,7 +1118,12 @@ class IfcImporter:
self.link_element(product, obj)
return product
- def create_product(self, element, shape=None, mesh=None):
+ def create_product(
+ self,
+ element: ifcopenshell.entity_instance,
+ shape: Optional[Any] = None,
+ mesh: Optional[OBJECT_DATA_TYPE] = None,
+ ) -> Union[bpy.types.Object, None]:
if element is None:
return
@@ -1125,6 +1168,9 @@ class IfcImporter:
return obj
+ def load_existing_meshes(self) -> None:
+ self.meshes.update({m.name: m for m in bpy.data.meshes})
+
def get_representation_item_material_name(self, item):
if not item.StyledByItem:
return
@@ -1531,18 +1577,19 @@ class IfcImporter:
"{}/{}".format(self.project["ifc"].is_a(), self.project["ifc"].Name)
)
obj = self.create_product(self.project["ifc"])
+ obj.hide_select = True
self.project["blender"].objects.link(obj)
self.project["blender"].BIMCollectionProperties.obj = obj
obj.BIMObjectProperties.collection = self.collections[project.GlobalId] = self.project["blender"]
- def create_collections(self):
+ def create_collections(self) -> None:
self.create_spatial_decomposition_collections()
if self.ifc_import_settings.collection_mode == "DECOMPOSITION":
self.create_aggregate_and_nest_collections()
elif self.ifc_import_settings.collection_mode == "SPATIAL_DECOMPOSITION":
pass
- def create_spatial_decomposition_collections(self):
+ def create_spatial_decomposition_collections(self) -> None:
for rel_aggregate in self.project["ifc"].IsDecomposedBy or []:
self.create_spatial_decomposition_collection(self.project["blender"], rel_aggregate.RelatedObjects)
@@ -1555,7 +1602,9 @@ class IfcImporter:
tool.Loader.create_project_collection("Views")
self.type_collection = tool.Loader.create_project_collection("Types")
- def create_spatial_decomposition_collection(self, parent, related_objects):
+ def create_spatial_decomposition_collection(
+ self, parent: Union[bpy.types.Collection, bpy.types.Object], related_objects: list[ifcopenshell.entity_instance]
+ ) -> None:
for element in related_objects:
if element not in self.spatial_elements:
continue
@@ -1595,7 +1644,11 @@ class IfcImporter:
] + [
r
for r in self.file.by_type("IfcRelNests")
- if (r.RelatingObject.is_a("IfcElement") or r.RelatingObject.is_a("IfcElementType"))
+ if (
+ r.RelatingObject.is_a("IfcElement")
+ or r.RelatingObject.is_a("IfcElementType")
+ or (r.RelatingObject.is_a("IfcPositioningElement") and not r.RelatingObject.is_a("IfcGrid"))
+ )
and [e for e in r.RelatedObjects if not e.is_a("IfcPort")]
]
@@ -1605,9 +1658,9 @@ class IfcImporter:
self.ifc_import_settings.collection_mode = "SPATIAL_DECOMPOSITION"
return
- aggregates = {}
+ aggregates: dict[str, dict] = {}
for rel_aggregate in rel_aggregates:
- element = rel_aggregate.RelatingObject
+ element: ifcopenshell.entity_instance = rel_aggregate.RelatingObject
collection = bpy.data.collections.new(tool.Loader.get_name(element))
aggregates[element.GlobalId] = {"element": element, "collection": collection}
self.collections[element.GlobalId] = collection
@@ -1659,7 +1712,9 @@ class IfcImporter:
continue
self.create_style(style)
- def create_style(self, style, blender_material=None):
+ def create_style(
+ self, style: ifcopenshell.entity_instance, blender_material: Optional[bpy.types.Material] = None
+ ) -> None:
if not blender_material:
name = style.Name or str(style.id())
blender_material = bpy.data.materials.new(name)
@@ -1676,18 +1731,20 @@ class IfcImporter:
else:
blender_material.BIMStyleProperties.active_style_type = "Shading"
- def place_objects_in_collections(self):
+ def place_objects_in_collections(self) -> None:
for ifc_definition_id, obj in self.added_data.items():
if isinstance(obj, bpy.types.Object):
self.place_object_in_collection(self.file.by_id(ifc_definition_id), obj)
- def place_object_in_collection(self, element, obj):
+ def place_object_in_collection(self, element: ifcopenshell.entity_instance, obj: bpy.types.Object) -> None:
if self.ifc_import_settings.collection_mode == "DECOMPOSITION":
self.place_object_in_decomposition_collection(element, obj)
elif self.ifc_import_settings.collection_mode == "SPATIAL_DECOMPOSITION":
self.place_object_in_spatial_decomposition_collection(element, obj)
- def place_object_in_decomposition_collection(self, element, obj):
+ def place_object_in_decomposition_collection(
+ self, element: ifcopenshell.entity_instance, obj: bpy.types.Object
+ ) -> None:
if element.is_a("IfcProject"):
return
elif element.is_a("IfcGridAxis"):
@@ -1704,10 +1761,12 @@ class IfcImporter:
elif getattr(element, "Nests", None) and not element.is_a("IfcPort"):
nest = ifcopenshell.util.element.get_nest(element)
return self.collections[nest.GlobalId].objects.link(obj)
- else:
- return self.place_object_in_spatial_decomposition_collection(element, obj)
- def place_object_in_spatial_decomposition_collection(self, element, obj):
+ return self.place_object_in_spatial_decomposition_collection(element, obj)
+
+ def place_object_in_spatial_decomposition_collection(
+ self, element: ifcopenshell.entity_instance, obj: bpy.types.Object
+ ) -> None:
if element.is_a("IfcProject"):
return
elif element.is_a("IfcGridAxis"):
@@ -1751,7 +1810,7 @@ class IfcImporter:
if rel.is_a("IfcRelAssignsToGroup") and rel.RelatingGroup.ObjectType == "DRAWING":
return rel.RelatingGroup
- def get_element_matrix(self, element):
+ def get_element_matrix(self, element: ifcopenshell.entity_instance) -> np.array:
if isinstance(element, ifcopenshell.sqlite_entity):
result = self.geometry_cache["shapes"][element.id()]["matrix"]
else:
@@ -1761,7 +1820,7 @@ class IfcImporter:
result[2][3] *= self.unit_scale
return result
- def scale_matrix(self, matrix):
+ def scale_matrix(self, matrix: np.array) -> np.array:
matrix[0][3] *= self.unit_scale
matrix[1][3] *= self.unit_scale
matrix[2][3] *= self.unit_scale
@@ -1793,7 +1852,7 @@ class IfcImporter:
):
return representation.Items[0].MappingTarget
- def create_curve(self, element, shape):
+ def create_curve(self, element: ifcopenshell.entity_instance, shape) -> bpy.types.Curve:
if hasattr(shape, "geometry"):
geometry = shape.geometry
else:
@@ -1818,12 +1877,13 @@ class IfcImporter:
polyline.points[-1].co = mathutils.Vector(v2)
return curve
- def create_mesh(self, element, shape):
+ def create_mesh(self, element: ifcopenshell.entity_instance, shape) -> bpy.types.Mesh:
try:
if hasattr(shape, "geometry"):
- geometry = shape.geometry
+ # shape is ifcopenshell_wrapper.TriangulationElement
+ geometry: ifcopenshell_wrapper.Triangulation = shape.geometry
else:
- geometry = shape
+ geometry: ifcopenshell_wrapper.Triangulation = shape
mesh = bpy.data.meshes.new(tool.Loader.get_mesh_name(geometry))
@@ -1989,7 +2049,7 @@ class IfcImportSettings:
self.has_filter = None
self.should_filter_spatial_elements = True
self.should_setup_viewport_camera = True
- self.elements = set()
+ self.elements: set[ifcopenshell.entity_instance] = set()
self.collection_mode = "DECOMPOSITION"
@staticmethod
diff --git a/src/blenderbim/blenderbim/bim/module/aggregate/operator.py b/src/blenderbim/blenderbim/bim/module/aggregate/operator.py
index 9e26e78db0..89185b9021 100644
--- a/src/blenderbim/blenderbim/bim/module/aggregate/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/aggregate/operator.py
@@ -44,7 +44,12 @@ class BIM_OT_aggregate_assign_object(bpy.types.Operator, Operator):
def _execute(self, context):
relating_obj = None
if self.relating_object:
- relating_obj = tool.Ifc.get_object(tool.Ifc.get().by_id(self.relating_object))
+ element = tool.Ifc.get().by_id(self.relating_object)
+ if element.IsDecomposedBy:
+ relating_obj = tool.Ifc.get_object(element)
+ else:
+ assembly = element.Decomposes[0].RelatingObject
+ relating_obj = tool.Ifc.get_object(assembly)
elif context.active_object:
relating_obj = context.active_object
if not relating_obj:
@@ -89,7 +94,7 @@ class BIM_OT_aggregate_unassign_object(bpy.types.Operator, Operator):
relating_obj=tool.Ifc.get_object(aggregate),
related_obj=tool.Ifc.get_object(element),
)
-
+
# Removes Pset related to Linked Aggregates
if not element.is_a('IfcElementAssembly'):
pset = ifcopenshell.util.element.get_pset(element, 'BBIM_Linked_Aggregate')
@@ -97,7 +102,7 @@ class BIM_OT_aggregate_unassign_object(bpy.types.Operator, Operator):
pset = tool.Ifc.get().by_id(pset["id"])
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
-
+
class BIM_OT_enable_editing_aggregate(bpy.types.Operator, Operator):
"""Enable editing aggregation relationship"""
@@ -202,15 +207,29 @@ class BIM_OT_select_aggregate(bpy.types.Operator):
bl_label = "Select Aggregate"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
+ select_parts: bpy.props.BoolProperty(default=False)
+
+ @classmethod
+ def description(cls, context, properties):
+ if properties.select_parts:
+ return "Select Aggregate and Parts"
+ else:
+ return "Select Aggregate"
def execute(self, context):
self.file = IfcStore.get_file()
obj = bpy.data.objects.get(self.obj) or context.active_object
aggregate = ifcopenshell.util.element.get_aggregate(tool.Ifc.get_entity(obj))
aggregate_obj = tool.Ifc.get_object(aggregate)
+ if self.select_parts:
+ bpy.ops.bim.select_parts(obj=aggregate_obj.name)
if aggregate_obj in context.selectable_objects:
- aggregate_obj.select_set(True)
- bpy.context.view_layer.objects.active = aggregate_obj
+ tool.Blender.set_objects_selection(
+ context,
+ aggregate_obj,
+ [aggregate_obj],
+ clear_previous_selection=not self.select_parts,
+ )
return {"FINISHED"}
@@ -238,6 +257,7 @@ class BIM_OT_add_part_to_object(bpy.types.Operator, Operator):
part_name=self.part_name,
)
+
class BIM_OT_break_link_to_other_aggregates(bpy.types.Operator, Operator):
bl_idname = "bim.break_link_to_other_aggregates"
bl_label = "Break link to other aggregates"
@@ -252,22 +272,23 @@ class BIM_OT_break_link_to_other_aggregates(bpy.types.Operator, Operator):
return []
parts = ifcopenshell.util.element.get_parts(aggregate)
-
+
for part in parts:
- pset = ifcopenshell.util.element.get_pset(part, 'BBIM_Linked_Aggregate')
+ pset = ifcopenshell.util.element.get_pset(part, "BBIM_Linked_Aggregate")
pset = tool.Ifc.get().by_id(pset["id"])
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
-
+
linked_aggregate_group = [
r.RelatingGroup
for r in getattr(aggregate, "HasAssignments", []) or []
if r.is_a("IfcRelAssignsToGroup")
if "BBIM_Linked_Aggregate" in r.RelatingGroup.Name
]
- tool.Ifc.run("group.unassign_group", group=linked_aggregate_group[0], product=aggregate)
-
+ tool.Ifc.run("group.unassign_group", group=linked_aggregate_group[0], products=[aggregate])
+
return {"FINISHED"}
+
class BIM_OT_select_linked_aggregates(bpy.types.Operator, Operator):
bl_idname = "bim.select_linked_aggregates"
bl_label = "Select linked aggregates"
@@ -280,17 +301,17 @@ class BIM_OT_select_linked_aggregates(bpy.types.Operator, Operator):
return []
if not element:
return []
-
+
linked_aggregate_group = [
r.RelatingGroup
for r in getattr(aggregate, "HasAssignments", []) or []
if r.is_a("IfcRelAssignsToGroup")
if "BBIM_Linked_Aggregate" in r.RelatingGroup.Name
]
-
+
for obj in context.selected_objects:
obj.select_set(False)
-
+
for rel in linked_aggregate_group[0].IsGroupedBy or []:
for element in rel.RelatedObjects:
obj = tool.Ifc.get_object(element)
diff --git a/src/blenderbim/blenderbim/bim/module/aggregate/ui.py b/src/blenderbim/blenderbim/bim/module/aggregate/ui.py
index dcef2a9435..41f4b5b98d 100644
--- a/src/blenderbim/blenderbim/bim/module/aggregate/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/aggregate/ui.py
@@ -62,8 +62,12 @@ class BIM_PT_aggregate(Panel):
row = layout.row(align=True)
if AggregateData.data["has_relating_object"]:
row.label(text=AggregateData.data["relating_object_label"], icon="TRIA_UP")
+ op = row.operator("bim.select_aggregate", icon="OBJECT_DATA", text="")
+ op.obj = context.active_object.name
+ op.select_parts = False
op = row.operator("bim.select_aggregate", icon="RESTRICT_SELECT_OFF", text="")
op.obj = context.active_object.name
+ op.select_parts = True
row.operator("bim.enable_editing_aggregate", icon="GREASEPENCIL", text="")
row.operator("bim.add_aggregate", icon="ADD", text="")
op = row.operator("bim.aggregate_unassign_object", icon="X", text="")
diff --git a/src/blenderbim/blenderbim/bim/module/attribute/operator.py b/src/blenderbim/blenderbim/bim/module/attribute/operator.py
index 89b2d06df2..a86ff15e21 100644
--- a/src/blenderbim/blenderbim/bim/module/attribute/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/attribute/operator.py
@@ -47,10 +47,18 @@ class EnableEditingAttributes(bpy.types.Operator):
obj = bpy.data.objects.get(self.obj)
elif self.obj_type == "Material":
obj = bpy.data.materials.get(self.obj)
- oprops = obj.BIMObjectProperties
props = obj.BIMAttributeProperties
props.attributes.clear()
+ element = tool.Ifc.get_entity(obj)
+ has_inherited_predefined_type = False
+ if not element.is_a("IfcTypeObject") and (element_type := ifcopenshell.util.element.get_type(element)):
+ # Allow for None due to https://github.com/buildingSMART/IFC4.3.x-development/issues/818
+ has_inherited_predefined_type = ifcopenshell.util.element.get_predefined_type(element_type) not in (
+ "NOTDEFINED",
+ None,
+ )
+
def callback(name, prop, data):
if name in ("RefLatitude", "RefLongitude"):
new = props.attributes.add()
@@ -62,10 +70,11 @@ class EnableEditingAttributes(bpy.types.Operator):
new.string_value = "" if new.is_null else json.dumps(data[name])
blenderbim.bim.helper.add_attribute_description(new)
new.description += " The degrees, minutes and seconds should follow this format : [12,34,56]"
+ if name in ("PredefinedType", "ObjectType") and has_inherited_predefined_type:
+ props.attributes.remove(len(props.attributes) - 1)
+ return True
- blenderbim.bim.helper.import_attributes2(
- tool.Ifc.get().by_id(oprops.ifc_definition_id), props.attributes, callback=callback
- )
+ blenderbim.bim.helper.import_attributes2(element, props.attributes, callback=callback)
props.is_editing_attributes = True
return {"FINISHED"}
@@ -120,20 +129,46 @@ class EditAttributes(bpy.types.Operator, Operator):
return {"FINISHED"}
-class GenerateGlobalId(bpy.types.Operator):
+class GenerateGlobalId(bpy.types.Operator, Operator):
bl_idname = "bim.generate_global_id"
bl_label = "Regenerate GlobalId"
+ bl_description = "Regenerate GlobalId\n\nSHIFT+CLICK to regenerate GlobalIds for all selected objects"
bl_options = {"REGISTER", "UNDO"}
- def execute(self, context):
- index = context.active_object.BIMAttributeProperties.attributes.find("GlobalId")
- if index >= 0:
- global_id = context.active_object.BIMAttributeProperties.attributes[index]
+ use_selected: bpy.props.BoolProperty(name="Use All Selected Objects", default=False, options={"SKIP_SAVE"})
+
+ def invoke(self, context, event):
+ # using all selected objects on shift+click
+ # make sure to use SKIP_SAVE on property, otherwise it might get stuck
+ if event.type == "LEFTMOUSE" and event.shift:
+ self.use_selected = True
+ return self.execute(context)
+
+ def _execute(self, context):
+ if self.use_selected:
+ for obj in context.selected_objects:
+ element = tool.Ifc.get_entity(obj)
+ if not element or not element.is_a("IfcRoot"):
+ continue
+ element.GlobalId = ifcopenshell.guid.new()
+
+ obj = context.active_object
+ if not obj or not obj.BIMAttributeProperties.is_editing_attributes:
+ return {"FINISHED"}
+
+ props = obj.BIMAttributeProperties
+ element = tool.Ifc.get_entity(obj)
+
+ if not element.is_a("IfcRoot"):
+ return {"FINISHED"}
+
+ if self.use_selected and obj in context.selected_objects:
+ # guid value was already regenerated, just update the ui prop
+ guid_value = element.GlobalId
else:
- global_id = context.active_object.BIMAttributeProperties.attributes.add()
- global_id.name = "GlobalId"
- global_id.data_type = "string"
- global_id.string_value = ifcopenshell.guid.new()
+ guid_value = ifcopenshell.guid.new()
+
+ props.attributes["GlobalId"].string_value = guid_value
return {"FINISHED"}
diff --git a/src/blenderbim/blenderbim/bim/module/boundary/__init__.py b/src/blenderbim/blenderbim/bim/module/boundary/__init__.py
index 389b1e64c9..02537d043d 100644
--- a/src/blenderbim/blenderbim/bim/module/boundary/__init__.py
+++ b/src/blenderbim/blenderbim/bim/module/boundary/__init__.py
@@ -22,6 +22,7 @@ from . import ui, operator, prop
classes = (
operator.AddBoundary,
operator.ColourByRelatedBuildingElement,
+ operator.DecorateBoundaries,
operator.DisableEditingBoundary,
operator.DisableEditingBoundaryGeometry,
operator.EditBoundaryAttributes,
diff --git a/src/blenderbim/blenderbim/bim/module/boundary/operator.py b/src/blenderbim/blenderbim/bim/module/boundary/operator.py
index fda54ceb63..6a20852f06 100644
--- a/src/blenderbim/blenderbim/bim/module/boundary/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/boundary/operator.py
@@ -24,6 +24,7 @@ import mathutils
import numpy as np
import multiprocessing
import ifcopenshell.api
+import ifcopenshell.geom
import ifcopenshell.util.unit
import ifcopenshell.util.shape
import ifcopenshell.util.element
@@ -37,6 +38,7 @@ from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.model.decorator import ProfileDecorator
from blenderbim.bim.module.boundary.decorator import BoundaryDecorator
import blenderbim.core
+import blenderbim.core.geometry
def get_boundaries_collection(blender_space):
@@ -147,7 +149,9 @@ class Loader:
self.ifc_importer = import_ifc.IfcImporter(ifc_import_settings)
self.ifc_importer.file = self.ifc_file
- def load_boundary(self, boundary, blender_space=None):
+ def load_boundary(
+ self, boundary: ifcopenshell.entity_instance, blender_space: bpy.types.Object = None
+ ) -> bpy.types.Object:
if not blender_space:
if not boundary.RelatingSpace:
self.operator.report(
@@ -509,10 +513,10 @@ class DisableEditingBoundaryGeometry(bpy.types.Operator, tool.Ifc.Operator):
class ShowBoundaries(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.show_boundaries"
bl_label = "Show Boundaries"
+ bl_description = "Load selected objects boundaries if they are not loaded"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
- props = bpy.context.scene.BIMBoundaryProperties
loader = Loader(self)
for obj in context.selected_objects:
element = tool.Ifc.get_entity(obj)
@@ -524,7 +528,7 @@ class ShowBoundaries(bpy.types.Operator, tool.Ifc.Operator):
for rel in element.BoundedBy or []:
boundary_obj = loader.load_boundary(rel, obj)
tool.Boundary.decorate_boundary(boundary_obj)
- BoundaryDecorator.install(bpy.context)
+ BoundaryDecorator.install(context)
return {"FINISHED"}
@@ -557,6 +561,36 @@ class HideBoundaries(bpy.types.Operator, tool.Ifc.Operator):
return {"FINISHED"}
+class DecorateBoundaries(bpy.types.Operator, tool.Ifc.Operator):
+ bl_idname = "bim.decorate_boundaries"
+ bl_label = "Toggle Boundaries Decorations"
+ bl_description = "Add special decorations for all boundaries in the scene or hide them if they are already added"
+ bl_options = {"REGISTER", "UNDO"}
+
+ def _execute(self, context):
+ props = context.scene.BIMBoundaryProperties
+ # filter not decorated boundaries and add decorations for them
+ decorated_boundaries = set([i.obj for i in props.boundaries])
+ active_boundaries = set()
+ for element in tool.Ifc.get().by_type("IfcRelSpaceBoundary"):
+ obj = tool.Ifc.get_object(element)
+ if obj is not None:
+ active_boundaries.add(obj)
+
+ boundaries_to_decorate = active_boundaries - decorated_boundaries
+ if boundaries_to_decorate:
+ for obj in boundaries_to_decorate:
+ tool.Boundary.decorate_boundary(obj)
+ BoundaryDecorator.install(context)
+ else:
+ for obj in decorated_boundaries:
+ tool.Boundary.undecorate_boundary(obj)
+ props.boundaries.clear()
+ BoundaryDecorator.uninstall()
+ tool.Blender.update_viewport()
+ return {"FINISHED"}
+
+
class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.add_boundary"
bl_label = "Add Boundary"
diff --git a/src/blenderbim/blenderbim/bim/module/cad/prop.py b/src/blenderbim/blenderbim/bim/module/cad/prop.py
index daf4346716..ed795a74aa 100644
--- a/src/blenderbim/blenderbim/bim/module/cad/prop.py
+++ b/src/blenderbim/blenderbim/bim/module/cad/prop.py
@@ -24,10 +24,10 @@ from math import pi
class BIMCadProperties(PropertyGroup):
resolution: bpy.props.IntProperty(name="Arc Resolution", min=1, default=1)
- radius: bpy.props.FloatProperty(name="Radius", default=0.1)
- distance: bpy.props.FloatProperty(name="Distance", default=0.1)
- x: bpy.props.FloatProperty(name="X", default=0.2)
- y: bpy.props.FloatProperty(name="Y", default=0.1)
+ radius: bpy.props.FloatProperty(name="Radius", default=0.1, subtype="DISTANCE")
+ distance: bpy.props.FloatProperty(name="Distance", default=0.1, subtype="DISTANCE")
+ x: bpy.props.FloatProperty(name="X", default=0.2, subtype="DISTANCE")
+ y: bpy.props.FloatProperty(name="Y", default=0.1, subtype="DISTANCE")
gable_roof_edge_angle: bpy.props.FloatProperty(
name="Gable Roof Edge Angle", default=pi / 2, soft_min=0, soft_max=pi / 2, subtype="ANGLE"
)
diff --git a/src/blenderbim/blenderbim/bim/module/cad/workspace.py b/src/blenderbim/blenderbim/bim/module/cad/workspace.py
index 60b04dfc9a..e5b8e33d0a 100644
--- a/src/blenderbim/blenderbim/bim/module/cad/workspace.py
+++ b/src/blenderbim/blenderbim/bim/module/cad/workspace.py
@@ -20,6 +20,7 @@ import os
import bpy
import blenderbim.tool as tool
import blenderbim.bim.module.type.prop as type_prop
+import ifcopenshell.util.unit
from bpy.types import WorkSpaceTool
from blenderbim.bim.module.model.data import AuthoringData, RailingData, RoofData
@@ -239,8 +240,9 @@ class CadHotkey(bpy.types.Operator):
row.prop(props, "resolution")
def hotkey_S_C(self):
+ si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
if self.is_profile():
- bpy.ops.bim.add_ifccircle(radius=self.props.radius)
+ bpy.ops.bim.add_ifccircle(radius=self.props.radius / si_conversion)
else:
bpy.ops.bim.cad_arc_from_2_points()
@@ -248,13 +250,15 @@ class CadHotkey(bpy.types.Operator):
bpy.ops.bim.cad_trim_extend()
def hotkey_S_F(self):
+ si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
if self.is_profile():
- bpy.ops.bim.add_ifcarcindex_fillet(radius=self.props.radius)
+ bpy.ops.bim.add_ifcarcindex_fillet(radius=self.props.radius / si_conversion)
else:
- bpy.ops.bim.cad_fillet(resolution=self.props.resolution, radius=self.props.radius)
+ bpy.ops.bim.cad_fillet(resolution=self.props.resolution, radius=self.props.radius / si_conversion)
def hotkey_S_O(self):
- bpy.ops.bim.cad_offset(distance=self.props.distance)
+ si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
+ bpy.ops.bim.cad_offset(distance=self.props.distance / si_conversion)
def hotkey_S_Q(self):
element = tool.Ifc.get_entity(bpy.context.active_object)
@@ -270,7 +274,8 @@ class CadHotkey(bpy.types.Operator):
def hotkey_S_R(self):
if self.is_profile():
- bpy.ops.bim.add_rectangle(x=self.props.x, y=self.props.y)
+ si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
+ bpy.ops.bim.add_rectangle(x=self.props.x / si_conversion, y=self.props.y / si_conversion)
elif (
(RoofData.is_loaded or not RoofData.load())
and RoofData.data["pset_data"]
diff --git a/src/blenderbim/blenderbim/bim/module/clash/__init__.py b/src/blenderbim/blenderbim/bim/module/clash/__init__.py
index 5cb662fa02..b87fb8c3ff 100644
--- a/src/blenderbim/blenderbim/bim/module/clash/__init__.py
+++ b/src/blenderbim/blenderbim/bim/module/clash/__init__.py
@@ -25,15 +25,12 @@ classes = (
operator.ExecuteIfcClash,
operator.ExportClashSets,
operator.ImportClashSets,
- operator.LoadIfcClashes,
- operator.SaveIfcClashes,
operator.LoadSmartGroupsForActiveClashSet,
operator.RemoveClashSet,
operator.RemoveClashSource,
operator.SelectClash,
operator.SelectClashResults,
operator.SelectClashSource,
- operator.SelectIfcClashResults,
operator.SelectSmartGroup,
operator.SelectSmartGroupedClashesPath,
operator.SmartClashGroup,
@@ -44,7 +41,6 @@ classes = (
prop.BIMClashProperties,
ui.BIM_PT_ifcclash,
ui.BIM_PT_clash_manager,
- ui.BIM_PT_dumb_clash_manager,
ui.BIM_PT_smart_clash_manager,
ui.BIM_UL_clashes,
ui.BIM_UL_clash_sets,
diff --git a/src/blenderbim/blenderbim/bim/module/clash/data.py b/src/blenderbim/blenderbim/bim/module/clash/data.py
new file mode 100644
index 0000000000..7f61222f7b
--- /dev/null
+++ b/src/blenderbim/blenderbim/bim/module/clash/data.py
@@ -0,0 +1,52 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2024 Dion Moult
+#
+# 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 .
+
+import bpy
+import json
+import ifcopenshell.util.element
+import blenderbim.tool as tool
+
+
+def refresh():
+ ClashData.is_loaded = False
+
+
+class ClashData:
+ data = {}
+ is_loaded = False
+
+ @classmethod
+ def load(cls):
+ cls.is_loaded = True
+ cls.data = {}
+ cls.data["saved_searches"] = cls.saved_searches()
+
+ @classmethod
+ def saved_searches(cls):
+ if not tool.Ifc.get():
+ return []
+ groups = tool.Ifc.get().by_type("IfcGroup")
+ results = []
+ for group in groups:
+ try:
+ data = json.loads(group.Description)
+ if isinstance(data, dict) and data.get("type", None) == "BBIM_Search" and data.get("query", None):
+ results.append(group)
+ except:
+ pass
+ return [(str(g.id()), g.Name or "Unnamed", "") for g in sorted(results, key=lambda x: x.Name or "Unnamed")]
diff --git a/src/blenderbim/blenderbim/bim/module/clash/decorator.py b/src/blenderbim/blenderbim/bim/module/clash/decorator.py
new file mode 100644
index 0000000000..af7946ddc1
--- /dev/null
+++ b/src/blenderbim/blenderbim/bim/module/clash/decorator.py
@@ -0,0 +1,102 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2024 Dion Moult
+#
+# 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 .
+
+import blf
+import gpu
+import bmesh
+import blenderbim.tool as tool
+from bpy.types import SpaceView3D
+from mathutils import Vector
+from gpu_extras.batch import batch_for_shader
+from bpy_extras.view3d_utils import location_3d_to_region_2d
+
+
+class ClashDecorator:
+ is_installed = False
+ handlers = []
+
+ @classmethod
+ def install(cls, context):
+ if cls.is_installed:
+ cls.uninstall()
+ handler = cls()
+ cls.handlers.append(SpaceView3D.draw_handler_add(handler.draw_text, (context,), "WINDOW", "POST_PIXEL"))
+ cls.handlers.append(SpaceView3D.draw_handler_add(handler.draw_geometry, (context,), "WINDOW", "POST_VIEW"))
+ cls.is_installed = True
+
+ @classmethod
+ def uninstall(cls):
+ for handler in cls.handlers:
+ try:
+ SpaceView3D.draw_handler_remove(handler, "WINDOW")
+ except ValueError:
+ pass
+ cls.is_installed = False
+
+ def draw_batch(self, shader_type, content_pos, color, indices=None):
+ shader = self.line_shader if shader_type == "LINES" else self.shader
+ batch = batch_for_shader(shader, shader_type, {"pos": content_pos}, indices=indices)
+ shader.uniform_float("color", color)
+ batch.draw(shader)
+
+ def draw_text(self, context):
+ self.addon_prefs = context.preferences.addons["blenderbim"].preferences
+ selected_elements_color = self.addon_prefs.decorator_color_selected
+ unselected_elements_color = self.addon_prefs.decorator_color_unselected
+ special_elements_color = self.addon_prefs.decorator_color_special
+
+ text = context.scene.BIMClashProperties.active_clash_text
+ p = context.scene.BIMClashProperties.p1.lerp(context.scene.BIMClashProperties.p2, 0.5)
+
+ font_id = 0
+ blf.size(font_id, 12)
+ coords_2d = location_3d_to_region_2d(context.region, context.region_data, p)
+ color = self.addon_prefs.decorations_colour
+ blf.color(font_id, *color)
+ if coords_2d:
+ w, h = blf.dimensions(font_id, text)
+ coords_2d -= Vector((w * .5, 0))
+ blf.position(font_id, coords_2d[0], coords_2d[1], 0)
+ blf.draw(font_id, text) # Set your text here
+
+ def draw_geometry(self, context):
+ self.addon_prefs = context.preferences.addons["blenderbim"].preferences
+ selected_elements_color = self.addon_prefs.decorator_color_selected
+ unselected_elements_color = self.addon_prefs.decorator_color_unselected
+ special_elements_color = self.addon_prefs.decorator_color_special
+
+ gpu.state.point_size_set(6)
+ gpu.state.blend_set("ALPHA")
+
+ self.line_shader = gpu.shader.from_builtin("POLYLINE_UNIFORM_COLOR")
+ self.line_shader.bind() # required to be able to change uniforms of the shader
+ # POLYLINE_UNIFORM_COLOR specific uniforms
+ self.line_shader.uniform_float("viewportSize", (context.region.width, context.region.height))
+ self.line_shader.uniform_float("lineWidth", 2.0)
+
+ # general shader
+ self.shader = gpu.shader.from_builtin("UNIFORM_COLOR")
+
+ selected_vertices = [context.scene.BIMClashProperties.p1, context.scene.BIMClashProperties.p2]
+ selected_edges = []
+ if selected_vertices[0] != selected_vertices[1]:
+ selected_edges = [[0, 1]]
+
+ self.draw_batch("POINTS", selected_vertices, special_elements_color)
+ if selected_edges:
+ self.draw_batch("LINES", selected_vertices, special_elements_color, selected_edges)
diff --git a/src/blenderbim/blenderbim/bim/module/clash/operator.py b/src/blenderbim/blenderbim/bim/module/clash/operator.py
index a56a45f560..f32e7bfad3 100644
--- a/src/blenderbim/blenderbim/bim/module/clash/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/clash/operator.py
@@ -23,10 +23,11 @@ import bmesh
import logging
import numpy as np
import ifcopenshell
-from mathutils import Matrix, Vector
-from math import radians
-from blenderbim.bim.ifc import IfcStore
import blenderbim.tool as tool
+from math import radians
+from mathutils import Matrix, Vector
+from blenderbim.bim.ifc import IfcStore
+from blenderbim.bim.module.clash.decorator import ClashDecorator
class ExportClashSets(bpy.types.Operator):
@@ -45,18 +46,7 @@ class ExportClashSets(bpy.types.Operator):
def execute(self, context):
self.filepath = bpy.path.ensure_ext(self.filepath, ".json")
- clash_sets = []
- for clash_set in context.scene.BIMClashProperties.clash_sets:
- self.a = []
- self.b = []
- for ab in ["a", "b"]:
- for data in getattr(clash_set, ab):
- clash_source = {"file": data.name}
- if data.selector:
- clash_source["selector"] = data.selector
- clash_source["mode"] = data.mode
- getattr(self, ab).append(clash_source)
- clash_sets.append({"name": clash_set.name, "tolerance": clash_set.tolerance, "a": self.a, "b": self.b})
+ clash_sets = tool.Clash.export_clash_sets()
with open(self.filepath, "w") as destination:
destination.write(json.dumps(clash_sets, indent=4))
return {"FINISHED"}
@@ -78,25 +68,34 @@ class ImportClashSets(bpy.types.Operator):
return {"RUNNING_MODAL"}
def execute(self, context):
- with open(self.filepath) as f:
- clash_sets = json.load(f)
- for clash_set in clash_sets:
+ tool.Clash.load_clash_sets(self.filepath)
+ context.scene.BIMClashProperties.clash_sets.clear()
+ for clash_set in tool.Clash.get_clash_sets():
new = context.scene.BIMClashProperties.clash_sets.add()
new.name = clash_set["name"]
- new.tolerance = clash_set["tolerance"]
+ new.mode = clash_set["mode"]
+ if new.mode == "intersection":
+ new.tolerance = clash_set["tolerance"]
+ new.check_all = clash_set["check_all"]
+ elif new.mode == "collision":
+ new.allow_touching = clash_set["allow_touching"]
+ elif new.mode == "clearance":
+ new.clearance = clash_set["clearance"]
+ new.check_all = clash_set["check_all"]
for clash_source in clash_set["a"]:
new_source = new.a.add()
new_source.name = clash_source["file"]
if "selector" in clash_source:
- new_source.selector = clash_source["selector"]
+ tool.Search.import_filter_query(clash_source["selector"], new_source.filter_groups)
new_source.mode = clash_source["mode"]
- if clash_set["b"]:
+ if "b" in clash_set and clash_set["b"]:
for clash_source in clash_set["b"]:
new_source = new.b.add()
new_source.name = clash_source["file"]
if "selector" in clash_source:
- new_source.selector = clash_source["selector"]
+ tool.Search.import_filter_query(clash_source["selector"], new_source.filter_groups)
new_source.mode = clash_source["mode"]
+ tool.Clash.import_active_clashes()
return {"FINISHED"}
@@ -109,7 +108,6 @@ class AddClashSet(bpy.types.Operator):
def execute(self, context):
new = context.scene.BIMClashProperties.clash_sets.add()
new.name = "New Clash Set"
- new.tolerance = 0.01
return {"FINISHED"}
@@ -211,8 +209,11 @@ class ExecuteIfcClash(bpy.types.Operator):
def invoke(self, context, event):
_, extension = os.path.splitext(self.filepath)
- if extension != ".json":
- self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".bcf")
+ if extension != ".bcf":
+ self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".json")
+ # TODO Temporarily until BCF support comes back
+ # if extension != ".json":
+ # self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".bcf")
WindowManager = context.window_manager
WindowManager.fileselect_add(self)
return {"RUNNING_MODAL"}
@@ -220,7 +221,12 @@ class ExecuteIfcClash(bpy.types.Operator):
def execute(self, context):
from ifcclash import ifcclash
+ self.props = context.scene.BIMClashProperties
+
_, extension = os.path.splitext(self.filepath)
+ if extension != ".bcf":
+ self.filepath = bpy.path.ensure_ext(self.filepath, ".json")
+ # TODO Temporarily until BCF support comes back
if extension != ".json":
self.filepath = bpy.path.ensure_ext(self.filepath, ".bcf")
@@ -230,7 +236,7 @@ class ExecuteIfcClash(bpy.types.Operator):
settings.logger.setLevel(logging.DEBUG)
clasher = ifcclash.Clasher(settings)
- if context.scene.BIMClashProperties.should_create_clash_snapshots:
+ if self.props.should_create_clash_snapshots:
def get_viewpoint_snapshot(viewpoint):
camera = bpy.data.objects.get("IFC Clash Camera")
@@ -271,23 +277,13 @@ class ExecuteIfcClash(bpy.types.Operator):
clasher.get_viewpoint_snapshot = get_viewpoint_snapshot
- clasher.clash_sets = []
- for clash_set in context.scene.BIMClashProperties.clash_sets:
- self.a = []
- self.b = []
- for ab in ["a", "b"]:
- for data in getattr(clash_set, ab):
- clash_source = {"file": data.name}
- if data.selector:
- clash_source["selector"] = data.selector
- clash_source["mode"] = data.mode
- getattr(self, ab).append(clash_source)
- clash_set_data = {"name": clash_set.name, "tolerance": clash_set.tolerance, "a": self.a}
- if self.b:
- clash_set_data["b"] = self.b
- clasher.clash_sets.append(clash_set_data)
+ clasher.clash_sets = tool.Clash.export_clash_sets()
clasher.clash()
clasher.export()
+
+ if extension == ".json":
+ tool.Clash.load_clash_sets(self.filepath)
+ tool.Clash.import_active_clashes()
return {"FINISHED"}
@@ -306,6 +302,7 @@ class SelectIfcClashResults(bpy.types.Operator):
return {"RUNNING_MODAL"}
def execute(self, context):
+ # TODO refactor into new clash results system
self.file = IfcStore.get_file()
self.filepath = bpy.path.ensure_ext(self.filepath, ".json")
with open(self.filepath) as f:
@@ -352,83 +349,6 @@ class SelectIfcClashResults(bpy.types.Operator):
return {"FINISHED"}
-class LoadIfcClashes(bpy.types.Operator):
- bl_idname = "bim.load_ifc_clashes"
- bl_label = "Load IFC Clashes"
- bl_options = {"REGISTER", "UNDO"}
- bl_description = "Load the clashing IFC geometry stored in a file"
- filename_ext = ".json"
- filepath: bpy.props.StringProperty(subtype="FILE_PATH")
-
- def invoke(self, context, event):
- self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".json")
- WindowManager = context.window_manager
- WindowManager.fileselect_add(self)
- return {"RUNNING_MODAL"}
-
- def execute(self, context):
- self.file = IfcStore.get_file()
- self.filepath = bpy.path.ensure_ext(self.filepath, ".json")
- with open(self.filepath) as f:
- clash_sets_json = json.load(f)
- active_clash_set = context.scene.BIMClashProperties.active_clash_set
-
- for clash_set in clash_sets_json:
- if not "clashes" in clash_set.keys():
- self.report({"WARNING"}, "No clashes found for the selected Clash Set.")
- return {"CANCELLED"}
- active_clash_set.clashes.clear()
- for clash in clash_set["clashes"].values():
- blender_clash = active_clash_set.clashes.add()
- blender_clash.a_global_id = clash["a_global_id"]
- blender_clash.b_global_id = clash["b_global_id"]
- blender_clash.a_name = "{}/{}".format(clash["a_ifc_class"], clash["a_name"])
- blender_clash.b_name = "{}/{}".format(clash["b_ifc_class"], clash["b_name"])
- blender_clash.status = False if not "status" in clash.keys() else clash["status"]
- return {"FINISHED"}
-
-
-class SaveIfcClashes(bpy.types.Operator):
- bl_idname = "bim.save_ifc_clashes"
- bl_label = "Save IFC Clashes"
- bl_options = {"REGISTER", "UNDO"}
- bl_description = "Save the clashing IFC geometry stored in a file"
- filename_ext = ".json"
- filepath: bpy.props.StringProperty(subtype="FILE_PATH")
-
- def invoke(self, context, event):
- self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".json")
- WindowManager = context.window_manager
- WindowManager.fileselect_add(self)
- return {"RUNNING_MODAL"}
-
- def execute(self, context):
- self.file = IfcStore.get_file()
- self.filepath = bpy.path.ensure_ext(self.filepath, ".json")
- clash_sets_json = self.load_json()
- active_clash_set = context.scene.BIMClashProperties.active_clash_set
- self.update_clash_sets(clash_sets_json, active_clash_set)
- self.save_json(clash_sets_json)
- return {"FINISHED"}
-
- def load_json(self):
- with open(self.filepath) as f:
- return json.load(f)
-
- def update_clash_sets(self, clash_sets_json, active_clash_set):
- for clash_set in clash_sets_json:
- if clash_set["name"] != active_clash_set.name or "clashes" not in clash_set.keys():
- continue
- for clash in active_clash_set.clashes:
- clash_key = clash.a_global_id + "-" + clash.b_global_id
- if clash_set.get("clashes", {}).get(clash_key, None) is not None:
- clash_set["clashes"][clash_key]["status"] = clash.status
-
- def save_json(self, clash_sets_json):
- with open(self.filepath, "w") as destination:
- json.dump(clash_sets_json, destination, indent=4)
-
-
class SelectClash(bpy.types.Operator):
bl_idname = "bim.select_clash"
bl_label = "Select Clash"
@@ -437,31 +357,29 @@ class SelectClash(bpy.types.Operator):
index: bpy.props.IntProperty()
def execute(self, context):
- clash = context.scene.BIMClashProperties.active_clash
+ self.props = context.scene.BIMClashProperties
+ clash_set = tool.Clash.get_clash_set(self.props.active_clash_set.name)
+ active_clash = self.props.active_clash
+ clash = tool.Clash.get_clash(clash_set, active_clash.a_global_id, active_clash.b_global_id)
+
+ if not clash:
+ return {"FINISHED"}
products = []
- linked_objects = []
- try:
- products.append(tool.Ifc.get().by_guid(clash.a_global_id))
- except:
- linked_objects.append(clash.a_name)
- try:
- products.append(tool.Ifc.get().by_guid(clash.b_global_id))
- except:
- linked_objects.append(clash.b_name)
+
+ for global_id in (clash["a_global_id"], clash["b_global_id"]):
+ try:
+ products.append(tool.Ifc.get().by_guid(global_id))
+ except:
+ pass
tool.Spatial.select_products(products, unhide=True)
- print(linked_objects)
- for obj_name in linked_objects:
- obj = bpy.data.objects.get(obj_name)
- if obj:
- obj.select_set(True)
- else:
- print("Object not found:", obj_name)
- if context.scene.BIMClashProperties.sould_focus_on_clash:
- context_override = tool.Blender.get_viewport_context()
- with bpy.context.temp_override(**context_override):
- bpy.ops.view3d.view_selected()
+ ClashDecorator.install(bpy.context)
+ target = Vector(clash["p1"])
+ tool.Clash.look_at(target, target + Vector((5, 5, 5)))
+ self.props.p1 = clash["p1"]
+ self.props.p2 = clash["p2"]
+ self.props.active_clash_text = clash["type"].title() + " " + str(round(clash["distance"] * 1000)) + "mm"
return {"FINISHED"}
diff --git a/src/blenderbim/blenderbim/bim/module/clash/prop.py b/src/blenderbim/blenderbim/bim/module/clash/prop.py
index 6bd8120fed..0f7e7c2275 100644
--- a/src/blenderbim/blenderbim/bim/module/clash/prop.py
+++ b/src/blenderbim/blenderbim/bim/module/clash/prop.py
@@ -17,7 +17,7 @@
# along with BlenderBIM Add-on. If not, see .
import bpy
-from blenderbim.bim.prop import StrProperty, Attribute
+from blenderbim.bim.prop import StrProperty, Attribute, BIMFilterGroup
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
@@ -33,11 +33,12 @@ from bpy.props import (
class ClashSource(PropertyGroup):
name: StringProperty(name="File")
- selector: StringProperty(name="Selector")
+ filter_groups: CollectionProperty(type=BIMFilterGroup, name="Filter Groups")
mode: EnumProperty(
items=[
- ("i", "Include", "Only the selected objects are included for clashing"),
- ("e", "Exclude", "All objects except the selected objects are included for clashing"),
+ ("a", "All Elements", "All elements will be used for clashing"),
+ ("i", "Include", "Only the selected elements are included for clashing"),
+ ("e", "Exclude", "All elements except the selected elements are included for clashing"),
],
name="Mode",
)
@@ -53,7 +54,24 @@ class Clash(PropertyGroup):
class ClashSet(PropertyGroup):
name: StringProperty(name="Name")
- tolerance: FloatProperty(name="Tolerance")
+ mode: EnumProperty(
+ items=[
+ (
+ "intersection",
+ "Intersection",
+ "Detect objects that protrude or pierce another object",
+ "PIVOT_MEDIAN",
+ 1,
+ ),
+ ("collision", "Collision", "Detect touching objects with any surface collision", "PIVOT_INDIVIDUAL", 2),
+ ("clearance", "Clearance", "Detect objects within a proximity threshold", "PIVOT_ACTIVE", 3),
+ ],
+ name="Mode",
+ )
+ tolerance: FloatProperty(name="Tolerance", default=0.002)
+ clearance: FloatProperty(name="Clearance", default=0.01)
+ allow_touching: BoolProperty(name="Allow Touching", default=False)
+ check_all: BoolProperty(name="Check All", default=False)
a: CollectionProperty(name="Group A", type=ClashSource)
b: CollectionProperty(name="Group B", type=ClashSource)
clashes: CollectionProperty(name="Clashes", type=Clash)
@@ -78,22 +96,21 @@ class BIMClashProperties(PropertyGroup):
smart_clash_grouping_max_distance: IntProperty(
name="Smart Clash Grouping Max Distance", default=3, soft_min=1, soft_max=10
)
- sould_focus_on_clash: BoolProperty(name="Show Focus Clash", default=False)
+ p1: FloatVectorProperty(name="P1", default=(0.0, 0.0, 0.0), subtype="XYZ")
+ p2: FloatVectorProperty(name="P2", default=(0.0, 0.0, 0.0), subtype="XYZ")
+ active_clash_text: StringProperty(name="Active Clash Text")
@property
def active_clash_set(self):
- if not self.clash_sets:
- return None
- return self.clash_sets[self.active_clash_set_index]
+ if self.active_clash_set_index < len(self.clash_sets):
+ return self.clash_sets[self.active_clash_set_index]
@property
def active_smart_group(self):
- if not self.smart_clash_groups:
- return None
- return self.smart_clash_groups[self.active_smart_group_index]
+ if self.active_smart_group_index < len(self.smart_clash_groups):
+ return self.smart_clash_groups[self.active_smart_group_index]
@property
def active_clash(self):
- if not self.active_clash_set:
- return None
- return self.active_clash_set.clashes[self.active_clash_index]
+ if self.active_clash_index < len(self.active_clash_set.clashes):
+ return self.active_clash_set.clashes[self.active_clash_index]
diff --git a/src/blenderbim/blenderbim/bim/module/clash/ui.py b/src/blenderbim/blenderbim/bim/module/clash/ui.py
index 8b7ba0110f..d5322a568b 100644
--- a/src/blenderbim/blenderbim/bim/module/clash/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/clash/ui.py
@@ -17,7 +17,9 @@
# along with BlenderBIM Add-on. If not, see .
import bpy
+import blenderbim.bim.helper
from bpy.types import Panel
+from blenderbim.bim.module.clash.data import ClashData
class BIM_PT_ifcclash(Panel):
@@ -30,6 +32,9 @@ class BIM_PT_ifcclash(Panel):
bl_parent_id = "BIM_PT_tab_clash_detection"
def draw(self, context):
+ if not ClashData.is_loaded:
+ ClashData.load()
+
layout = self.layout
scene = context.scene
@@ -45,57 +50,83 @@ class BIM_PT_ifcclash(Panel):
layout.template_list("BIM_UL_clash_sets", "", props, "clash_sets", props, "active_clash_set_index")
- if props.active_clash_set_index < len(props.clash_sets):
- clash_set = props.active_clash_set
+ if not props.active_clash_set:
+ return
- row = layout.row(align=True)
- row.prop(clash_set, "name")
- row.operator("bim.remove_clash_set", icon="X", text="").index = props.active_clash_set_index
+ clash_set = props.active_clash_set
- row = layout.row(align=True)
+ row = layout.row(align=True)
+ row.prop(clash_set, "name")
+ row.operator("bim.remove_clash_set", icon="X", text="").index = props.active_clash_set_index
+
+ row = layout.row()
+ row.prop(clash_set, "mode")
+
+ if clash_set.mode == "intersection":
+ row = layout.row()
row.prop(clash_set, "tolerance")
-
- layout.label(text="Group A:")
row = layout.row()
- row.operator("bim.add_clash_source").group = "a"
-
- for index, source in enumerate(clash_set.a):
- row = layout.row(align=True)
- row.prop(source, "name", text="")
- op = row.operator("bim.select_clash_source", icon="FILE_FOLDER", text="")
- op.index = index
- op.group = "a"
- op = row.operator("bim.remove_clash_source", icon="X", text="")
- op.index = index
- op.group = "a"
-
- row = layout.row(align=True)
- row.prop(source, "mode", text="")
- row.prop(source, "selector", text="")
-
- layout.label(text="Group B:")
+ row.prop(clash_set, "check_all")
+ elif clash_set.mode == "collision":
row = layout.row()
- row.operator("bim.add_clash_source").group = "b"
-
- for index, source in enumerate(clash_set.b):
- row = layout.row(align=True)
- row.prop(source, "name", text="")
- op = row.operator("bim.select_clash_source", icon="FILE_FOLDER", text="")
- op.index = index
- op.group = "b"
- op = row.operator("bim.remove_clash_source", icon="X", text="")
- op.index = index
- op.group = "b"
-
- row = layout.row(align=True)
- row.prop(source, "mode", text="")
- row.prop(source, "selector", text="")
-
+ row.prop(clash_set, "allow_touching")
+ elif clash_set.mode == "clearance":
row = layout.row()
- row.prop(props, "should_create_clash_snapshots")
+ row.prop(clash_set, "clearance")
+ row = layout.row()
+ row.prop(clash_set, "check_all")
+
+ row = layout.row(align=True)
+ row.label(text="Group A:", icon="OUTLINER_OB_POINTCLOUD")
+ row.operator("bim.add_clash_source", icon="ADD", text="").group = "a"
+
+ for index, source in enumerate(clash_set.a):
row = layout.row(align=True)
- row.operator("bim.execute_ifc_clash")
- row.operator("bim.select_ifc_clash_results")
+ row.prop(source, "name", text="")
+ row.prop(source, "mode", text="")
+ op = row.operator("bim.select_clash_source", icon="FILE_FOLDER", text="")
+ op.index = index
+ op.group = "a"
+ op = row.operator("bim.remove_clash_source", icon="X", text="")
+ op.index = index
+ op.group = "a"
+
+ if source.mode != "a":
+ blenderbim.bim.helper.draw_filter(
+ layout, source.filter_groups, ClashData, f"clash_{props.active_clash_set_index}_a_{index}"
+ )
+
+ row = layout.row(align=True)
+ row.label(text="Group B:", icon="OUTLINER_OB_POINTCLOUD")
+ row.operator("bim.add_clash_source", icon="ADD", text="").group = "b"
+
+ for index, source in enumerate(clash_set.b):
+ row = layout.row(align=True)
+ row.prop(source, "name", text="")
+ row.prop(source, "mode", text="")
+ op = row.operator("bim.select_clash_source", icon="FILE_FOLDER", text="")
+ op.index = index
+ op.group = "b"
+ op = row.operator("bim.remove_clash_source", icon="X", text="")
+ op.index = index
+ op.group = "b"
+
+ if source.mode != "a":
+ blenderbim.bim.helper.draw_filter(
+ layout, source.filter_groups, ClashData, f"clash_{props.active_clash_set_index}_b_{index}"
+ )
+
+ row = layout.row()
+ row.prop(props, "should_create_clash_snapshots")
+ row = layout.row()
+ row.operator("bim.execute_ifc_clash")
+
+ row = layout.row()
+ row.label(text=f"{len(clash_set.clashes)} Clashes Found", icon="PIVOT_CURSOR")
+
+ layout.template_list("BIM_UL_clashes", "", props.active_clash_set, "clashes", props, "active_clash_index")
+ row = layout.row()
+ row.operator("bim.select_clash")
class BIM_PT_clash_manager(Panel):
@@ -111,29 +142,6 @@ class BIM_PT_clash_manager(Panel):
pass
-class BIM_PT_dumb_clash_manager(Panel):
- bl_idname = "BIM_PT_dumb_clash_manager"
- bl_label = "Dumb Manager"
- bl_options = {"DEFAULT_CLOSED"}
- bl_space_type = "PROPERTIES"
- bl_region_type = "WINDOW"
- bl_context = "scene"
- bl_parent_id = "BIM_PT_clash_manager"
-
- def draw(self, context):
- layout = self.layout
- props = context.scene.BIMClashProperties
-
- row = layout.row(align=True)
- row.operator("bim.load_ifc_clashes", text="LOAD IFC CLASHES", icon="IMPORT")
- row.operator("bim.save_ifc_clashes", text="SAVE CLASH RESULTS", icon="EXPORT")
- layout.template_list("BIM_UL_clashes", "", props.active_clash_set, "clashes", props, "active_clash_index")
- # bim.select_clash
- row = layout.row(align=True)
- row.operator("bim.select_clash", text="SELECT CLASH", icon="IMPORT")
- row.prop(props, "sould_focus_on_clash", icon="RESTRICT_VIEW_OFF")
-
-
class BIM_PT_smart_clash_manager(Panel):
bl_idname = "BIM_PT_smart_clash_manager"
bl_label = "Smart Clash Manager"
diff --git a/src/blenderbim/blenderbim/bim/module/classification/operator.py b/src/blenderbim/blenderbim/bim/module/classification/operator.py
index 607ba967f5..623aa59770 100644
--- a/src/blenderbim/blenderbim/bim/module/classification/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/classification/operator.py
@@ -20,6 +20,8 @@ import bpy
import json
import ifcopenshell
import ifcopenshell.api
+import ifcopenshell.util.classification
+import ifcopenshell.util.element
import blenderbim.tool as tool
import blenderbim.bim.helper
from blenderbim.bim.ifc import IfcStore
@@ -87,7 +89,7 @@ class AddManualClassificationReference(bpy.types.Operator, tool.Ifc.Operator):
reference = ifcopenshell.api.run(
"classification.add_reference",
tool.Ifc.get(),
- product=product,
+ products=[product],
classification=classification,
identification="X",
name="Unnamed",
@@ -292,20 +294,25 @@ class RemoveClassificationReference(bpy.types.Operator, tool.Ifc.Operator):
active_reference = tool.Ifc.get().by_id(self.reference)
identification = active_reference[1]
+ elements_by_references: dict[ifcopenshell.entity_instance, list[ifcopenshell.entity_instance]] = {}
for obj in objects:
- ifc_definition_id = blenderbim.bim.helper.get_obj_ifc_definition_id(context, obj, self.obj_type)
+ ifc_definition_id = tool.Blender.get_obj_ifc_definition_id(obj, self.obj_type, context)
element = tool.Ifc.get().by_id(ifc_definition_id)
references = ifcopenshell.util.classification.get_references(element, should_inherit=False)
for reference in references:
if (identification and reference[1] == identification) or (
not identification and reference == active_reference
):
- ifcopenshell.api.run(
- "classification.remove_reference",
- tool.Ifc.get(),
- reference=reference,
- product=element,
- )
+ elements_by_references.setdefault(reference, []).append(element)
+
+ if elements_by_references:
+ for reference, products in elements_by_references.items():
+ ifcopenshell.api.run(
+ "classification.remove_reference",
+ tool.Ifc.get(),
+ reference=reference,
+ products=products,
+ )
class EditClassificationReference(bpy.types.Operator, tool.Ifc.Operator):
@@ -357,15 +364,18 @@ class AddClassificationReference(bpy.types.Operator, tool.Ifc.Operator):
classification = element
break
- for obj in objects:
- ifc_definition_id = blenderbim.bim.helper.get_obj_ifc_definition_id(context, obj, self.obj_type)
- if not ifc_definition_id:
- continue
+ ifc_file = tool.Ifc.get()
+ products = [
+ ifc_file.by_id(ifc_definition_id)
+ for obj in objects
+ if (ifc_definition_id := tool.Blender.get_obj_ifc_definition_id(obj, self.obj_type, context))
+ ]
+ if products:
ifcopenshell.api.run(
"classification.add_reference",
tool.Ifc.get(),
reference=IfcStore.classification_file.by_id(self.reference),
- product=tool.Ifc.get().by_id(ifc_definition_id),
+ products=products,
classification=classification,
)
@@ -406,14 +416,14 @@ class AddClassificationReferenceFromBSDD(bpy.types.Operator, tool.Ifc.Operator):
classification.Location = bsdd_classification.domain_namespace_uri
for obj in objects:
- ifc_definition_id = blenderbim.bim.helper.get_obj_ifc_definition_id(context, obj, self.obj_type)
+ ifc_definition_id = tool.Blender.get_obj_ifc_definition_id(obj, self.obj_type, context)
if not ifc_definition_id:
continue
element = tool.Ifc.get().by_id(ifc_definition_id)
reference = ifcopenshell.api.run(
"classification.add_reference",
tool.Ifc.get(),
- product=element,
+ products=[element],
classification=classification,
identification=bsdd_classification.reference_code,
name=bsdd_classification.name,
diff --git a/src/blenderbim/blenderbim/bim/module/constraint/operator.py b/src/blenderbim/blenderbim/bim/module/constraint/operator.py
index 6ab81a4695..f461554c49 100644
--- a/src/blenderbim/blenderbim/bim/module/constraint/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/constraint/operator.py
@@ -114,7 +114,7 @@ class EditObjective(bpy.types.Operator):
ifcopenshell.api.run(
"constraint.edit_objective",
self.file,
- **{"objective": self.file.by_id(props.active_constraint_id), "attributes": attributes}
+ **{"objective": self.file.by_id(props.active_constraint_id), "attributes": attributes},
)
bpy.ops.bim.load_objectives()
return {"FINISHED"}
@@ -179,19 +179,17 @@ class AssignConstraint(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
- self.file = IfcStore.get_file()
+ self.file = tool.Ifc.get()
objs = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects
- for obj in objs:
- obj_id = obj.BIMObjectProperties.ifc_definition_id
- if not obj_id:
- continue
+ products = [self.file.by_id(obj_id) for obj in objs if (obj_id := obj.BIMObjectProperties.ifc_definition_id)]
+ if products:
ifcopenshell.api.run(
"constraint.assign_constraint",
self.file,
**{
- "product": self.file.by_id(obj_id),
+ "products": products,
"constraint": self.file.by_id(self.constraint),
- }
+ },
)
return {"FINISHED"}
@@ -207,18 +205,16 @@ class UnassignConstraint(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
- self.file = IfcStore.get_file()
+ self.file = tool.Ifc.get()
objs = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects
- for obj in objs:
- obj_id = obj.BIMObjectProperties.ifc_definition_id
- if not obj_id:
- continue
+ products = [self.file.by_id(obj_id) for obj in objs if (obj_id := obj.BIMObjectProperties.ifc_definition_id)]
+ if products:
ifcopenshell.api.run(
"constraint.unassign_constraint",
self.file,
**{
- "product": self.file.by_id(obj_id),
+ "products": products,
"constraint": self.file.by_id(self.constraint),
- }
+ },
)
return {"FINISHED"}
diff --git a/src/blenderbim/blenderbim/bim/module/covering/workspace.py b/src/blenderbim/blenderbim/bim/module/covering/workspace.py
index abae481529..cf4218a40e 100644
--- a/src/blenderbim/blenderbim/bim/module/covering/workspace.py
+++ b/src/blenderbim/blenderbim/bim/module/covering/workspace.py
@@ -191,11 +191,11 @@ class CoveringToolUI:
row = cls.layout.row(align=True)
row.label(text="", icon="FILE_3D")
prop_with_search(row, cls.props, "relating_type_id", text="")
- row.operator("bim.launch_type_manager", icon="LIGHTPROBE_GRID", text="")
+ row.operator("bim.launch_type_manager", icon=tool.Blender.TYPE_MANAGER_ICON, text="")
else:
row.label(text=f"No {AuthoringData.data['ifc_element_type']} Found", icon="ERROR")
row = cls.layout.row()
- row.operator("bim.launch_type_manager", icon="LIGHTPROBE_GRID", text="Launch Type Manager")
+ row.operator("bim.launch_type_manager", icon=tool.Blender.TYPE_MANAGER_ICON, text="Launch Type Manager")
@classmethod
def draw_basic_bim_tool_interface(cls):
diff --git a/src/blenderbim/blenderbim/bim/module/debug/__init__.py b/src/blenderbim/blenderbim/bim/module/debug/__init__.py
index 93f3713205..271950ea61 100644
--- a/src/blenderbim/blenderbim/bim/module/debug/__init__.py
+++ b/src/blenderbim/blenderbim/bim/module/debug/__init__.py
@@ -24,6 +24,7 @@ classes = (
operator.CopyDebugInformation,
operator.CreateAllShapes,
operator.CreateShapeFromStepId,
+ operator.DebugActiveDrawing,
operator.InspectFromObject,
operator.InspectFromStepId,
operator.OverrideDisplayType,
diff --git a/src/blenderbim/blenderbim/bim/module/debug/operator.py b/src/blenderbim/blenderbim/bim/module/debug/operator.py
index 1a1f80f123..c97791b6ca 100644
--- a/src/blenderbim/blenderbim/bim/module/debug/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/debug/operator.py
@@ -20,11 +20,14 @@ import os
import sys
import bpy
import time
+import random
import logging
import platform
import subprocess
import addon_utils
import ifcopenshell
+import ifcopenshell.api
+import ifcopenshell.util.element
import ifcopenshell.util.placement
import ifcopenshell.util.representation
import blenderbim.tool as tool
@@ -660,3 +663,104 @@ class PipInstall(bpy.types.Operator):
]
)
return {"FINISHED"}
+
+
+class DebugActiveDrawing(bpy.types.Operator):
+ bl_idname = "bim.debug_active_drawing"
+ bl_label = "Search Active Drawing For Failing Elements"
+ bl_description = (
+ "Will iterate over all visible drawing's objects, trying to narrow down the list of possible failing objects"
+ )
+
+ def execute(self, context: bpy.types.Context):
+ props = context.scene.DocProperties
+ drawing_item = props.drawings[props.active_drawing_index]
+ drawing = tool.Ifc.get().by_id(drawing_item.ifc_definition_id)
+
+ GREEN = "\033[92m"
+ CYAN = "\033[96m"
+ END = "\033[0m"
+ ATTEMPS = 10
+
+ # run create drawing with sync for once
+ # to make sure everything is actually in sync
+ try:
+ bpy.ops.bim.create_drawing(sync=True)
+ except:
+ pass
+ else:
+ self.report({"INFO"}, "No errors creating drawing, nothing to investigate.")
+ return {"FINISHED"}
+
+ all_elements = [e for obj in context.visible_objects if (e := tool.Ifc.get_entity(obj))]
+ all_elements = set(all_elements)
+
+ original_exclude = ifcopenshell.util.element.get_pset(drawing, "EPset_Drawing", "Exclude")
+ pset = tool.Pset.get_element_pset(drawing, "EPset_Drawing")
+
+ def drawing_fails_to_load(chunk_to_include: set) -> bool:
+ current_elements = all_elements - chunk_to_include
+ excluded_guids = ", ".join([e.GlobalId for e in current_elements if hasattr(e, "GlobalId")])
+ tool.Ifc.run("pset.edit_pset", pset=pset, properties={"Exclude": f"{original_exclude}, {excluded_guids}"})
+
+ try:
+ bpy.ops.bim.create_drawing(sync=False)
+ result = False
+ except Exception as e:
+ # print(e)
+ result = True
+
+ tool.Ifc.run("pset.edit_pset", pset=pset, properties={"Exclude": original_exclude})
+ return result
+
+ def test_elements(elements: list[ifcopenshell.entity_instance], attempts: int = ATTEMPS) -> None:
+ print(f"{CYAN}processing {len(elements)} elements{END}")
+ if not elements:
+ print(f"Empty list of elements, will stop...")
+ return
+
+ n_elements = len(elements)
+ middle = int(n_elements / 2)
+ chunk1, chunk2 = elements[:middle], elements[middle:]
+ test_chunk_1 = drawing_fails_to_load(set(chunk1))
+ test_chunk_2 = drawing_fails_to_load(set(chunk2))
+
+ if (
+ # both chunks do not fail anymore
+ (not test_chunk_1 and not test_chunk_2)
+ # or we have 1 element chunk that is still failing
+ or (test_chunk_1 and not chunk2)
+ or (test_chunk_2 and not chunk1)
+ ):
+ if attempts == 0 or n_elements in (1, 2):
+ print(f"{GREEN}Couldn't narrow it down any further.{END}")
+ print(f"It's some of the {n_elements} elements:")
+ print(elements)
+
+ print("Let's test excluding them...")
+ for element in elements:
+ test = drawing_fails_to_load(all_elements - {element})
+ if test:
+ print(f"{CYAN}Excluding element didn't fixed the drawing: {END}")
+ print(element)
+ else:
+ print(f"{GREEN}Excluding element fixed the drawing: {END}")
+ print(element)
+ else:
+ print(f"{CYAN}Will try to reshuffle elements and try again, attempt {ATTEMPS-attempts+1}/{ATTEMPS}")
+ attempts -= 1
+ random.shuffle(elements)
+ test_elements(elements, attempts)
+ return
+
+ # if chunk fails we need to investigate it further
+ if test_chunk_1:
+ test_elements(chunk1)
+
+ if test_chunk_2:
+ test_elements(chunk2)
+
+ test_elements(list(all_elements))
+
+ self.report({"INFO"}, "See system console for the results")
+ return {"FINISHED"}
diff --git a/src/blenderbim/blenderbim/bim/module/debug/ui.py b/src/blenderbim/blenderbim/bim/module/debug/ui.py
index 21a6e11f80..2fd503cf6f 100644
--- a/src/blenderbim/blenderbim/bim/module/debug/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/debug/ui.py
@@ -75,6 +75,9 @@ class BIM_PT_debug(Panel):
row = layout.row()
row.operator("bim.profile_import_ifc")
+ row = layout.row()
+ row.operator("bim.debug_active_drawing")
+
row = layout.split(factor=0.5, align=True)
row.operator("bim.create_shape_from_step_id").should_include_curves = False
row.operator("bim.create_shape_from_step_id", text="", icon="IPO_ELASTIC").should_include_curves = True
diff --git a/src/blenderbim/blenderbim/bim/module/diff/data.py b/src/blenderbim/blenderbim/bim/module/diff/data.py
index 8dfc47e615..d84b9c19a2 100644
--- a/src/blenderbim/blenderbim/bim/module/diff/data.py
+++ b/src/blenderbim/blenderbim/bim/module/diff/data.py
@@ -38,6 +38,7 @@ class DiffData:
cls.data["total_deleted"] = cls.total_deleted()
cls.data["total_changed"] = cls.total_changed()
cls.data["changes"] = cls.changes()
+ cls.data["saved_searches"] = cls.saved_searches()
cls.is_loaded = True
@classmethod
@@ -84,3 +85,18 @@ class DiffData:
elif element.GlobalId in diff["deleted"]:
changes["Deleted"] = True
return changes
+
+ @classmethod
+ def saved_searches(cls):
+ if not tool.Ifc.get():
+ return []
+ groups = tool.Ifc.get().by_type("IfcGroup")
+ results = []
+ for group in groups:
+ try:
+ data = json.loads(group.Description)
+ if isinstance(data, dict) and data.get("type", None) == "BBIM_Search" and data.get("query", None):
+ results.append(group)
+ except:
+ pass
+ return [(str(g.id()), g.Name or "Unnamed", "") for g in sorted(results, key=lambda x: x.Name or "Unnamed")]
diff --git a/src/blenderbim/blenderbim/bim/module/diff/operator.py b/src/blenderbim/blenderbim/bim/module/diff/operator.py
index a0bb7d0c2c..9f1e85f66c 100644
--- a/src/blenderbim/blenderbim/bim/module/diff/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/diff/operator.py
@@ -135,6 +135,7 @@ class ExecuteIfcDiff(bpy.types.Operator):
bl_label = "Execute IFC Diff"
filename_ext = ".json"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
+ filter_glob: bpy.props.StringProperty(default="*.json", options={"HIDDEN"})
def invoke(self, context, event):
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".json")
@@ -162,7 +163,7 @@ class ExecuteIfcDiff(bpy.types.Operator):
new = ifcopenshell.open(self.props.new_file)
relationships = [r.relationship for r in self.props.diff_relationships]
- query = self.props.diff_filter_elements
+ query = tool.Search.export_filter_query(self.props.filter_groups) or None
ifc_diff = ifcdiff.IfcDiff(old, new, relationships=relationships, filter_elements=query)
ifc_diff.diff()
diff --git a/src/blenderbim/blenderbim/bim/module/diff/prop.py b/src/blenderbim/blenderbim/bim/module/diff/prop.py
index 8a1522e771..c6b0a06c91 100644
--- a/src/blenderbim/blenderbim/bim/module/diff/prop.py
+++ b/src/blenderbim/blenderbim/bim/module/diff/prop.py
@@ -17,7 +17,7 @@
# along with BlenderBIM Add-on. If not, see .
import bpy
-from blenderbim.bim.prop import StrProperty
+from blenderbim.bim.prop import StrProperty, BIMFilterGroup
from blenderbim.bim.module.diff.data import DiffData
from bpy.types import PropertyGroup
from bpy.props import (
@@ -48,7 +48,7 @@ class DiffProperties(PropertyGroup):
old_file: StringProperty(default="", name="Old IFC File")
new_file: StringProperty(default="", name="New IFC File")
diff_relationships: CollectionProperty(type=Relationships, name="Relationships")
- diff_filter_elements: StringProperty(default="", name="Filter")
+ filter_groups: CollectionProperty(type=BIMFilterGroup, name="Filter Groups")
should_load_changed_elements: BoolProperty(name="Load Changed Elements", default=True)
active_file: EnumProperty(
items=[
diff --git a/src/blenderbim/blenderbim/bim/module/diff/ui.py b/src/blenderbim/blenderbim/bim/module/diff/ui.py
index d91f0eb9a0..6593e7cd6f 100644
--- a/src/blenderbim/blenderbim/bim/module/diff/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/diff/ui.py
@@ -19,8 +19,8 @@
from bpy.types import Panel
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.diff.data import DiffData
+import blenderbim.bim.helper
import blenderbim.tool as tool
-import json
class BIM_PT_diff(Panel):
@@ -93,9 +93,7 @@ class BIM_PT_diff(Panel):
remove.collection = "diff_relationships"
remove.index = index
- row = layout.row(align=True)
- row.prop(props, "diff_filter_elements")
- row.operator("bim.ifc_selector", icon="FILTER", text="")
+ blenderbim.bim.helper.draw_filter(self.layout, props.filter_groups, DiffData, "diff")
row = layout.row()
row.operator("bim.execute_ifc_diff")
diff --git a/src/blenderbim/blenderbim/bim/module/drawing/__init__.py b/src/blenderbim/blenderbim/bim/module/drawing/__init__.py
index 61af897a73..18227bed86 100644
--- a/src/blenderbim/blenderbim/bim/module/drawing/__init__.py
+++ b/src/blenderbim/blenderbim/bim/module/drawing/__init__.py
@@ -41,6 +41,7 @@ classes = (
operator.CleanWireframes,
operator.ContractSheet,
operator.ContractTargetView,
+ operator.ConvertSVGToDXF,
operator.CreateDrawing,
operator.CreateSheets,
operator.DisableAddAnnotationType,
@@ -126,7 +127,7 @@ def register():
bpy.types.Object.BIMAssignedProductProperties = bpy.props.PointerProperty(type=prop.BIMAssignedProductProperties)
bpy.types.Object.BIMTextProperties = bpy.props.PointerProperty(type=prop.BIMTextProperties)
bpy.types.TextCurve.BIMTextProperties = bpy.props.PointerProperty(type=prop.BIMTextProperties)
- bpy.app.handlers.load_post.append(handler.toggleDecorationsOnLoad)
+ bpy.app.handlers.load_post.append(handler.load_post)
bpy.app.handlers.depsgraph_update_pre.append(handler.depsgraph_update_pre_handler)
bpy.types.VIEW3D_MT_image_add.append(ui.add_object_button)
@@ -140,6 +141,6 @@ def unregister():
del bpy.types.Object.BIMAssignedProductProperties
del bpy.types.Object.BIMTextProperties
del bpy.types.TextCurve.BIMTextProperties
- bpy.app.handlers.load_post.remove(handler.toggleDecorationsOnLoad)
+ bpy.app.handlers.load_post.remove(handler.load_post)
bpy.app.handlers.depsgraph_update_pre.remove(handler.depsgraph_update_pre_handler)
bpy.types.VIEW3D_MT_image_add.remove(ui.add_object_button)
diff --git a/src/blenderbim/blenderbim/bim/module/drawing/data.py b/src/blenderbim/blenderbim/bim/module/drawing/data.py
index d9b493cae0..61652baa8e 100644
--- a/src/blenderbim/blenderbim/bim/module/drawing/data.py
+++ b/src/blenderbim/blenderbim/bim/module/drawing/data.py
@@ -23,6 +23,7 @@ import ifcopenshell.util.element
import ifcopenshell.util.representation
import blenderbim.tool as tool
from pathlib import Path
+from typing import Any, Union
def refresh():
@@ -234,9 +235,9 @@ class DecoratorData:
cut_cache = {}
layerset_cache = {}
- # used by Ifc Annotations with ObjectType = "BATTING"
@classmethod
def get_batting_thickness(cls, obj):
+ """used by IfcAnnotations with ObjectType = "BATTING" """
result = cls.data.get(obj.name, None)
if result is not None:
return result
@@ -248,9 +249,9 @@ class DecoratorData:
cls.data[obj.name] = thickness
return thickness
- # used by Ifc Annotations with ObjectType = "SECTION"
@classmethod
def get_section_markers_display_data(cls, obj):
+ """used by IfcAnnotations with ObjectType = "SECTION" """
result = cls.data.get(obj.name, None)
if result is not None:
return result
@@ -290,10 +291,10 @@ class DecoratorData:
cls.data[obj.name] = display_data
return display_data
- # used by Ifc Annotations with ObjectType = "TEXT" / "TEXT_LEADER"
@classmethod
- def get_ifc_text_data(cls, obj):
- """returns font size in mm for current ifc text object"""
+ def get_ifc_text_data(cls, obj: bpy.types.Object) -> dict[str, Any]:
+ """used by Ifc Annotations with ObjectType = "TEXT" / "TEXT_LEADER"\n
+ returns font size in mm for current ifc text object"""
result = cls.data.get(obj.name, None)
if result is not None:
return result
@@ -317,7 +318,7 @@ class DecoratorData:
font_size = FONT_SIZES[font_size_type]
# get symbol
- symbol = pset_data.get("Symbol", None)
+ symbol = tool.Drawing.get_annotation_symbol(element)
# other attributes
props_literals = props.literals
@@ -340,6 +341,11 @@ class DecoratorData:
cls.data[obj.name] = text_data
return text_data
+ @classmethod
+ def get_symbol(cls, obj: bpy.types.Object) -> Union[str, None]:
+ """used by IfcAnnotations with ObjectType MULTI_SYMBOL"""
+ return tool.Drawing.get_annotation_symbol(tool.Ifc.get_entity(obj))
+
@classmethod
def get_dimension_data(cls, obj):
"""used by Ifc Annotations with ObjectType:
@@ -412,10 +418,12 @@ class AnnotationData:
relating_types = []
for relating_type in tool.Ifc.get().by_type("IfcTypeProduct"):
if tool.Drawing.is_annotation_object_type(relating_type, object_type):
- relating_types.append({
- "id": relating_type.id(),
- "name": relating_type.Name or "Unnamed",
- "description": relating_type.Description or "No Description",
- })
+ relating_types.append(
+ {
+ "id": relating_type.id(),
+ "name": relating_type.Name or "Unnamed",
+ "description": relating_type.Description or "No Description",
+ }
+ )
return sorted(relating_types, key=lambda x: x["name"])
diff --git a/src/blenderbim/blenderbim/bim/module/drawing/decoration.py b/src/blenderbim/blenderbim/bim/module/drawing/decoration.py
index bee1b3c318..663b060db9 100644
--- a/src/blenderbim/blenderbim/bim/module/drawing/decoration.py
+++ b/src/blenderbim/blenderbim/bim/module/drawing/decoration.py
@@ -37,6 +37,7 @@ from blenderbim.bim.module.drawing.shaders import add_verts_sequence, add_offset
from blenderbim.bim.module.drawing.helper import format_distance
from timeit import default_timer as timer
from functools import lru_cache
+from typing import Optional, Iterator, Type, Union
UNSPECIAL_ELEMENT_COLOR = (0.2, 0.2, 0.2, 1) # GREY
@@ -69,7 +70,7 @@ class profile_consequential:
cls.lines = []
-def worldspace_to_winspace(verts, context):
+def worldspace_to_winspace(verts: list[Vector], context: bpy.types.Context) -> list[Vector]:
"""Convert world space verts to window space"""
region = context.region
region3d = context.region_data
@@ -80,7 +81,7 @@ def worldspace_to_winspace(verts, context):
return winspace_verts
-def winspace_to_worldspace(verts, context):
+def winspace_to_worldspace(verts: list[Vector], context: bpy.types.Context) -> list[Vector]:
"""Convert winspace verts to world space"""
region = context.region
region3d = context.region_data
@@ -121,17 +122,18 @@ def get_callout_head(edge_dir, edge_side, callout_size, callout_gap):
return head
-def get_circle_head(size, segments=20):
+def get_circle_head(size: float, segments: int = 20, rotation: float = 0.0) -> list[Vector]:
+ """`rotation` value in radians"""
angle_d = 2 * pi / segments
head = []
for i in range(segments):
- angle = angle_d * i
+ angle = angle_d * i + rotation
head.append(Vector([cos(angle), sin(angle), 0]) * size)
return head
-def get_circle_head_asterisk(size, segments=6):
- circle_head = get_circle_head(size, segments)
+def get_circle_head_asterisk(size: float, segments: int = 6, rotation: float = 0.0) -> Iterator[tuple[Vector, Vector]]:
+ circle_head = get_circle_head(size, segments, rotation)
middle = segments // 2
return zip(circle_head[:middle], circle_head[middle:])
@@ -264,7 +266,7 @@ class BaseDecorator:
vertices = [obj.matrix_world @ v.co for v in bm.verts]
return vertices, indices
- def decorate(self, context, obj):
+ def decorate(self, context: bpy.types.Context, obj: bpy.types.Object):
"""perform actual drawing stuff"""
raise NotImplementedError()
@@ -310,13 +312,28 @@ class BaseDecorator:
self.draw_lines(context, obj, output_verts, output_edges)
- def draw_batch(self, shader_type, content_pos, color, indices=None):
+ def draw_batch(
+ self,
+ shader_type: str,
+ content_pos: list[Vector],
+ color: tuple[float, float, float, float],
+ indices: Optional[list[tuple[int, int]]] = None,
+ ) -> None:
shader = self.line_shader if shader_type == "LINES" else self.base_shader
batch = batch_for_shader(shader, shader_type, {"pos": content_pos}, indices=indices)
shader.uniform_float("color", color)
batch.draw(shader)
- def draw_lines(self, context, obj, vertices, indices, color=None, line_width=1.0):
+ def draw_lines(
+ self,
+ context: bpy.types.Context,
+ obj: Optional[bpy.types.Object],
+ vertices: list[Vector],
+ indices: list[tuple[int, int]],
+ color: Optional[tuple[float, float, float, float]] = None,
+ line_width: float = 1.0,
+ ) -> None:
+ # TODO: `obj` is actually never used, need to remove it
"""`verts` should be in winspace with `(0,0,0)` in the screen left bottom corner, not in the center"""
region = context.region
if not color:
@@ -329,7 +346,7 @@ class BaseDecorator:
gpu.state.blend_set("ALPHA")
self.draw_batch("LINES", vertices, color, indices)
- def get_viewport_drawing_scale(self, context):
+ def get_viewport_drawing_scale(self, context: bpy.types.Context) -> float:
# Horrific prototype code
factor = self.camera_zoom_to_factor(context.space_data.region_3d.view_camera_zoom)
camera_width_px = factor * context.region.width
@@ -485,14 +502,16 @@ class BaseDecorator:
decimal_places = drawing_pset_data.get("DecimalPlaces", None)
return format_distance(value, precision=precision, decimal_places=decimal_places)
- def draw_asterisk(self, context, obj):
+ def draw_asterisk(self, context: bpy.types.Context, pos: Vector, rotation: float = 0.0, scale: float = 1.0) -> None:
+ """`pos` is a world space position\n
+ `rotation` value in radians"""
# gather geometry data and convert to winspace
- verts = [obj.location]
+ verts = [pos]
viewportDrawingScale = self.get_viewport_drawing_scale(context)
winspace_verts = worldspace_to_winspace(verts, context)
# setup geometry parameters
- circle_size = viewportDrawingScale * 4
+ circle_size = viewportDrawingScale * 4 * scale
output_verts = []
output_edges = []
@@ -502,20 +521,13 @@ class BaseDecorator:
}
v0 = winspace_verts[0]
- asterisk_head = get_circle_head_asterisk(circle_size)
+ asterisk_head = get_circle_head_asterisk(circle_size, rotation=rotation)
start_i = 0
for segment in asterisk_head:
start_i = add_verts_sequence(add_offsets(v0, segment), start_i, **out_kwargs)
- self.draw_lines(context, obj, output_verts, output_edges)
+ self.draw_lines(context, None, output_verts, output_edges)
- def draw_text(self, context, obj, text_world_position=None, reverse_lines_order=False):
- """if `text_world_position` is not provided, the object's location will be used"""
-
- if not text_world_position:
- text_world_position = obj.location
-
- region = context.region
- region3d = context.region_data
+ def get_annotation_direction(self, context: bpy.types.Context, obj: bpy.types.Object) -> Vector:
camera = context.scene.camera
def get_basis_vector(matrix, i=0):
@@ -523,10 +535,57 @@ class BaseDecorator:
return matrix.inverted()[i].to_3d().normalized()
text_dir_world_x_axis = get_basis_vector(obj.matrix_world)
-
camera_matrix = tool.Drawing.get_camera_matrix(camera)
-
text_dir = (camera_matrix.inverted().to_quaternion() @ text_dir_world_x_axis).to_2d().normalized()
+ return text_dir
+
+ def draw_symbol(self, context: bpy.types.Context, obj: bpy.types.Object, annotation_dir: Vector) -> None:
+ if obj.type not in ("MESH", "EMPTY"):
+ return
+
+ if obj.type == "MESH":
+ mesh: bpy.types.Mesh = obj.data
+
+ if len(mesh.vertices) == 0:
+ return
+
+ if mesh.edges:
+ MiscDecorator.decorate(self, context, obj)
+ return
+
+ symbol = DecoratorData.get_symbol(obj)
+ if not symbol:
+ return
+
+ for vert in mesh.vertices:
+ self.draw_asterisk(context, obj.matrix_world @ vert.co)
+
+ return
+
+ # EMPTY objects
+ symbol = DecoratorData.get_symbol(obj)
+ if not symbol:
+ return
+
+ rotation = -Vector((1, 0)).angle_signed(annotation_dir)
+ # NOTE: for now we assume that scale is uniform
+ self.draw_asterisk(context, obj.location, rotation, obj.scale.x)
+
+ def draw_text(
+ self,
+ context: bpy.types.Context,
+ obj: bpy.types.Object,
+ text_world_position: Optional[Vector] = None,
+ reverse_lines_order=False,
+ ) -> None:
+ """if `text_world_position` is not provided, the object's location will be used"""
+
+ if not text_world_position:
+ text_world_position = obj.location
+
+ region = context.region
+ region3d = context.region_data
+ text_dir = self.get_annotation_direction(context, obj)
pos = location_3d_to_region_2d(region, region3d, text_world_position)
props = obj.BIMTextProperties
@@ -539,8 +598,7 @@ class BaseDecorator:
# draw asterisk symbol to indicate that there is some symbol that's not shown in viewport
if symbol:
- self.draw_asterisk(context, obj)
- # NOTE: for now we assume that scale is uniform
+ self.draw_symbol(context, obj, text_dir)
text_scale = obj.scale.x
line_i = 0
@@ -548,7 +606,7 @@ class BaseDecorator:
for literal_data in literals_data:
box_alignment = literal_data["BoxAlignment"]
- for line in literal_data["CurrentValue"].split("\\n"):
+ for line in literal_data["CurrentValue"].split("\n"):
self.draw_label(
context,
line,
@@ -812,8 +870,9 @@ class AngleDecorator(BaseDecorator):
base_edge = edge0 if tool.Cad.is_counter_clockwise_order(p0, p1, p2) else edge1
text_offset = (Matrix.Rotation(-angle_rad / 2, 2) @ base_edge).normalized() * (radius + ANGLE_LABEL_OFFSET)
label_position = p1 + text_offset
-
- text = f"{int(angle)}deg"
+ drawing_pset_data = DrawingsData.data["active_drawing_pset_data"]
+ decimal_places = drawing_pset_data.get("AngleDecimalPlaces", None)
+ text = f"{round(angle, decimal_places)}°"
label_dir = Vector((1, 0))
self.draw_label(context, text, label_position, label_dir, box_alignment="center")
@@ -1003,7 +1062,7 @@ class StairDecorator(BaseDecorator):
class MiscDecorator(BaseDecorator):
objecttype = "MISC"
- def decorate(self, context, obj):
+ def decorate(self, context: bpy.types.Context, obj: bpy.types.Object):
if obj.data.is_editmode:
verts, idxs = self.get_editmesh_geom(obj)
else:
@@ -1505,6 +1564,14 @@ class TextDecorator(BaseDecorator):
self.draw_text(context, obj)
+class SymbolDecorator(BaseDecorator):
+ objecttype = "SYMBOL"
+
+ def decorate(self, context: bpy.types.Context, obj: bpy.types.Object) -> None:
+ annotation_dir = self.get_annotation_direction(context, obj)
+ self.draw_symbol(context, obj, annotation_dir)
+
+
class CutDecorator:
installed = None
cache = {}
@@ -1901,7 +1968,7 @@ class CutDecorator:
class DecorationsHandler:
- decorators_classes = [
+ decorators_classes: list[Type[BaseDecorator]] = [
DimensionDecorator,
AngleDecorator,
GridDecorator,
@@ -1918,6 +1985,7 @@ class DecorationsHandler:
SectionDecorator,
ElevationDecorator,
TextDecorator,
+ SymbolDecorator,
BattingDecorator,
FallDecorator,
]
@@ -1945,6 +2013,7 @@ class DecorationsHandler:
self.decorators = {cls.objecttype: cls() for cls in self.decorators_classes}
for object_type in ("SLOPE_ANGLE", "SLOPE_FRACTION", "SLOPE_PERCENT"):
self.decorators[object_type] = self.decorators["FALL"]
+ self.decorators["MULTI_SYMBOL"] = self.decorators["SYMBOL"]
def get_objects_and_decorators(self, collection):
# TODO: do it in data instead of the handler for performance?
@@ -1962,7 +2031,7 @@ class DecorationsHandler:
if not element.is_a("IfcAnnotation"):
continue
- object_type = element.ObjectType
+ object_type: Union[str, None] = element.ObjectType
if object_type == "DRAWING":
continue
diff --git a/src/blenderbim/blenderbim/bim/module/drawing/handler.py b/src/blenderbim/blenderbim/bim/module/drawing/handler.py
index d553fcb113..9e45415d8f 100644
--- a/src/blenderbim/blenderbim/bim/module/drawing/handler.py
+++ b/src/blenderbim/blenderbim/bim/module/drawing/handler.py
@@ -23,7 +23,7 @@ from bpy.app.handlers import persistent
@persistent
-def toggleDecorationsOnLoad(*args):
+def load_post(*args):
if bpy.context.scene.DocProperties.should_draw_decorations:
decoration.DecorationsHandler.install(bpy.context)
else:
diff --git a/src/blenderbim/blenderbim/bim/module/drawing/helper.py b/src/blenderbim/blenderbim/bim/module/drawing/helper.py
index 043c5d7bbd..70a066c2f7 100644
--- a/src/blenderbim/blenderbim/bim/module/drawing/helper.py
+++ b/src/blenderbim/blenderbim/bim/module/drawing/helper.py
@@ -188,7 +188,6 @@ def format_distance(
feet += 1
inches = 0
-
if not isArea:
add_inches = bool(inches) or not suppress_zero_inches
tx_dist = ""
@@ -299,7 +298,9 @@ def parse_diagram_scale(camera):
return float(numerator) / float(denominator)
-def ortho_view_frame(camera, margin=0.015):
+def ortho_view_frame(
+ camera: bpy.types.Camera, margin: float = 0.015
+) -> tuple[float, float, float, float, float, float]:
"""Calculates 2d bounding box of camera view area.
Similar to `bpy.types.Camera.view_frame`
diff --git a/src/blenderbim/blenderbim/bim/module/drawing/operator.py b/src/blenderbim/blenderbim/bim/module/drawing/operator.py
index 9702b9188a..1d5e751062 100644
--- a/src/blenderbim/blenderbim/bim/module/drawing/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/drawing/operator.py
@@ -44,7 +44,7 @@ import blenderbim.bim.module.drawing.helper as helper
import blenderbim.bim.export_ifc
from blenderbim.bim.module.drawing.decoration import CutDecorator
from blenderbim.bim.module.drawing.data import DecoratorData, DrawingsData
-from typing import NamedTuple, List
+from typing import NamedTuple, List, Union, Optional
from lxml import etree
from mathutils import Vector, Color, Matrix
from timeit import default_timer as timer
@@ -199,6 +199,11 @@ class CreateDrawing(bpy.types.Operator):
+ "SHIFT+CLICK to print all selected drawings"
)
print_all: bpy.props.BoolProperty(name="Print All", default=False, options={"SKIP_SAVE"})
+ sync: bpy.props.BoolProperty(
+ name="Sync Before Creating Drawing",
+ description="Could save some time if you're sure IFC and current Blender session are already in sync",
+ default=True,
+ )
@classmethod
def poll(cls, context):
@@ -220,7 +225,8 @@ class CreateDrawing(bpy.types.Operator):
else:
drawings_to_print = [self.props.active_drawing_id]
- for drawing_id in drawings_to_print:
+ for drawing_i, drawing_id in enumerate(drawings_to_print):
+ self.drawing_index = drawing_i
if self.print_all:
bpy.ops.bim.activate_drawing(drawing=drawing_id, camera_view_point=False)
@@ -335,6 +341,13 @@ class CreateDrawing(bpy.types.Operator):
if not ifcopenshell.util.element.get_pset(self.drawing, "EPset_Drawing", "HasUnderlay"):
return
svg_path = self.get_svg_path(cache_type="underlay")
+ if os.path.isfile(svg_path) and self.props.should_use_underlay_cache:
+ return svg_path
+
+ visible_object_names = {obj.name for obj in bpy.context.visible_objects}
+ for obj in bpy.context.view_layer.objects:
+ obj.hide_render = obj.name not in visible_object_names
+
context.scene.render.filepath = svg_path[0:-4] + ".png"
drawing_style = context.scene.DocProperties.drawing_styles[self.cprops.active_drawing_style_index]
@@ -445,8 +458,14 @@ class CreateDrawing(bpy.types.Operator):
return LineworkContexts(body_contexts, annotation_contexts)
def serialize_contexts_elements(
- self, ifc, tree: ifcopenshell.geom.tree, contexts: LineworkContexts, context_type, drawing_elements, target_view
- ):
+ self,
+ ifc: ifcopenshell.file,
+ tree: ifcopenshell.geom.tree,
+ contexts: LineworkContexts,
+ context_type: str,
+ drawing_elements: set[ifcopenshell.entity_instance],
+ target_view: str,
+ ) -> None:
drawing_elements = drawing_elements.copy()
contexts = getattr(contexts, context_type)
for context in contexts:
@@ -472,20 +491,24 @@ class CreateDrawing(bpy.types.Operator):
tree.add_element(elem)
drawing_elements -= processed
- def generate_linework(self, context):
+ def generate_linework(self, context: bpy.types.Context) -> Union[str, None]:
if not ifcopenshell.util.element.get_pset(self.drawing, "EPset_Drawing", "HasLinework"):
return
svg_path = self.get_svg_path(cache_type="linework")
if os.path.isfile(svg_path) and self.props.should_use_linework_cache:
return svg_path
- with profile("sync"):
- # All very hackish whilst prototyping
- exporter = blenderbim.bim.export_ifc.IfcExporter(None)
- exporter.file = tool.Ifc.get()
- invalidated_elements = exporter.sync_all_objects()
- invalidated_elements += exporter.sync_edited_objects()
- invalidated_guids = [e.GlobalId for e in invalidated_elements if hasattr(e, "GlobalId")]
+ # in case of printing multiple drawings we need to sync just once
+ if self.sync and self.drawing_index == 0:
+ with profile("sync"):
+ # All very hackish whilst prototyping
+ exporter = blenderbim.bim.export_ifc.IfcExporter(None)
+ exporter.file = tool.Ifc.get()
+ invalidated_elements = exporter.sync_all_objects(skip_unlinking=True)
+ invalidated_elements += exporter.sync_edited_objects()
+ invalidated_guids = [e.GlobalId for e in invalidated_elements if hasattr(e, "GlobalId")]
+ cache = IfcStore.get_cache()
+ [cache.remove(guid) for guid in invalidated_guids]
# If we have already calculated it in the SVG in the past, don't recalculate
edited_guids = set()
@@ -506,6 +529,7 @@ class CreateDrawing(bpy.types.Operator):
for ifc_path, ifc in files.items():
# Don't use draw.main() just whilst we're prototyping and experimenting
+ # TODO: hash paths are never used
ifc_hash = hashlib.md5(ifc_path.encode("utf-8")).hexdigest()
ifc_cache_path = os.path.join(context.scene.BIMProperties.data_dir, "cache", f"{ifc_hash}.h5")
@@ -518,8 +542,6 @@ class CreateDrawing(bpy.types.Operator):
drawing_elements = tool.Drawing.get_drawing_elements(self.camera_element)
self.setup_serialiser(ifc, target_view)
- cache = IfcStore.get_cache()
- [cache.remove(guid) for guid in invalidated_guids]
tree = ifcopenshell.geom.tree()
tree.enable_face_styles(True)
@@ -1115,14 +1137,14 @@ class CreateDrawing(bpy.types.Operator):
continue
return space
- def get_material_name(self, element):
+ def get_material_name(self, element: ifcopenshell.entity_instance) -> str:
if hasattr(element, "Name") and element.Name:
return element.Name
elif hasattr(element, "LayerSetName") and element.LayerSetName:
return element.LayerSetName
return "mat-" + str(element.id())
- def get_svg_path(self, cache_type=None):
+ def get_svg_path(self, cache_type: Optional[str] = None) -> str:
drawing_path = tool.Drawing.get_document_uri(self.camera_document)
drawings_dir = os.path.dirname(drawing_path)
@@ -1345,7 +1367,7 @@ class SelectAllDrawings(bpy.types.Operator):
bl_label = "Select All Drawings"
view: bpy.props.StringProperty()
bl_description = "Select all drawings in the drawing list.\n\n" + "SHIFT+CLICK to deselect all drawings"
- select_all: bpy.props.BoolProperty(name="Open All", default=False, options={"SKIP_SAVE"})
+ select_all: bpy.props.BoolProperty(name="Open All", default=True, options={"SKIP_SAVE"})
def invoke(self, context, event):
# deselect all drawings on shift+click
@@ -1451,10 +1473,6 @@ class ActivateModel(bpy.types.Operator):
class ActivateDrawing(bpy.types.Operator):
- """
- Activates the selected drawing view
- """
-
bl_idname = "bim.activate_drawing"
bl_label = "Activate Drawing"
bl_options = {"REGISTER", "UNDO"}
@@ -1600,8 +1618,14 @@ class ReloadDrawingStyles(bpy.types.Operator):
json_path = Path(tool.Ifc.resolve_uri(rel_path))
if not json_path.exists():
- self.report({"ERROR"}, "Shading styles file not found: {}".format(json_path))
- return {"CANCELLED"}
+ ootb_resource = Path(context.scene.BIMProperties.data_dir) / "assets" / "shading_styles.json"
+ print(
+ f"WARNING. Couldn't find shading_styles for the drawing by the path: {json_path}. "
+ f"Default BBIM resource will be copied from {ootb_resource}"
+ )
+ if ootb_resource.exists():
+ os.makedirs(json_path.parent, exist_ok=True)
+ shutil.copy(ootb_resource, json_path)
with open(json_path, "r") as fi:
shading_styles_json = json.load(fi)
@@ -2546,8 +2570,16 @@ class EnableEditingElementFilter(bpy.types.Operator, Operator):
def _execute(self, context):
obj = bpy.context.scene.camera
- if obj:
- obj.data.BIMCameraProperties.filter_mode = self.filter_mode
+ if not obj:
+ return
+ obj.data.BIMCameraProperties.filter_mode = self.filter_mode
+ element = tool.Ifc.get_entity(obj)
+ if query := ifcopenshell.util.element.get_pset(element, "EPset_Drawing", self.filter_mode.title()):
+ filter_groups = tool.Search.get_filter_groups(f"drawing_{self.filter_mode.lower()}")
+ try:
+ tool.Search.import_filter_query(query, filter_groups)
+ except:
+ pass
class EditElementFilter(bpy.types.Operator, Operator):
@@ -2689,3 +2721,50 @@ class AddReferenceImage(bpy.types.Operator, Operator):
)
tool.Style.reload_material_from_ifc(material)
tool.Geometry.record_object_materials(obj)
+
+
+class ConvertSVGToDXF(bpy.types.Operator):
+ bl_idname = "bim.convert_svg_to_dxf"
+ bl_label = "Convert SVG to DXF"
+ bl_options = {"REGISTER", "UNDO"}
+ view: bpy.props.StringProperty()
+ bl_description = "Convert current drawing's .svg to .dxf.\n\nSHIFT+CLICK to convert all selected drawings"
+ convert_all: bpy.props.BoolProperty(name="Convert All", default=False, options={"SKIP_SAVE"})
+
+ def invoke(self, context, event):
+ # convert all drawings on shift+click
+ # make sure to use SKIP_SAVE on property, otherwise it might get stuck
+ if event.type == "LEFTMOUSE" and event.shift:
+ self.open_all = True
+ return self.execute(context)
+
+ def execute(self, context):
+ if self.convert_all:
+ drawings = [
+ tool.Ifc.get().by_id(d.ifc_definition_id) for d in context.scene.DocProperties.drawings if d.is_selected
+ ]
+ else:
+ drawings = [tool.Ifc.get().by_id(context.scene.DocProperties.drawings.get(self.view).ifc_definition_id)]
+
+ drawing_uris: list[Path] = []
+ drawings_not_found: list[str] = []
+
+ for drawing in drawings:
+ drawing_uri = tool.Drawing.get_document_uri(tool.Drawing.get_drawing_document(drawing))
+ if drawing_uri is None or not os.path.exists(drawing_uri):
+ drawings_not_found.append(drawing.Name)
+ else:
+ drawing_uris.append(Path(drawing_uri))
+
+ if drawings_not_found:
+ msg = "Some drawings .svg files were not found, need to print them first: \n{}.".format(
+ "\n".join(drawings_not_found)
+ )
+ self.report({"ERROR"}, msg)
+ return {"CANCELLED"}
+
+ for drawing_uri in drawing_uris:
+ tool.Drawing.convert_svg_to_dxf(drawing_uri, drawing_uri.with_suffix(".dxf"))
+
+ self.report({"INFO"}, f"{len(drawing_uris)} drawings were converted to .dxf.")
+ return {"FINISHED"}
diff --git a/src/blenderbim/blenderbim/bim/module/drawing/prop.py b/src/blenderbim/blenderbim/bim/module/drawing/prop.py
index 3e4d3e1d03..7773d41930 100644
--- a/src/blenderbim/blenderbim/bim/module/drawing/prop.py
+++ b/src/blenderbim/blenderbim/bim/module/drawing/prop.py
@@ -66,6 +66,11 @@ def get_location_hint(self, context):
def update_diagram_scale(self, context):
+ if not context.scene.camera or context.scene.camera.data != self.id_data:
+ return
+ element = tool.Ifc.get_entity(context.scene.camera)
+ if not element:
+ return
try:
element = (
tool.Ifc.get()
@@ -87,6 +92,11 @@ def update_diagram_scale(self, context):
def update_is_nts(self, context):
+ if not context.scene.camera or context.scene.camera.data != self.id_data:
+ return
+ element = tool.Ifc.get_entity(context.scene.camera)
+ if not element:
+ return
try:
element = (
tool.Ifc.get()
@@ -194,7 +204,7 @@ def update_document_name(self, context):
def update_has_underlay(self, context):
update_layer(self, context, "HasUnderlay", self.has_underlay)
# making sure that camera is active
- if self.has_underlay and (context.active_object and context.active_object.data == self.id_data):
+ if self.has_underlay and (context.scene.camera and context.scene.camera.data == self.id_data):
bpy.ops.bim.reload_drawing_styles()
bpy.ops.bim.activate_drawing_style()
@@ -208,10 +218,12 @@ def update_has_annotation(self, context):
def update_layer(self, context, name, value):
- element = tool.Ifc.get_entity(context.active_object)
+ if not context.scene.camera or context.scene.camera.data != self.id_data:
+ return
+ element = tool.Ifc.get_entity(context.scene.camera)
if not element:
return
- pset = ifcopenshell.util.element.get_psets(element).get("EPset_Drawing")
+ pset = ifcopenshell.util.element.get_pset(element, "EPset_Drawing")
if pset:
pset = tool.Ifc.get().by_id(pset["id"])
else:
@@ -229,9 +241,8 @@ def update_titleblocks(self, context):
SheetsData.data["titleblocks"] = SheetsData.titleblocks()
-def toggleDecorations(self, context):
- toggle = self.should_draw_decorations
- if toggle:
+def update_should_draw_decorations(self, context):
+ if self.should_draw_decorations:
# TODO: design a proper text variable templating renderer
collection = context.scene.camera.BIMObjectProperties.collection
for obj in collection.objects:
@@ -343,7 +354,7 @@ class DocProperties(PropertyGroup):
active_sheet_index: IntProperty(name="Active Sheet Index")
ifc_files: CollectionProperty(name="IFCs", type=StrProperty)
drawing_styles: CollectionProperty(name="Drawing Styles", type=DrawingStyle)
- should_draw_decorations: BoolProperty(name="Should Draw Decorations", update=toggleDecorations)
+ should_draw_decorations: BoolProperty(name="Should Draw Decorations", update=update_should_draw_decorations)
sheets_dir: StringProperty(default=os.path.join("sheets") + os.path.sep, name="Default Sheets Directory")
layouts_dir: StringProperty(default=os.path.join("layouts") + os.path.sep, name="Default Layouts Directory")
titleblocks_dir: StringProperty(
diff --git a/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py b/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py
index b56d5a795e..736d8efbe5 100644
--- a/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py
+++ b/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py
@@ -29,9 +29,11 @@ import svgwrite
import ifcopenshell
import ifcopenshell.util.element
import ifcopenshell.util.representation
+import ifcopenshell.util.selector
import blenderbim.tool as tool
import blenderbim.bim.module.drawing.helper as helper
import blenderbim.bim.module.drawing.annotation as annotation
+from blenderbim.bim.module.drawing.data import DrawingsData
from blenderbim.bim.module.drawing.data import DecoratorData
from blenderbim.bim.ifc import IfcStore
@@ -39,6 +41,7 @@ from blenderbim.bim.ifc import IfcStore
from math import pi, ceil, atan, degrees, acos
from mathutils import geometry, Vector
from bpy_extras import view3d_utils
+from typing import Optional
class External(svgwrite.container.Group):
@@ -322,7 +325,7 @@ class SvgWriter:
)
)
- def draw_misc_annotation(self, obj):
+ def draw_misc_annotation(self, obj: bpy.types.Object) -> None:
# We have to decide whether this should come from Blender or from IFC.
# For the moment, for convenience of experimenting with ideas, it comes
# from Blender. In the future, it should probably come from IFC.
@@ -356,14 +359,14 @@ class SvgWriter:
d = "M{}".format(d[1:])
path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes)))
- def get_attribute_classes(self, obj):
+ def get_attribute_classes(self, obj: bpy.types.Object) -> list[str]:
element = tool.Ifc.get_entity(obj)
global_id = "GlobalId-{}".format(element.GlobalId)
predefined_type = "PredefinedType-" + tool.Drawing.canonicalise_class_name(
str(ifcopenshell.util.element.get_predefined_type(element))
)
classes = [global_id, element.is_a(), predefined_type]
- custom_classes = ifcopenshell.util.element.get_pset(element, "EPset_Annotation", "Classes")
+ custom_classes: str = ifcopenshell.util.element.get_pset(element, "EPset_Annotation", "Classes")
if custom_classes:
classes.extend(custom_classes.split())
for key in self.metadata:
@@ -787,7 +790,7 @@ class SvgWriter:
attrib["filter"] = "url(#fill-background)"
return element
- def draw_text_annotation(self, text_obj, position):
+ def draw_text_annotation(self, text_obj: bpy.types.Object, position: Vector) -> None:
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
element = tool.Ifc.get_entity(text_obj)
@@ -799,17 +802,7 @@ class SvgWriter:
text_position_svg = text_position * self.svg_scale
text_position_svg_str = ", ".join(map(str, text_position_svg))
- def get_basis_vector(matrix, i=0):
- """returns basis vector for i in world space, unaffected by object scale"""
- return matrix.inverted()[i].to_3d().normalized()
-
- text_dir_world_x_axis = get_basis_vector(text_obj.matrix_world)
-
- # RCP cameras may be scaled, so reset scales.
- camera_matrix = tool.Drawing.get_camera_matrix(self.camera)
- text_dir = (camera_matrix.inverted().to_quaternion() @ text_dir_world_x_axis).to_2d().normalized()
- angle = math.degrees(-text_dir.angle_signed(Vector((1, 0))))
-
+ angle = self.get_empty_object_angle(text_obj)
classes = self.get_attribute_classes(text_obj)
classes_str = " ".join(classes)
fill_bg = "fill-bg" in classes
@@ -817,8 +810,7 @@ class SvgWriter:
symbol = tool.Drawing.get_annotation_symbol(element)
template_text_fields = []
if symbol:
- # NOTE: for now we assume that scale is uniform
- symbol_transform = f"translate({text_position_svg_str}) rotate({angle}) scale({text_obj.scale.x})"
+ symbol_transform = self.get_symbol_transform(text_position_svg_str, angle, text_obj)
symbol_svg = self.find_xml_symbol_by_id(symbol)
if symbol_svg:
@@ -847,7 +839,7 @@ class SvgWriter:
return None
if not symbol_svg or not template_text_fields:
- self.svg.add(self.svg.use(f"#{symbol}", transform=symbol_transform))
+ self.draw_symbol(symbol, symbol_transform)
line_number = 0
for text_literal in text_literals:
@@ -865,17 +857,14 @@ class SvgWriter:
self.svg.add(tag)
line_number += len(tag.elements)
- def draw_empty_annotation(self, obj, classes):
+ def draw_empty_annotation(self, obj: bpy.types.Object, classes: list[str]) -> None:
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
point = self.project_point_onto_camera(obj.matrix_world.translation)
element = tool.Ifc.get_entity(obj)
- svg_id = ifcopenshell.util.element.get_pset(element, "EPset_Annotation", "Symbol")
- if not svg_id:
- # EPset_AnnotationSurveyArea is not standard! See bSI-4.3 proposal #660.
- svg_id = ifcopenshell.util.element.get_pset(element, "EPset_AnnotationSurveyArea", "PointType")
+ svg_id = tool.Drawing.get_annotation_symbol(element)
if not svg_id:
svg_id = str(ifcopenshell.util.element.get_predefined_type(element))
if not svg_id:
@@ -883,9 +872,37 @@ class SvgWriter:
point = Vector(((x_offset + point.x), (y_offset - point.y)))
symbol_position_svg = point * self.svg_scale
- self.svg.add(self.svg.use(f"#{svg_id}", insert=symbol_position_svg))
+ svg_position_str = ", ".join(map(str, symbol_position_svg))
- def draw_point_annotation(self, obj, classes):
+ angle = self.get_empty_object_angle(obj)
+ symbol_transform = self.get_symbol_transform(svg_position_str, angle, obj)
+ self.draw_symbol(svg_id, symbol_transform)
+
+ def get_empty_object_angle(self, obj: bpy.types.Object) -> float:
+ def get_basis_vector(matrix: bpy.types.Object, i: int = 0) -> Vector:
+ """returns basis vector for i in world space, unaffected by object scale"""
+ return matrix.inverted()[i].to_3d().normalized()
+
+ text_dir_world_x_axis = get_basis_vector(obj.matrix_world)
+
+ # RCP cameras may be scaled, so reset scales.
+ camera_matrix = tool.Drawing.get_camera_matrix(self.camera)
+ text_dir = (camera_matrix.inverted().to_quaternion() @ text_dir_world_x_axis).to_2d().normalized()
+ return math.degrees(-text_dir.angle_signed(Vector((1, 0))))
+
+ def get_symbol_transform(
+ self, svg_position_str: str, angle: float = 0.0, obj: Optional[bpy.types.Object] = None
+ ) -> str:
+ """`obj` will be used to identify symbol scale,
+ ignore it if object scale doesn't match the symbol scale"""
+ # NOTE: for now we assume that scale is uniform
+ scale = 1.0 if not obj else obj.scale.x
+ return f"translate({svg_position_str}) rotate({angle}) scale({scale})"
+
+ def draw_symbol(self, symbol_id: str, symbol_transform: str) -> None:
+ self.svg.add(self.svg.use(f"#{symbol_id}", transform=symbol_transform))
+
+ def draw_point_annotation(self, obj: bpy.types.Object, classes: list[str]) -> None:
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
@@ -893,10 +910,7 @@ class SvgWriter:
projected_points = [self.project_point_onto_camera(matrix_world @ v.co) for v in obj.data.vertices]
element = tool.Ifc.get_entity(obj)
- svg_id = ifcopenshell.util.element.get_pset(element, "EPset_Annotation", "Symbol")
- if not svg_id:
- # EPset_AnnotationSurveyArea is not standard! See bSI-4.3 proposal #660.
- svg_id = ifcopenshell.util.element.get_pset(element, "EPset_AnnotationSurveyArea", "PointType")
+ svg_id = tool.Drawing.get_annotation_symbol(element)
if not svg_id:
svg_id = str(ifcopenshell.util.element.get_predefined_type(element))
if not svg_id:
@@ -905,7 +919,10 @@ class SvgWriter:
for symbol_position in projected_points:
symbol_position = Vector(((x_offset + symbol_position.x), (y_offset - symbol_position.y)))
symbol_position_svg = symbol_position * self.svg_scale
- self.svg.add(self.svg.use(f"#{svg_id}", insert=symbol_position_svg))
+ svg_position_str = ", ".join(map(str, symbol_position_svg))
+
+ symbol_transform = self.get_symbol_transform(svg_position_str)
+ self.draw_symbol(svg_id, symbol_transform)
def draw_break_annotations(self, obj):
x_offset = self.raw_width / 2
@@ -1066,7 +1083,9 @@ class SvgWriter:
# calculating text parameters and adding text
text_position = arc_mid_point + arc_mid_dir * 5
text_style = SvgWriter.get_box_alignment_parameters("center")
- angle_text = f"{int(angle)}deg"
+ drawing_pset_data = DrawingsData.data["active_drawing_pset_data"]
+ decimal_places = drawing_pset_data.get("AngleDecimalPlaces", None)
+ angle_text = f"{round(angle, decimal_places)}°"
self.svg.add(self.svg.text(angle_text, insert=text_position, class_="ANGLE", **text_style))
# Draw SVG arc, see for details: http://xahlee.info/js/svg_circle_arc.html
@@ -1402,7 +1421,7 @@ class SvgWriter:
return text_tags
- def project_point_onto_camera(self, point):
+ def project_point_onto_camera(self, point: Vector) -> Vector:
# TODO is this needlessly complex?
return self.camera.matrix_world.inverted() @ geometry.intersect_line_plane(
point.xyz,
diff --git a/src/blenderbim/blenderbim/bim/module/drawing/ui.py b/src/blenderbim/blenderbim/bim/module/drawing/ui.py
index 2ccdb9ae8c..33a40450fd 100644
--- a/src/blenderbim/blenderbim/bim/module/drawing/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/drawing/ui.py
@@ -268,6 +268,11 @@ class BIM_PT_drawings(Panel):
col = row.column()
col.alignment = "RIGHT"
+ convert_to_dxf = row.row(align=True)
+ op = convert_to_dxf.operator("bim.convert_svg_to_dxf", text="", icon="IMAGE_DATA")
+ op.view = active_drawing.name
+ convert_to_dxf.enabled = active_drawing.ifc_definition_id > 0
+
op = row.operator("bim.select_all_drawings", icon="SELECT_SUBTRACT", text="")
open_drawing_button = row.row(align=True)
diff --git a/src/blenderbim/blenderbim/bim/module/drawing/workspace.py b/src/blenderbim/blenderbim/bim/module/drawing/workspace.py
index 4e4b7e4dc6..0a7ae62ee6 100644
--- a/src/blenderbim/blenderbim/bim/module/drawing/workspace.py
+++ b/src/blenderbim/blenderbim/bim/module/drawing/workspace.py
@@ -250,7 +250,7 @@ class AnnotationToolUI:
row = cls.layout.row(align=True)
row.label(text="", icon="FILE_3D")
prop_with_search(row, cls.props, "relating_type_id", text="")
- row.operator("bim.launch_annotation_type_manager", icon="LIGHTPROBE_GRID", text="")
+ row.operator("bim.launch_annotation_type_manager", icon=tool.Blender.TYPE_MANAGER_ICON, text="")
add_layout_hotkey_operator(cls.layout, "Add", "S_A", "Create a new annotation")
diff --git a/src/blenderbim/blenderbim/bim/module/geometry/helper.py b/src/blenderbim/blenderbim/bim/module/geometry/helper.py
index 383bdb6a4b..3b7281a1a8 100644
--- a/src/blenderbim/blenderbim/bim/module/geometry/helper.py
+++ b/src/blenderbim/blenderbim/bim/module/geometry/helper.py
@@ -21,12 +21,14 @@ import bmesh
import mathutils
import ifcopenshell
import ifcopenshell.util.unit
+import blenderbim.tool as tool
from math import pi, pow
from mathutils import Vector, Matrix, geometry
+from typing import Union
class Helper:
- def __init__(self, file):
+ def __init__(self, file: ifcopenshell.file):
self.file = file
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file)
@@ -34,7 +36,7 @@ class Helper:
# edge that shares a single vertex only with that face to find the extrusion
# edge. A face with the normal facing down is prioritised. A limited
# dissolve ensure that faces are quads and not tris.
- def auto_detect_rectangle_profile_extruded_area_solid(self, mesh):
+ def auto_detect_rectangle_profile_extruded_area_solid(self, mesh: bpy.types.Mesh) -> dict:
bm = bmesh.new()
bm.from_mesh(mesh)
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
@@ -115,7 +117,9 @@ class Helper:
return {"profile": profile, "extrusion": extrusion}
- def auto_detect_arbitrary_profile_with_voids(self, obj, mesh):
+ def auto_detect_arbitrary_profile_with_voids(
+ self, obj: bpy.types.Object, mesh: bpy.types.Mesh
+ ) -> Union[tuple, dict]:
groups = {"IFCARCINDEX": [], "IFCCIRCLE": []}
for i, group in enumerate(obj.vertex_groups):
if "IFCARCINDEX" in group.name:
@@ -138,28 +142,31 @@ class Helper:
total_groups = 0
is_circle = False
for group_type, group_indices in groups.items():
- for group_index in group_indices:
- if group_index in vert[deform_layer]:
- if group_type == "IFCCIRCLE":
- is_circle = True
- group_verts[group_type].setdefault(group_index, 0)
- group_verts[group_type][group_index] += 1
- total_groups += 0
+ if not group_indices:
+ continue
+ is_special, group_index = tool.Blender.bmesh_check_vertex_in_groups(vert, deform_layer, group_indices)
+ if not is_special:
+ continue
+ if group_type == "IFCCIRCLE":
+ is_circle = True
+ group_verts[group_type].setdefault(group_index, 0)
+ group_verts[group_type][group_index] += 1
+ total_groups += 0
if total_groups > 1: # A vert can only belong to one group
return (False, "AMBIGUOUS_SPECIAL_VERTEX")
elif is_circle:
- pass # Circles are allowed to be unclosed
+ pass # Circles are allowed to be unclosed
elif total_groups == 0 and len(vert.link_edges) != 2: # Unclosed loop or forked loop
return (False, "UNCLOSED_LOOP")
for group_type, group_counts in group_verts.items():
if group_type == "IFCARCINDEX":
for group_count in group_counts.values():
- if group_count != 3: # Each arc needs 3 verts
+ if group_count != 3: # Each arc needs 3 verts
return (False, "3POINT_ARC")
elif group_type == "IFCCIRCLE":
for group_count in group_counts.values():
- if group_count != 2: # Each circle needs 2 verts
+ if group_count != 2: # Each circle needs 2 verts
return (False, "CIRCLE")
loop_edges = set(bm.edges)
@@ -269,7 +276,7 @@ class Helper:
inner_loops.remove(outer_loop)
# Copy vectors to prevent random data mangling after bmesh is freed.
- points = [Vector(list(v.co)) for v in bm.verts]
+ points = [v.co.copy() for v in bm.verts]
bm.to_mesh(mesh)
mesh.update()
diff --git a/src/blenderbim/blenderbim/bim/module/geometry/operator.py b/src/blenderbim/blenderbim/bim/module/geometry/operator.py
index 50ad245ac0..fb37c3cf7e 100644
--- a/src/blenderbim/blenderbim/bim/module/geometry/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/geometry/operator.py
@@ -324,8 +324,8 @@ class UpdateRepresentation(bpy.types.Operator, Operator):
# We are explicitly casting to a tessellation, so remove all parametric materials.
element_type = ifcopenshell.util.element.get_type(product)
if element_type: # Some invalid IFCs use material sets without a type.
- ifcopenshell.api.run("material.unassign_material", tool.Ifc.get(), product=element_type)
- ifcopenshell.api.run("material.unassign_material", tool.Ifc.get(), product=product)
+ ifcopenshell.api.run("material.unassign_material", tool.Ifc.get(), products=[element_type])
+ ifcopenshell.api.run("material.unassign_material", tool.Ifc.get(), products=[product])
else:
# These objects are parametrically based on an axis and should not be modified as a mesh
return
@@ -972,7 +972,7 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator):
if parts:
index = get_max_index(parts)
add_linked_aggregate_pset(element, index)
- index +=1
+ index += 1
for part in parts:
if part.is_a("IfcElementAssembly"):
select_objects_and_add_data(part)
@@ -982,16 +982,13 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator):
obj = tool.Ifc.get_object(part)
obj.select_set(True)
-
-
+
def add_linked_aggregate_pset(part, index):
pset = ifcopenshell.util.element.get_pset(part, self.pset_name)
-
+
if not pset:
- pset = ifcopenshell.api.run(
- "pset.add_pset", tool.Ifc.get(), product=part, name=self.pset_name
- )
-
+ pset = ifcopenshell.api.run("pset.add_pset", tool.Ifc.get(), product=part, name=self.pset_name)
+
ifcopenshell.api.run(
"pset.edit_pset",
tool.Ifc.get(),
@@ -1016,7 +1013,9 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator):
return
linked_aggregate_group = ifcopenshell.api.run("group.add_group", tool.Ifc.get(), Name=self.group_name)
- ifcopenshell.api.run("group.assign_group", tool.Ifc.get(), products=[element], group=linked_aggregate_group)
+ ifcopenshell.api.run(
+ "group.assign_group", tool.Ifc.get(), products=[element], group=linked_aggregate_group
+ )
def custom_incremental_naming_for_element_assembly(old_to_new):
for new in old_to_new.values():
@@ -1027,16 +1026,16 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator):
if r.is_a("IfcRelAssignsToGroup")
if "BBIM_Linked_Aggregate" in r.RelatingGroup.Name
][0]
-
+
number = len(group_elements) - 1
number = f"{number:02d}"
new_obj = tool.Ifc.get_object(new[0])
- pattern1 = r'_\d'
+ pattern1 = r"_\d"
if re.findall(pattern1, new_obj.name):
split_name = new_obj.name.split("_")
new_obj.name = split_name[0] + "_" + number
continue
- pattern2 = r'\.\d{3}'
+ pattern2 = r"\.\d{3}"
if re.findall(pattern2, new_obj.name):
split_name = new_obj.name.split(".")
new_obj.name = split_name[0] + "_" + number
@@ -1074,6 +1073,7 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator):
]
tool.Ifc.run("group.assign_group", group=linked_aggregate_group[0], products=new)
+
if len(context.selected_objects) != 1:
return {"FINISHED"}
@@ -1099,14 +1099,11 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator):
copy_linked_aggregate_data(old_to_new)
custom_incremental_naming_for_element_assembly(old_to_new)
-
+
blenderbim.bim.handler.refresh_ui_data()
return old_to_new
-
-
-
class RefreshLinkedAggregate(bpy.types.Operator):
bl_idname = "bim.refresh_linked_aggregate"
@@ -1154,9 +1151,9 @@ class RefreshLinkedAggregate(bpy.types.Operator):
original_names[group] = {}
pset = ifcopenshell.util.element.get_pset(element, self.pset_name)
- index = pset['Index']
+ index = pset["Index"]
original_names[group][index] = tool.Ifc.get_object(element).name
-
+
parts = ifcopenshell.util.element.get_parts(element)
if parts:
for part in parts:
@@ -1164,20 +1161,22 @@ class RefreshLinkedAggregate(bpy.types.Operator):
original_names | get_original_names(part)
else:
try:
- pset = ifcopenshell.util.element.get_pset(part, self.pset_name)
+ pset = ifcopenshell.util.element.get_pset(part, self.pset_name)
except:
- continue
- index = pset['Index']
+ continue
+ index = pset["Index"]
original_names[group][index] = tool.Ifc.get_object(part).name
-
+
return original_names
def set_original_name(obj, original_names):
element = tool.Ifc.get_entity(obj)
aggregate = ifcopenshell.util.element.get_aggregate(element)
- if ifcopenshell.util.element.get_parts(element): # if element has parts it means it is the base of and aggregate or sub-aggregate
+ if ifcopenshell.util.element.get_parts(
+ element
+ ): # if element has parts it means it is the base of and aggregate or sub-aggregate
aggregate = element
-
+
group = [
r.RelatingGroup
for r in getattr(aggregate, "HasAssignments", []) or []
@@ -1186,12 +1185,12 @@ class RefreshLinkedAggregate(bpy.types.Operator):
]
if not group:
return
-
+
group = group[0].id()
-
+
pset = ifcopenshell.util.element.get_pset(element, self.pset_name)
- index = pset['Index']
-
+ index = pset["Index"]
+
try:
obj.name = original_names[group][index]
except:
@@ -1295,9 +1294,9 @@ class RefreshLinkedAggregate(bpy.types.Operator):
element_aggregate = ifcopenshell.util.element.get_aggregate(element)
selected_matrix, duplicate_matrix = get_original_matrix(element, base_instance)
-
+
original_names = get_original_names(element)
-
+
delete_objects(element)
for obj in context.selected_objects:
@@ -1308,24 +1307,24 @@ class RefreshLinkedAggregate(bpy.types.Operator):
old_to_new = DuplicateMoveLinkedAggregate.execute_ifc_duplicate_linked_aggregate_operator(self, context)
set_new_matrix(selected_matrix, duplicate_matrix, old_to_new)
-
+
for old, new in old_to_new.items():
if element_aggregate and new[0].is_a("IfcElementAssembly"):
new_aggregate = ifcopenshell.util.element.get_aggregate(new[0])
if not new_aggregate:
blenderbim.core.aggregate.assign_object(
- tool.Ifc,
- tool.Aggregate,
- tool.Collector,
- relating_obj=tool.Ifc.get_object(element_aggregate),
- related_obj=tool.Ifc.get_object(new[0]),
- )
-
+ tool.Ifc,
+ tool.Aggregate,
+ tool.Collector,
+ relating_obj=tool.Ifc.get_object(element_aggregate),
+ related_obj=tool.Ifc.get_object(new[0]),
+ )
+
for old, new in old_to_new.items():
new_obj = tool.Ifc.get_object(new[0])
set_original_name(new_obj, original_names)
-
+
blenderbim.bim.handler.refresh_ui_data()
operator_time = time() - refresh_start_time
@@ -1473,7 +1472,9 @@ class OverrideModeSetEdit(bpy.types.Operator):
for obj in selected_objs:
if not obj:
continue
- if not obj.data:
+ obj_supports_edit_mode, message = tool.Blender.object_supports_edit_mode(obj)
+ if not obj_supports_edit_mode:
+ self.report({"INFO"}, message)
obj.select_set(False)
continue
element = tool.Ifc.get_entity(obj)
diff --git a/src/blenderbim/blenderbim/bim/module/geometry/ui.py b/src/blenderbim/blenderbim/bim/module/geometry/ui.py
index ea596cad28..b2a52315ec 100644
--- a/src/blenderbim/blenderbim/bim/module/geometry/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/geometry/ui.py
@@ -212,7 +212,7 @@ class BIM_PT_representation_items(Panel):
if props.is_editing_item_style:
# NOTE: we currently support 1 item having just 1 style
# when IfcStyledItem can actually have multiple styles
- row.prop(props, "representation_item_style", icon="SHADING_RENDERED", text="")
+ prop_with_search(row, props, "representation_item_style", icon="SHADING_RENDERED", text="")
row.operator("bim.edit_representation_item_style", icon="CHECKMARK", text="")
row.operator("bim.disable_editing_representation_item_style", icon="CANCEL", text="")
else:
diff --git a/src/blenderbim/blenderbim/bim/module/group/operator.py b/src/blenderbim/blenderbim/bim/module/group/operator.py
index c7dbdd6c8b..ddb5bb8845 100644
--- a/src/blenderbim/blenderbim/bim/module/group/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/group/operator.py
@@ -212,7 +212,7 @@ class UnassignGroup(bpy.types.Operator, tool.Ifc.Operator):
"group.unassign_group",
self.file,
**{
- "product": self.file.by_id(product.BIMObjectProperties.ifc_definition_id),
+ "products": [self.file.by_id(product.BIMObjectProperties.ifc_definition_id)],
"group": self.file.by_id(self.group),
}
)
diff --git a/src/blenderbim/blenderbim/bim/module/layer/operator.py b/src/blenderbim/blenderbim/bim/module/layer/operator.py
index c3c918e907..70d1213586 100644
--- a/src/blenderbim/blenderbim/bim/module/layer/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/layer/operator.py
@@ -151,7 +151,10 @@ class AssignPresentationLayer(bpy.types.Operator):
ifcopenshell.api.run(
"layer.assign_layer",
self.file,
- **{"item": self.file.by_id(item.BIMMeshProperties.ifc_definition_id), "layer": self.file.by_id(self.layer)}
+ **{
+ "items": [self.file.by_id(item.BIMMeshProperties.ifc_definition_id)],
+ "layer": self.file.by_id(self.layer),
+ }
)
return {"FINISHED"}
@@ -173,7 +176,10 @@ class UnassignPresentationLayer(bpy.types.Operator):
ifcopenshell.api.run(
"layer.unassign_layer",
self.file,
- **{"item": self.file.by_id(item.BIMMeshProperties.ifc_definition_id), "layer": self.file.by_id(self.layer)}
+ **{
+ "items": [self.file.by_id(item.BIMMeshProperties.ifc_definition_id)],
+ "layer": self.file.by_id(self.layer),
+ }
)
return {"FINISHED"}
diff --git a/src/blenderbim/blenderbim/bim/module/material/data.py b/src/blenderbim/blenderbim/bim/module/material/data.py
index d2a07d480d..cfdf3a8058 100644
--- a/src/blenderbim/blenderbim/bim/module/material/data.py
+++ b/src/blenderbim/blenderbim/bim/module/material/data.py
@@ -19,6 +19,7 @@
import os
import bpy
import ifcopenshell
+import ifcopenshell.util.doc
import ifcopenshell.util.schema
import blenderbim.tool as tool
@@ -109,29 +110,41 @@ class MaterialsData:
def active_styles(cls):
props = bpy.context.scene.BIMMaterialProperties
results = []
- if props.materials and props.active_material_index < len(props.materials):
- material = props.materials[props.active_material_index].ifc_definition_id
- if not material:
- return results
- material = tool.Ifc.get().by_id(material)
- for definition in material.HasRepresentation:
- for representation in definition.Representations:
- if not representation.is_a("IfcStyledRepresentation"):
+ if not props.materials or props.active_material_index >= len(props.materials):
+ return results
+
+ material = props.materials[props.active_material_index].ifc_definition_id
+ if not material:
+ return results
+
+ material = tool.Ifc.get_entity_by_id(material)
+
+ # NOTE: it's possible that data will be refreshed during material removal
+ # (when it will try to fetch active material type and will reach for material_types)
+ # and props.materials[i].ifc_definition_id will contain already removed id
+ if not material or not material.is_a("IfcMaterial"):
+ return results
+
+ for definition in material.HasRepresentation:
+ for representation in definition.Representations:
+ if not representation.is_a("IfcStyledRepresentation"):
+ continue
+ context = representation.ContextOfItems
+ for item in representation.Items:
+ if not item.is_a("IfcStyledItem"):
continue
- context = representation.ContextOfItems
- for item in representation.Items:
- if not item.is_a("IfcStyledItem"):
- continue
- for style in item.Styles:
- if style.is_a("IfcSurfaceStyle"):
- results.append({
+ for style in item.Styles:
+ if style.is_a("IfcSurfaceStyle"):
+ results.append(
+ {
"context_type": context.ContextType,
"context_identifier": getattr(context, "ContextIdentifier", ""),
"target_view": getattr(context, "TargetView", ""),
"name": style.Name or "Unnamed",
"id": style.id(),
"context_id": context.id(),
- })
+ }
+ )
return results
diff --git a/src/blenderbim/blenderbim/bim/module/material/operator.py b/src/blenderbim/blenderbim/bim/module/material/operator.py
index a682a66e20..77241afef5 100644
--- a/src/blenderbim/blenderbim/bim/module/material/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/material/operator.py
@@ -34,6 +34,7 @@ from blenderbim.bim.ifc import IfcStore
class LoadMaterials(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.load_materials"
bl_label = "Load Materials"
+ bl_description = "Display list of named materials"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
@@ -117,7 +118,7 @@ class AddMaterial(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.add_material"
bl_label = "Add Material"
bl_options = {"REGISTER", "UNDO"}
- obj: bpy.props.StringProperty()
+ obj: bpy.props.StringProperty(name="Material Name")
name: bpy.props.StringProperty(default="Default")
def invoke(self, context, event):
@@ -762,6 +763,11 @@ class EditMaterialStyle(bpy.types.Operator, tool.Ifc.Operator):
ifcopenshell.api.run("style.assign_material_style", ifc_file, material=material, style=style, context=context)
tool.Material.disable_editing_material()
core.load_materials(tool.Material, props.material_type)
+ # NOTE: Update all elements that has this material and
+ # not just the ones that are using it in IfcMaterialConstituent,
+ # to handle cases where Style is connected to geometry implicitly:
+ # e.g. RepItem->IfcElement->IfcMaterial->Style
+ # instead of RepItem->ShapeAspect->Constituent->Style.
tool.Material.update_elements_using_material(material)
diff --git a/src/blenderbim/blenderbim/bim/module/material/ui.py b/src/blenderbim/blenderbim/bim/module/material/ui.py
index 06a0fc04c3..1f24392af2 100644
--- a/src/blenderbim/blenderbim/bim/module/material/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/material/ui.py
@@ -175,7 +175,7 @@ class BIM_PT_object_material(Panel):
if ObjectMaterialData.data["type_material"]:
row = self.layout.row(align=True)
- row.label(text="Inherited Material: " + ObjectMaterialData.data["type_material"], icon="FILE_PARENT")
+ row.label(text="Inherited Material: " + ObjectMaterialData.data["type_material"], icon="CON_CHILDOF")
if ObjectMaterialData.data["material_class"]:
return self.draw_material_ui()
diff --git a/src/blenderbim/blenderbim/bim/module/model/decorator.py b/src/blenderbim/blenderbim/bim/module/model/decorator.py
index 72d47d3c42..080966c22d 100644
--- a/src/blenderbim/blenderbim/bim/module/model/decorator.py
+++ b/src/blenderbim/blenderbim/bim/module/model/decorator.py
@@ -23,6 +23,7 @@ from math import sin, cos, radians
from bpy.types import SpaceView3D
from mathutils import Vector, Matrix
from gpu_extras.batch import batch_for_shader
+from typing import Union
ERROR_ELEMENTS_COLOR = (1, 0.2, 0.322, 1) # RED
@@ -35,15 +36,6 @@ def transparent_color(color, alpha=0.1):
return color
-def bm_check_vertex_in_groups(vertex, deform_layer, groups):
- """returns tuple boolean (whether vertex is in any of the groups)
- and related group index"""
- for group_index in vertex[deform_layer].keys():
- if group_index in groups:
- return True, group_index
- return False, None
-
-
class ProfileDecorator:
installed = None
@@ -148,12 +140,12 @@ class ProfileDecorator:
# deform_layer is None if there are no verts assigned to vertex groups
# even if there are vertex groups in the obj.vertex_groups
if deform_layer:
- is_arc, group_index = bm_check_vertex_in_groups(vertex, deform_layer, arc_groups)
+ is_arc, group_index = tool.Blender.bmesh_check_vertex_in_groups(vertex, deform_layer, arc_groups)
if is_arc:
arcs.setdefault(group_index, []).append(vertex)
special_vertex_indices[vertex.index] = group_index
- is_circle, group_index = bm_check_vertex_in_groups(vertex, deform_layer, circle_groups)
+ is_circle, group_index = tool.Blender.bmesh_check_vertex_in_groups(vertex, deform_layer, circle_groups)
if is_circle:
circles.setdefault(group_index, []).append(vertex)
special_vertex_indices[vertex.index] = group_index
diff --git a/src/blenderbim/blenderbim/bim/module/model/mep.py b/src/blenderbim/blenderbim/bim/module/model/mep.py
index cc3daa610d..c3a19c5727 100644
--- a/src/blenderbim/blenderbim/bim/module/model/mep.py
+++ b/src/blenderbim/blenderbim/bim/module/model/mep.py
@@ -542,7 +542,7 @@ class MEPGenerator:
ifc_representation_class=None,
)
- rel = ifcopenshell.api.run("material.assign_material", ifc_file, product=element, type="IfcMaterialProfileSet")
+ rel = ifcopenshell.api.run("material.assign_material", ifc_file, products=[element], type="IfcMaterialProfileSet")
profile_set = rel.RelatingMaterial
material_profile = ifcopenshell.api.run(
"material.add_profile", ifc_file, profile_set=profile_set, material=material
diff --git a/src/blenderbim/blenderbim/bim/module/model/opening.py b/src/blenderbim/blenderbim/bim/module/model/opening.py
index 2c21ba8352..1d55fb305d 100644
--- a/src/blenderbim/blenderbim/bim/module/model/opening.py
+++ b/src/blenderbim/blenderbim/bim/module/model/opening.py
@@ -24,10 +24,14 @@ import shapely
import logging
import numpy as np
import ifcopenshell
+import ifcopenshell.api
+import ifcopenshell.geom
import ifcopenshell.util.shape
import ifcopenshell.util.element
import ifcopenshell.util.shape_builder
+import ifcopenshell.util.placement
import ifcopenshell.util.representation
+import ifcopenshell.util.unit
import blenderbim.tool as tool
import blenderbim.core.geometry
import blenderbim.bim.import_ifc as import_ifc
@@ -107,7 +111,7 @@ class FilledOpeningGenerator:
# Equivalent to "side Z" for a wall axis, so that stuff like skylights appear on the top.
local_position_on_voided_obj.z = layers["offset"] + layers["thickness"]
new_matrix.translation.xyz = voided_obj.matrix_world @ local_position_on_voided_obj
- rotation_matrix = Matrix.Rotation(radians(-90), 4, 'X')
+ rotation_matrix = Matrix.Rotation(radians(-90), 4, "X")
new_matrix @= rotation_matrix
filling_obj.matrix_world = new_matrix
@@ -184,7 +188,13 @@ class FilledOpeningGenerator:
)
def regenerate_from_type(self, usecase_path, ifc_file, settings):
- filling = settings["related_object"]
+ relating_type = settings["relating_type"]
+
+ for related_object in settings["related_objects"]:
+ self._regenerate_from_type(related_object)
+
+ def _regenerate_from_type(self, related_object: ifcopenshell.entity_instance) -> None:
+ filling = related_object
if not getattr(filling, "FillsVoids", None):
return
diff --git a/src/blenderbim/blenderbim/bim/module/model/product.py b/src/blenderbim/blenderbim/bim/module/model/product.py
index 8d255a38a0..8c66e838d9 100644
--- a/src/blenderbim/blenderbim/bim/module/model/product.py
+++ b/src/blenderbim/blenderbim/bim/module/model/product.py
@@ -25,9 +25,13 @@ import ifcopenshell.util.system
import ifcopenshell.util.element
import ifcopenshell.util.placement
import ifcopenshell.util.representation
+import ifcopenshell.util.type
+import ifcopenshell.util.unit
import blenderbim.tool as tool
+import blenderbim.core.aggregate
import blenderbim.core.type
import blenderbim.core.geometry
+import blenderbim.core.spatial
from . import wall, slab, profile, mep
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.model.data import AuthoringData
@@ -273,7 +277,7 @@ class AddConstrTypeInstance(bpy.types.Operator):
obj.location[2] = collection_obj.location[2] + bpy.context.scene.BIMModelProperties.rl2
@staticmethod
- def generate_layered_element(ifc_class, relating_type):
+ def generate_layered_element(ifc_class: str, relating_type: ifcopenshell.entity_instance) -> bool:
layer_set_direction = None
parametric = ifcopenshell.util.element.get_psets(relating_type).get("EPset_Parametric")
@@ -297,6 +301,7 @@ class AddConstrTypeInstance(bpy.types.Operator):
material = ifcopenshell.util.element.get_material(tool.Ifc.get_entity(obj))
material.LayerSetDirection = layer_set_direction
return True
+ return False
class ChangeTypePage(bpy.types.Operator, tool.Ifc.Operator):
@@ -510,7 +515,7 @@ def ensure_material_assigned(usecase_path, ifc_file, settings):
if usecase_path == "material.assign_material":
if not settings.get("material", None):
return
- elements = [settings["product"]]
+ elements = settings["products"]
else:
elements = []
for rel in ifc_file.by_type("IfcRelAssociatesMaterial"):
@@ -546,7 +551,7 @@ def ensure_material_assigned(usecase_path, ifc_file, settings):
def ensure_material_unassigned(usecase_path, ifc_file, settings):
- elements = [settings["product"]]
+ elements = settings["products"]
if elements[0].is_a("IfcElementType"):
elements.extend(ifcopenshell.util.element.get_types(elements[0]))
for element in elements:
diff --git a/src/blenderbim/blenderbim/bim/module/model/profile.py b/src/blenderbim/blenderbim/bim/module/model/profile.py
index 0b889ba035..c7b0af7b78 100644
--- a/src/blenderbim/blenderbim/bim/module/model/profile.py
+++ b/src/blenderbim/blenderbim/bim/module/model/profile.py
@@ -21,14 +21,18 @@ import copy
import bmesh
import mathutils.geometry
import ifcopenshell
+import ifcopenshell.api
import ifcopenshell.util.type
import ifcopenshell.util.unit
import ifcopenshell.util.element
+import ifcopenshell.util.placement
+import ifcopenshell.util.representation
import blenderbim.bim.handler
import blenderbim.tool as tool
import blenderbim.core.type
import blenderbim.core.geometry
import blenderbim.core.material
+import blenderbim.core.root
from math import pi, degrees, inf
from mathutils import Vector, Matrix, Quaternion
from blenderbim.bim.module.geometry.helper import Helper
@@ -92,7 +96,7 @@ class DumbProfileGenerator:
should_add_representation=False,
context=self.body_context,
)
- ifcopenshell.api.run("type.assign_type", self.file, related_object=element, relating_type=self.relating_type)
+ ifcopenshell.api.run("type.assign_type", self.file, related_objects=[element], relating_type=self.relating_type)
material = ifcopenshell.util.element.get_material(element)
material.CardinalPoint = self.cardinal_point
@@ -207,12 +211,19 @@ class DumbProfileRegenerator:
return results
def regenerate_from_type(self, usecase_path, ifc_file, settings):
- obj = tool.Ifc.get_object(settings["related_object"])
- if not obj or not obj.data or not obj.data.BIMMeshProperties.ifc_definition_id:
- return
- new_material = ifcopenshell.util.element.get_material(settings["relating_type"])
+ relating_type = settings["relating_type"]
+
+ new_material = ifcopenshell.util.element.get_material(relating_type)
if not new_material or not new_material.is_a("IfcMaterialProfileSet"):
return
+
+ for related_object in settings["related_objects"]:
+ self._regenerate_from_type(related_object)
+
+ def _regenerate_from_type(self, related_object: ifcopenshell.entity_instance) -> None:
+ obj = tool.Ifc.get_object(related_object)
+ if not obj or not obj.data or not obj.data.BIMMeshProperties.ifc_definition_id:
+ return
DumbProfileRecalculator().recalculate([obj])
diff --git a/src/blenderbim/blenderbim/bim/module/model/slab.py b/src/blenderbim/blenderbim/bim/module/model/slab.py
index 624d41e2bd..ec23ca101f 100644
--- a/src/blenderbim/blenderbim/bim/module/model/slab.py
+++ b/src/blenderbim/blenderbim/bim/module/model/slab.py
@@ -20,17 +20,22 @@ import bpy
import json
import bmesh
import ifcopenshell
-import ifcopenshell.util.type
-import ifcopenshell.util.unit
+import ifcopenshell.api
import ifcopenshell.util.element
+import ifcopenshell.util.placement
+import ifcopenshell.util.representation
+import ifcopenshell.util.unit
+import ifcopenshell.util.type
import blenderbim.bim.handler
import blenderbim.core.type
import blenderbim.core.geometry
+import blenderbim.core.root
import blenderbim.tool as tool
from math import radians
from mathutils import Vector, Matrix
from blenderbim.bim.module.geometry.helper import Helper
from blenderbim.bim.module.model.decorator import ProfileDecorator
+from typing import Optional
def calculate_quantities(usecase_path, ifc_file, settings):
@@ -110,7 +115,7 @@ def calculate_quantities(usecase_path, ifc_file, settings):
class DumbSlabGenerator:
- def __init__(self, relating_type):
+ def __init__(self, relating_type: ifcopenshell.entity_instance):
self.relating_type = relating_type
def generate(self):
@@ -173,7 +178,7 @@ class DumbSlabGenerator:
should_add_representation=False,
context=self.body_context,
)
- ifcopenshell.api.run("type.assign_type", self.file, related_object=element, relating_type=self.relating_type)
+ ifcopenshell.api.run("type.assign_type", self.file, related_objects=[element], relating_type=self.relating_type)
blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
representation = ifcopenshell.api.run(
@@ -246,27 +251,38 @@ class DumbSlabPlaner:
self.change_thickness(element, total_thickness)
def regenerate_from_type(self, usecase_path, ifc_file, settings):
- obj = tool.Ifc.get_object(settings["related_object"])
- if not obj or not obj.data or not obj.data.BIMMeshProperties.ifc_definition_id:
- return
- self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
- new_material = ifcopenshell.util.element.get_material(settings["relating_type"])
+ relating_type = settings["relating_type"]
+
+ new_material = ifcopenshell.util.element.get_material(relating_type)
if not new_material or not new_material.is_a("IfcMaterialLayerSet"):
return
+
parametric = ifcopenshell.util.element.get_psets(settings["relating_type"]).get("EPset_Parametric")
layer_set_direction = None
if parametric:
layer_set_direction = parametric.get("LayerSetDirection", layer_set_direction)
new_thickness = sum([l.LayerThickness for l in new_material.MaterialLayers])
- material = ifcopenshell.util.element.get_material(settings["related_object"])
+
+ self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
+ for related_object in settings["related_objects"]:
+ self._regenerate_from_type(related_object, layer_set_direction, new_thickness)
+
+ def _regenerate_from_type(
+ self, related_object: ifcopenshell.entity_instance, layer_set_direction: Optional[str], new_thickness: float
+ ) -> None:
+ obj = tool.Ifc.get_object(related_object)
+ if not obj or not obj.data or not obj.data.BIMMeshProperties.ifc_definition_id:
+ return
+
+ material = ifcopenshell.util.element.get_material(related_object)
if not material or not material.is_a("IfcMaterialLayerSetUsage"):
return
if layer_set_direction:
material.LayerSetDirection = layer_set_direction
if material.LayerSetDirection == "AXIS3":
- self.change_thickness(settings["related_object"], new_thickness)
+ self.change_thickness(related_object, new_thickness)
- def change_thickness(self, element, thickness):
+ def change_thickness(self, element: ifcopenshell.entity_instance, thickness: float) -> None:
body_context = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW")
obj = tool.Ifc.get_object(element)
if not obj:
@@ -698,9 +714,7 @@ class EditExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
new_footprint = ifcopenshell.api.run(
"geometry.add_footprint_representation", tool.Ifc.get(), context=footprint_context, curves=curves
)
- old_footprint = ifcopenshell.util.representation.get_representation(
- element, "Plan", "FootPrint", "SKETCH_VIEW"
- )
+ old_footprint = ifcopenshell.util.representation.get_representation(element, "Plan", "FootPrint", "SKETCH_VIEW")
if old_footprint:
for inverse in tool.Ifc.get().get_inverse(old_footprint):
ifcopenshell.util.element.replace_attribute(inverse, old_footprint, new_footprint)
diff --git a/src/blenderbim/blenderbim/bim/module/model/space.py b/src/blenderbim/blenderbim/bim/module/model/space.py
index 2e14db2097..90ada213a6 100644
--- a/src/blenderbim/blenderbim/bim/module/model/space.py
+++ b/src/blenderbim/blenderbim/bim/module/model/space.py
@@ -29,13 +29,17 @@ class GenerateSpace(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.generate_space"
bl_label = "Generate Space"
bl_options = {"REGISTER"}
- bl_description = "Create a space from the cursor position. Move the cursor position into the desired position, select the right space collection and run the operator"
+ bl_description = (
+ "Create a space from the cursor position. "
+ "Move the cursor position into the desired position, "
+ "select the right space collection and run the operator"
+ )
@classmethod
def poll(cls, context):
collection = context.view_layer.active_layer_collection.collection
collection_obj = collection.BIMCollectionProperties.obj
- active_obj = bpy.context.active_object
+ active_obj = context.active_object
element = tool.Ifc.get_entity(active_obj)
return tool.Ifc.get_entity(collection_obj) and not element.is_a("IfcWall")
@@ -47,16 +51,14 @@ class GenerateSpace(bpy.types.Operator, tool.Ifc.Operator):
def msg(self, context):
self.layout.label(text="NO ACTIVE STOREY")
-# props = context.scene.BIMModelProperties
-
collection = context.view_layer.active_layer_collection.collection
collection_obj = collection.BIMCollectionProperties.obj
if not collection_obj:
- bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
+ context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
return
spatial_element = tool.Ifc.get_entity(collection_obj)
if not spatial_element:
- bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
+ context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
return
core.generate_space(tool.Ifc, tool.Spatial, tool.Model, tool.Type)
@@ -70,7 +72,7 @@ class GenerateSpacesFromWalls(bpy.types.Operator, tool.Ifc.Operator):
@classmethod
def poll(cls, context):
- active_obj = bpy.context.active_object
+ active_obj = context.active_object
element = tool.Ifc.get_entity(active_obj)
if element:
return context.selected_objects and element.is_a("IfcWall")
@@ -81,7 +83,7 @@ class GenerateSpacesFromWalls(bpy.types.Operator, tool.Ifc.Operator):
# In order to run, the active object must be a wall and
# there must be selected walls
- active_obj = bpy.context.active_object
+ active_obj = context.active_object
element = tool.Ifc.get_entity(active_obj)
container = tool.Spatial.get_container(element)
@@ -96,28 +98,30 @@ class GenerateSpacesFromWalls(bpy.types.Operator, tool.Ifc.Operator):
if not container:
self.report({"ERROR"}, "The wall is not contained.")
- if not bpy.context.selected_objects:
+ if not context.selected_objects:
self.report({"ERROR"}, "No selected objects found. Please select walls.")
return
core.generate_spaces_from_walls(tool.Ifc, tool.Spatial, tool.Collector)
+
class ToggleSpaceVisibility(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.toggle_space_visibility"
bl_label = "Toggle Space Visibility"
bl_options = {"REGISTER"}
bl_description = "Change the space visibility"
- def execute(cls, context):
+ def execute(self, context):
core.toggle_space_visibility(tool.Ifc, tool.Spatial)
return {"FINISHED"}
+
class ToggleHideSpaces(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.toggle_hide_spaces"
bl_label = "Toggle Hide Spaces"
bl_options = {"REGISTER"}
bl_description = "Hide or Unhide all spaces"
- def execute(cls, context):
+ def execute(self, context):
core.toggle_hide_spaces(tool.Ifc, tool.Spatial)
return {"FINISHED"}
diff --git a/src/blenderbim/blenderbim/bim/module/model/ui.py b/src/blenderbim/blenderbim/bim/module/model/ui.py
index f3ecff8691..367917a3f0 100644
--- a/src/blenderbim/blenderbim/bim/module/model/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/model/ui.py
@@ -54,8 +54,10 @@ class LaunchTypeManager(bpy.types.Operator):
props.type_page = 1
if get_ifc_class(None, context):
ifc_class = props.ifc_class or AuthoringData.data["ifc_element_type"]
- props.type_class = ifc_class
- bpy.ops.bim.load_type_thumbnails(ifc_class=ifc_class, offset=0, limit=9)
+ else:
+ ifc_class = AuthoringData.data["ifc_element_type"]
+ props.type_class = ifc_class
+ bpy.ops.bim.load_type_thumbnails(ifc_class=ifc_class, offset=0, limit=9)
return context.window_manager.invoke_popup(self, width=550)
def draw(self, context):
diff --git a/src/blenderbim/blenderbim/bim/module/model/wall.py b/src/blenderbim/blenderbim/bim/module/model/wall.py
index dc1be54f0c..78e8b4c579 100644
--- a/src/blenderbim/blenderbim/bim/module/model/wall.py
+++ b/src/blenderbim/blenderbim/bim/module/model/wall.py
@@ -24,7 +24,9 @@ import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.unit
import ifcopenshell.util.element
+import ifcopenshell.util.placement
import ifcopenshell.util.representation
+import ifcopenshell.util.type
import mathutils.geometry
import blenderbim.bim.handler
import blenderbim.core.type
@@ -35,6 +37,7 @@ from blenderbim.bim.ifc import IfcStore
from math import pi, sin, cos, degrees, radians
from mathutils import Vector, Matrix
from blenderbim.bim.module.model.opening import FilledOpeningGenerator
+from typing import Optional
class JoinWall(bpy.types.Operator, tool.Ifc.Operator):
@@ -555,7 +558,10 @@ class DumbWallGenerator:
for face in sibling_obj.data.polygons:
normal = (sibling_obj.matrix_world.to_quaternion() @ face.normal).normalized()
face_center = sibling_obj.matrix_world @ face.center
- if normal.z != 0 or abs(mathutils.geometry.distance_point_to_plane(self.location, face_center, normal)) > 0.01:
+ if (
+ normal.z != 0
+ or abs(mathutils.geometry.distance_point_to_plane(self.location, face_center, normal)) > 0.01
+ ):
continue
rotation = math.atan2(normal[1], normal[0])
@@ -603,7 +609,7 @@ class DumbWallGenerator:
should_add_representation=False,
context=self.body_context,
)
- ifcopenshell.api.run("type.assign_type", self.file, related_object=element, relating_type=self.relating_type)
+ ifcopenshell.api.run("type.assign_type", self.file, related_objects=[element], relating_type=self.relating_type)
if self.axis_context:
representation = ifcopenshell.api.run(
"geometry.add_axis_representation",
@@ -724,18 +730,29 @@ class DumbWallPlaner:
DumbWallRecalculator().recalculate([w for w in set(walls) if w])
def regenerate_from_type(self, usecase_path, ifc_file, settings):
- obj = tool.Ifc.get_object(settings["related_object"])
- if not obj or not obj.data or not obj.data.BIMMeshProperties.ifc_definition_id:
- return
- self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
- new_material = ifcopenshell.util.element.get_material(settings["relating_type"])
+ relating_type = settings["relating_type"]
+
+ new_material = ifcopenshell.util.element.get_material(relating_type)
if not new_material or not new_material.is_a("IfcMaterialLayerSet"):
return
- parametric = ifcopenshell.util.element.get_psets(settings["relating_type"]).get("EPset_Parametric")
+
+ parametric = ifcopenshell.util.element.get_psets(relating_type).get("EPset_Parametric")
layer_set_direction = None
if parametric:
layer_set_direction = parametric.get("LayerSetDirection", layer_set_direction)
- material = ifcopenshell.util.element.get_material(settings["related_object"])
+
+ self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
+ for related_object in settings["related_objects"]:
+ self._regenerate_from_type(related_object, layer_set_direction)
+
+ def _regenerate_from_type(
+ self, related_object: ifcopenshell.entity_instance, layer_set_direction: Optional[str]
+ ) -> None:
+ obj = tool.Ifc.get_object(related_object)
+ if not obj or not obj.data or not obj.data.BIMMeshProperties.ifc_definition_id:
+ return
+
+ material = ifcopenshell.util.element.get_material(related_object)
if not material or not material.is_a("IfcMaterialLayerSetUsage"):
return
if layer_set_direction:
@@ -854,11 +871,11 @@ class DumbWallJoiner:
if (location_on_base - location).length < (location_on_side - location).length:
axis_offset = location_on_side - location_on_base
offset_from_axis = location_on_base - location
- opening_matrix.translation = (location_on_base - axis_offset - offset_from_axis)
+ opening_matrix.translation = location_on_base - axis_offset - offset_from_axis
else:
axis_offset = location_on_side - location_on_base
offset_from_axis = location_on_side - location
- opening_matrix.translation = (location_on_side - axis_offset - offset_from_axis)
+ opening_matrix.translation = location_on_side - axis_offset - offset_from_axis
opening_matrixes[opening] = opening_matrix
for filling in [r.RelatedBuildingElement for r in opening.HasFillings]:
@@ -871,11 +888,11 @@ class DumbWallJoiner:
if (location_on_base - location).length < (location_on_side - location).length:
axis_offset = location_on_side - location_on_base
offset_from_axis = location_on_base - location
- filling_matrix.translation = (location_on_base - axis_offset - offset_from_axis)
+ filling_matrix.translation = location_on_base - axis_offset - offset_from_axis
else:
axis_offset = location_on_side - location_on_base
offset_from_axis = location_on_side - location
- filling_matrix.translation = (location_on_side - axis_offset - offset_from_axis)
+ filling_matrix.translation = location_on_side - axis_offset - offset_from_axis
filling_matrixes[filling] = filling_matrix
self.recreate_wall(element1, wall1, axis1["reference"], axis1["reference"])
@@ -1120,13 +1137,8 @@ class DumbWallJoiner:
other = tool.Ifc.get_object(rel.RelatedElement)
if connection not in ["ATPATH", "NOTDEFINED"]:
self.join(
- obj,
- other,
- connection,
- rel.RelatedConnectionType,
- is_relating=True,
- description=rel.Description
- )
+ obj, other, connection, rel.RelatedConnectionType, is_relating=True, description=rel.Description
+ )
for rel in element.ConnectedFrom:
if rel.is_a("IfcRelConnectsPathElements"):
connection = rel.RelatedConnectionType
diff --git a/src/blenderbim/blenderbim/bim/module/model/workspace.py b/src/blenderbim/blenderbim/bim/module/model/workspace.py
index 100d59cb5d..c1c62f95ce 100644
--- a/src/blenderbim/blenderbim/bim/module/model/workspace.py
+++ b/src/blenderbim/blenderbim/bim/module/model/workspace.py
@@ -516,20 +516,21 @@ class BimToolUI:
prop_with_search(row, cls.props, "relating_type_id", text="")
else:
row.label(text="No Construction Type", icon="FILE_3D")
- row.operator("bim.launch_type_manager", icon="LIGHTPROBE_GRID", text="")
+ row.operator("bim.launch_type_manager", icon=tool.Blender.TYPE_MANAGER_ICON, text="")
else:
if AuthoringData.data["ifc_element_type"]:
row.label(text=f"No {AuthoringData.data['ifc_element_type']} Found", icon="ERROR")
row = cls.layout.row()
row.prop(cls.props, "type_name")
- row = cls.layout.row()
+ row = cls.layout.row(align=True)
op = row.operator(
"bim.add_default_type", icon="ADD", text=f"Add {AuthoringData.data['ifc_element_type']}"
)
op.ifc_element_type = AuthoringData.data["ifc_element_type"]
+ row.operator("bim.launch_type_manager", icon=tool.Blender.TYPE_MANAGER_ICON, text="")
else:
row.label(text="No Element Types Found", icon="ERROR")
- row.operator("bim.launch_type_manager", icon="LIGHTPROBE_GRID", text="Launch Type Manager")
+ row.operator("bim.launch_type_manager", icon=tool.Blender.TYPE_MANAGER_ICON, text="Launch Type Manager")
@classmethod
def draw_basic_bim_tool_interface(cls):
diff --git a/src/blenderbim/blenderbim/bim/module/patch/prop.py b/src/blenderbim/blenderbim/bim/module/patch/prop.py
index 1fd91dc973..e965e63e15 100644
--- a/src/blenderbim/blenderbim/bim/module/patch/prop.py
+++ b/src/blenderbim/blenderbim/bim/module/patch/prop.py
@@ -18,6 +18,7 @@
import bpy
import importlib
+import importlib.util
from pathlib import Path
import ifcpatch
from blenderbim.bim.prop import StrProperty, Attribute
diff --git a/src/blenderbim/blenderbim/bim/module/project/__init__.py b/src/blenderbim/blenderbim/bim/module/project/__init__.py
index 7dc03dceaa..ec1008cd33 100644
--- a/src/blenderbim/blenderbim/bim/module/project/__init__.py
+++ b/src/blenderbim/blenderbim/bim/module/project/__init__.py
@@ -21,9 +21,12 @@ from . import ui, prop, operator, workspace, gizmo, decorator
classes = (
operator.AppendEntireLibrary,
+ operator.AppendInspectedLinkedElement,
operator.AppendLibraryElement,
operator.AppendLibraryElementByQuery,
operator.AssignLibraryDeclaration,
+ operator.BIM_OT_load_clipping_planes,
+ operator.BIM_OT_save_clipping_planes,
operator.ChangeLibraryElement,
operator.CreateClippingPlane,
operator.CreateProject,
diff --git a/src/blenderbim/blenderbim/bim/module/project/operator.py b/src/blenderbim/blenderbim/bim/module/project/operator.py
index 938ba91598..7449f8bcad 100644
--- a/src/blenderbim/blenderbim/bim/module/project/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/project/operator.py
@@ -28,6 +28,7 @@ import ifcopenshell.api
import ifcopenshell.util.selector
import ifcopenshell.util.geolocation
import ifcopenshell.util.representation
+import ifcopenshell.util.element
import blenderbim.bim.handler
import blenderbim.bim.schema
import blenderbim.tool as tool
@@ -38,6 +39,8 @@ from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.ui import IFCFileSelector
from blenderbim.bim import import_ifc
from blenderbim.bim import export_ifc
+from collections import defaultdict
+import json
from math import radians
from pathlib import Path
from mathutils import Vector, Matrix
@@ -112,6 +115,10 @@ class CreateProject(bpy.types.Operator):
if tool.Blender.is_default_scene():
for obj in bpy.data.objects:
bpy.data.objects.remove(obj)
+ for mesh in bpy.data.meshes:
+ bpy.data.meshes.remove(mesh)
+ for mat in bpy.data.materials:
+ bpy.data.materials.remove(mat)
core.create_project(tool.Ifc, tool.Project, schema=props.export_schema, template=template)
tool.Blender.register_toolbar()
@@ -185,7 +192,9 @@ class RefreshLibrary(bpy.types.Operator):
self.props.active_library_element = ""
types = IfcStore.library_file.wrapped_data.types_with_super()
- for importable_type in sorted(["IfcTypeProduct", "IfcMaterial", "IfcCostSchedule", "IfcProfileDef"]):
+
+ element_classes = ["IfcTypeProduct", "IfcMaterial", "IfcCostSchedule", "IfcProfileDef"]
+ for importable_type in sorted(element_classes):
if importable_type in types:
new = self.props.library_elements.add()
new.name = importable_type
@@ -204,11 +213,15 @@ class ChangeLibraryElement(bpy.types.Operator):
self.library_file = IfcStore.library_file
ifc_classes = set()
self.props.active_library_element = self.element_name
+
crumb = self.props.library_breadcrumb.add()
crumb.name = self.element_name
+
elements = self.library_file.by_type(self.element_name)
[ifc_classes.add(e.is_a()) for e in elements]
+
self.props.library_elements.clear()
+
if len(ifc_classes) == 1 and list(ifc_classes)[0] == self.element_name:
for name, ifc_definition_id in sorted([(self.get_name(e), e.id()) for e in elements]):
self.add_library_asset(name, ifc_definition_id)
@@ -429,7 +442,13 @@ class AppendLibraryElement(bpy.types.Operator):
if element.is_a("IfcTypeProduct"):
self.import_type_from_ifc(element, context)
elif element.is_a("IfcProduct"):
+ # NOTE: not used as UI doesn't allow appending non-types
self.import_product_from_ifc(element, context)
+ element_type = ifcopenshell.util.element.get_type(element)
+ obj = tool.Ifc.get_object(element_type)
+ if obj is None:
+ self.import_type_from_ifc(element_type, context)
+
elif element.is_a("IfcMaterial"):
self.import_material_from_ifc(element, context)
try:
@@ -440,7 +459,7 @@ class AppendLibraryElement(bpy.types.Operator):
blenderbim.bim.handler.refresh_ui_data()
return {"FINISHED"}
- def import_material_from_ifc(self, element, context):
+ def import_material_from_ifc(self, element: ifcopenshell.entity_instance, context: bpy.types.Context) -> None:
self.file = IfcStore.get_file()
logger = logging.getLogger("ImportIFC")
ifc_import_settings = import_ifc.IfcImportSettings.factory(context, IfcStore.path, logger)
@@ -449,7 +468,7 @@ class AppendLibraryElement(bpy.types.Operator):
blender_material = ifc_importer.create_material(element)
self.import_material_styles(blender_material, element, ifc_importer)
- def import_product_from_ifc(self, element, context):
+ def import_product_from_ifc(self, element: ifcopenshell.entity_instance, context: bpy.types.Context) -> None:
self.file = IfcStore.get_file()
logger = logging.getLogger("ImportIFC")
ifc_import_settings = import_ifc.IfcImportSettings.factory(context, IfcStore.path, logger)
@@ -462,7 +481,7 @@ class AppendLibraryElement(bpy.types.Operator):
ifc_importer.create_generic_elements({element})
ifc_importer.place_objects_in_collections()
- def import_type_from_ifc(self, element, context):
+ def import_type_from_ifc(self, element: ifcopenshell.entity_instance, context: bpy.types.Context) -> None:
self.file = IfcStore.get_file()
logger = logging.getLogger("ImportIFC")
ifc_import_settings = import_ifc.IfcImportSettings.factory(context, IfcStore.path, logger)
@@ -487,14 +506,14 @@ class AppendLibraryElement(bpy.types.Operator):
ifc_importer.create_element_type(element)
ifc_importer.place_objects_in_collections()
- def import_materials(self, element, ifc_importer):
+ def import_materials(self, element: ifcopenshell.entity_instance, ifc_importer: import_ifc.IfcImporter) -> None:
for material in ifcopenshell.util.element.get_materials(element):
if IfcStore.get_element(material.id()):
continue
blender_material = ifc_importer.create_material(material)
self.import_material_styles(blender_material, material, ifc_importer)
- def import_styles(self, element, ifc_importer):
+ def import_styles(self, element: ifcopenshell.entity_instance, ifc_importer: import_ifc.IfcImporter) -> None:
if element.is_a("IfcTypeProduct"):
representations = element.RepresentationMaps or []
elif element.is_a("IfcProduct"):
@@ -507,7 +526,12 @@ class AppendLibraryElement(bpy.types.Operator):
if element2.is_a("IfcSurfaceStyle") and not IfcStore.get_element(element2.id()):
ifc_importer.create_style(element2)
- def import_material_styles(self, blender_material, material, ifc_importer):
+ def import_material_styles(
+ self,
+ blender_material: bpy.types.Material,
+ material: ifcopenshell.entity_instance,
+ ifc_importer: import_ifc.IfcImporter,
+ ) -> None:
if not material.HasRepresentation:
return
for element in self.file.traverse(material.HasRepresentation[0]):
@@ -838,7 +862,10 @@ class LinkIfc(bpy.types.Operator):
continue
new = context.scene.BIMProjectProperties.links.add()
if self.use_relative_path:
- filepath = os.path.relpath(filepath, bpy.path.abspath("//")).replace("\\", "/")
+ try:
+ filepath = os.path.relpath(filepath, bpy.path.abspath("//")).replace("\\", "/")
+ except:
+ pass # Perhaps on another drive or something
new.name = filepath
bpy.ops.bim.load_link(filepath=filepath, false_origin=self.false_origin)
print(f"Finished linking {len(files)} IFCs", time.time() - start)
@@ -876,15 +903,30 @@ class UnloadLink(bpy.types.Operator):
self.filepath = self.filepath.replace("\\", "/")
filepath = self.filepath
if not os.path.isabs(filepath):
- filepath = os.path.abspath(os.path.join(bpy.path.abspath("//"), filepath)).replace("\\", "/")
- for collection in context.scene.collection.children:
- if collection.library and collection.library.filepath.replace("\\", "/") == filepath:
- context.scene.collection.children.unlink(collection)
- for scene in bpy.data.scenes:
- if scene.library and scene.library.filepath.replace("\\", "/") == filepath:
+ filepath = os.path.abspath(os.path.join(bpy.path.abspath("//"), filepath))
+ filepath = Path(filepath)
+ if filepath.suffix.lower() == ".ifc":
+ filepath = filepath.with_suffix(".ifc.cache.blend")
+
+ for collection in context.scene.collection.children[:]:
+ if collection.library and Path(collection.library.filepath) == filepath:
+ bpy.data.collections.remove(collection)
+
+ for scene in bpy.data.scenes[:]:
+ if scene.library and Path(scene.library.filepath) == filepath:
bpy.data.scenes.remove(scene)
- link = context.scene.BIMProjectProperties.links.get(self.filepath)
- link.is_loaded = False
+
+ links = context.scene.BIMProjectProperties.links
+ links.get(self.filepath).is_loaded = False
+
+ if not any([l.is_loaded for l in links]):
+ ProjectDecorator.uninstall()
+ # we make sure we don't draw queried object from the file that was just unlinked
+ elif queried_obj := context.scene.BIMProjectProperties.queried_obj:
+ queried_filepath = Path(queried_obj["ifc_filepath"])
+ if queried_filepath == filepath:
+ ProjectDecorator.uninstall()
+
return {"FINISHED"}
@@ -975,24 +1017,24 @@ class ToggleLinkSelectability(bpy.types.Operator):
bl_label = "Toggle Link Selectability"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Toggle selectability"
- link: bpy.props.StringProperty()
+ link: bpy.props.StringProperty(name="Linked IFC Filepath")
def execute(self, context):
props = context.scene.BIMProjectProperties
link = props.links.get(self.link)
- self.filepath = self.link
- if not os.path.isabs(self.filepath):
- self.filepath = os.path.abspath(os.path.join(bpy.path.abspath("//"), self.filepath)).replace("\\", "/")
+ if not os.path.isabs(self.link):
+ self.link = os.path.abspath(os.path.join(bpy.path.abspath("//"), self.link))
+ self.library_filepath = Path(self.link).with_suffix(".ifc.cache.blend")
for collection in self.get_linked_collections():
collection.hide_select = not collection.hide_select
link.is_selectable = not collection.hide_select
return {"FINISHED"}
- def get_linked_collections(self):
+ def get_linked_collections(self) -> list[bpy.types.Collection]:
return [
c
for c in bpy.data.collections
- if "IfcProject" in c.name and c.library and c.library.filepath.replace("\\", "/") == self.filepath
+ if "IfcProject" in c.name and c.library and Path(c.library.filepath) == self.library_filepath
]
@@ -1001,15 +1043,15 @@ class ToggleLinkVisibility(bpy.types.Operator):
bl_label = "Toggle Link Visibility"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Toggle visibility between SOLID and WIREFRAME"
- link: bpy.props.StringProperty()
- mode: bpy.props.StringProperty()
+ link: bpy.props.StringProperty(name="Linked IFC Filepath")
+ mode: bpy.props.EnumProperty(name="Visibility Mode", items=((i, i, "") for i in ("WIREFRAME", "VISIBLE")))
def execute(self, context):
props = context.scene.BIMProjectProperties
link = props.links.get(self.link)
- self.filepath = self.link
- if not os.path.isabs(self.filepath):
- self.filepath = os.path.abspath(os.path.join(bpy.path.abspath("//"), self.filepath)).replace("\\", "/")
+ if not os.path.isabs(self.link):
+ self.link = os.path.abspath(os.path.join(bpy.path.abspath("//"), self.link))
+ self.library_filepath = Path(self.link).with_suffix(".ifc.cache.blend")
if self.mode == "WIREFRAME":
self.toggle_wireframe(link)
elif self.mode == "VISIBLE":
@@ -1030,25 +1072,17 @@ class ToggleLinkVisibility(bpy.types.Operator):
def toggle_visibility(self, link):
linked_collections = self.get_linked_collections()
- queue = [bpy.context.view_layer.layer_collection]
- layer_collection = None
- while queue:
- layer = queue.pop()
- if layer.collection in linked_collections:
- layer_collection = layer
- break
- queue.extend(list(layer.children))
-
- if layer_collection:
+ layer_collections = tool.Blender.get_layer_collections_mapping(linked_collections)
+ for layer_collection in layer_collections.values():
layer_collection.exclude = not layer_collection.exclude
link.is_hidden = layer_collection.exclude
- def get_linked_collections(self):
+ def get_linked_collections(self) -> list[bpy.types.Collection]:
return [
c
for c in bpy.data.collections
- if "IfcProject" in c.name and c.library and c.library.filepath.replace("\\", "/") == self.filepath
+ if "IfcProject" in c.name and c.library and Path(c.library.filepath) == self.library_filepath
]
@@ -1262,6 +1296,8 @@ class LoadLinkedProject(bpy.types.Operator):
if len(shape.geometry.faces) > 1000 and self.is_local(shape): # 333 tris
self.process_occurrence(shape)
if not iterator.next():
+ if not chunked_verts:
+ break
mats = np.concatenate(chunked_materials)
midx = np.concatenate(chunked_material_ids)
mats, mapping = np.unique(mats, axis=0, return_inverse=True)
@@ -1297,6 +1333,9 @@ class LoadLinkedProject(bpy.types.Operator):
ms = np.vstack([default_mat, np.frombuffer(shape.geometry.colors_buffer).reshape((-1, 4))])
mi = np.frombuffer(shape.geometry.material_ids_buffer, dtype=np.int32)
+ for geom_material_idx, geom_material in enumerate(shape.geometry.materials):
+ if not geom_material.has_diffuse:
+ ms[geom_material_idx + 1] = (0.8, 0.8, 0.8, 1)
chunked_materials.append(ms)
chunked_material_ids.append(mi + material_offset + 1)
material_offset += len(ms)
@@ -1417,7 +1456,10 @@ class LoadLinkedProject(bpy.types.Operator):
alpha = 1.0
if material.has_transparency and material.transparency > 0:
alpha = 1.0 - material.transparency
- diffuse = material.diffuse + (alpha,)
+ if material.has_diffuse:
+ diffuse = material.diffuse + (alpha,)
+ else:
+ diffuse = (0.8, 0.8, 0.8, 1) # Blender's default material
material_name = f"{diffuse[0]}-{diffuse[1]}-{diffuse[2]}-{diffuse[3]}"
blender_mat = self.blender_mats.get(material_name, None)
if not blender_mat:
@@ -1495,6 +1537,7 @@ class LoadLinkedProject(bpy.types.Operator):
obj["guids"] = list(guids)
obj["guid_ids"] = list(guid_ids)
obj["db"] = self.db_filepath
+ obj["ifc_filepath"] = self.filepath
self.collection.objects.link(obj)
@@ -1626,6 +1669,42 @@ class QueryLinkedElement(bpy.types.Operator):
return self.execute(context)
+class AppendInspectedLinkedElement(AppendLibraryElement):
+ bl_idname = "bim.append_inspected_linked_element"
+ bl_label = "Append Inspected Linked Element"
+ bl_description = "Append inspected linked element"
+ bl_options = {"REGISTER"}
+
+ def _execute(self, context):
+ from blenderbim.bim.module.project.data import LinksData
+
+ if not LinksData.linked_data:
+ self.report({"INFO"}, "No linked element found.")
+ return {"CANCELLED"}
+
+ guid = LinksData.linked_data["attributes"].get("GlobalId")
+ if guid is None:
+ self.report({"INFO"}, "Cannot find Global Id for element.")
+ return {"CANCELLED"}
+
+ queried_obj = context.scene.BIMProjectProperties.queried_obj
+ ifc_file = ifcopenshell.open(queried_obj["ifc_filepath"])
+ element_to_append = ifc_file.by_guid(guid)
+ element = ifcopenshell.api.run(
+ "project.append_asset",
+ tool.Ifc.get(),
+ library=ifc_file,
+ element=element_to_append,
+ )
+ self.import_product_from_ifc(element, context)
+ element_type = ifcopenshell.util.element.get_type(element)
+ obj = tool.Ifc.get_object(element_type)
+ if obj is None:
+ self.import_type_from_ifc(element_type, context)
+
+ return {"FINISHED"}
+
+
class EnableCulling(bpy.types.Operator):
bl_idname = "bim.enable_culling"
bl_label = "Enable Culling"
@@ -1843,32 +1922,32 @@ class CreateClippingPlane(bpy.types.Operator):
bl_label = "Create Clipping Plane"
bl_options = {"REGISTER", "UNDO"}
- @classmethod
- def poll(cls, context):
- return context.area.type == "VIEW_3D"
-
def execute(self, context):
from bpy_extras.view3d_utils import region_2d_to_vector_3d, region_2d_to_origin_3d
# Clean up deleted planes
- if len(bpy.context.scene.BIMProjectProperties.clipping_planes) > 5:
+ if len(context.scene.BIMProjectProperties.clipping_planes) > 5:
self.report({"INFO"}, "Maximum of six clipping planes allowed.")
return {"FINISHED"}
- for area in bpy.context.screen.areas:
+ for area in context.screen.areas:
if area.type == "VIEW_3D":
area.tag_redraw()
region = context.region
rv3d = context.region_data
- coord = (self.mouse_x, self.mouse_y)
- origin = region_2d_to_origin_3d(region, rv3d, coord)
- direction = region_2d_to_vector_3d(region, rv3d, coord)
- hit, location, normal, face_index, obj, matrix = self.ray_cast(context, origin, direction)
- if not hit:
- self.report({"INFO"}, "No object found.")
- return {"FINISHED"}
+ if rv3d: # Called from a 3D viewport
+ coord = (self.mouse_x, self.mouse_y)
+ origin = region_2d_to_origin_3d(region, rv3d, coord)
+ direction = region_2d_to_vector_3d(region, rv3d, coord)
+ hit, location, normal, face_index, obj, matrix = self.ray_cast(context, origin, direction)
+ if not hit:
+ self.report({"INFO"}, "No object found.")
+ return {"FINISHED"}
+ else: # Not Called from a 3D viewport
+ location = (0, 0, 1)
+ normal = (0, 0, 1)
vertices = [(-0.5, -0.5, 0), (0.5, -0.5, 0), (0.5, 0.5, 0), (-0.5, 0.5, 0)]
@@ -1879,20 +1958,20 @@ class CreateClippingPlane(bpy.types.Operator):
mesh.update()
plane_obj = bpy.data.objects.new("ClippingPlane", mesh)
- bpy.context.collection.objects.link(plane_obj)
+ context.collection.objects.link(plane_obj)
z_axis = Vector((0, 0, 1))
rotation_matrix = z_axis.rotation_difference(normal).to_matrix().to_4x4()
plane_obj.matrix_world = rotation_matrix
plane_obj.matrix_world.translation = location
- bpy.context.scene.cursor.location = location
+ context.scene.cursor.location = location
- new = bpy.context.scene.BIMProjectProperties.clipping_planes.add()
+ new = context.scene.BIMProjectProperties.clipping_planes.add()
new.obj = plane_obj
tool.Blender.set_active_object(plane_obj)
- ClippingPlaneDecorator.install(bpy.context)
+ ClippingPlaneDecorator.install(context)
bpy.ops.bim.refresh_clipping_planes("INVOKE_DEFAULT")
return {"FINISHED"}
@@ -1914,11 +1993,69 @@ class FlipClippingPlane(bpy.types.Operator):
@classmethod
def poll(cls, context):
- return context.area.type == "VIEW_3D"
+ return context.active_object
def execute(self, context):
- obj = bpy.context.active_object
- if obj in [cp.obj for cp in bpy.context.scene.BIMProjectProperties.clipping_planes]:
+ obj = context.active_object
+ if obj in context.scene.BIMProjectProperties.clipping_planes_objs:
obj.rotation_euler[0] += radians(180)
- bpy.context.view_layer.update()
+ context.view_layer.update()
+ return {"FINISHED"}
+
+
+CLIPPING_PLANES_FILE_NAME = "ClippingPlanes.json" # TODO un-hardcode :=
+
+
+class BIM_OT_save_clipping_planes(bpy.types.Operator):
+ bl_idname = "bim.save_clipping_planes"
+ bl_label = "Save Clipping Planes"
+ bl_description = "Save Clipping Planes to Disk"
+ bl_options = {"REGISTER", "UNDO"}
+
+ @classmethod
+ def poll(cls, context):
+ if IfcStore.path:
+ return context.scene.BIMProjectProperties.clipping_planes
+ cls.poll_message_set("Please Save The IFC File")
+
+ def execute(self, context):
+ clipping_planes_to_serialize = defaultdict(dict)
+ clipping_planes = context.scene.BIMProjectProperties.clipping_planes
+ for clipping_plane in clipping_planes:
+ obj = clipping_plane.obj
+ name = obj.name
+ clipping_planes_to_serialize[name]["location"] = obj.location[0:3]
+ clipping_planes_to_serialize[name]["rotation"] = obj.rotation_euler[0:3]
+ with open(Path(IfcStore.path).with_name(CLIPPING_PLANES_FILE_NAME), "w") as file:
+ json.dump(clipping_planes_to_serialize, file, indent=4)
+ return {"FINISHED"}
+
+
+class BIM_OT_load_clipping_planes(bpy.types.Operator):
+ bl_idname = "bim.load_clipping_planes"
+ bl_label = "Load Clipping Planes"
+ bl_description = "Load Clipping Planes from Disk"
+ bl_options = {"REGISTER", "UNDO"}
+
+ @classmethod
+ def poll(cls, context):
+ if filepath := IfcStore.path:
+ if Path(filepath).with_name(CLIPPING_PLANES_FILE_NAME).exists():
+ return True
+ else:
+ cls.poll_message_set(f"No Clipping Planes File in Folder {filepath}")
+ else:
+ cls.poll_message_set("Please Save The IFC File")
+
+ def execute(self, context):
+ bpy.data.batch_remove(context.scene.BIMProjectProperties.clipping_planes_objs)
+ context.scene.BIMProjectProperties.clipping_planes.clear()
+ with open(Path(IfcStore.path).with_name(CLIPPING_PLANES_FILE_NAME), "r") as file:
+ clipping_planes_dict = json.load(file)
+ for name, values in clipping_planes_dict.items():
+ bpy.ops.bim.create_clipping_plane()
+ obj = context.scene.BIMProjectProperties.clipping_planes_objs[-1]
+ obj.name = name
+ obj.location = values["location"]
+ obj.rotation_euler = values["rotation"]
return {"FINISHED"}
diff --git a/src/blenderbim/blenderbim/bim/module/project/prop.py b/src/blenderbim/blenderbim/bim/module/project/prop.py
index e988a89f5b..6e34461be4 100644
--- a/src/blenderbim/blenderbim/bim/module/project/prop.py
+++ b/src/blenderbim/blenderbim/bim/module/project/prop.py
@@ -187,6 +187,11 @@ class BIMProjectProperties(PropertyGroup):
use_relative_project_path: BoolProperty(name="Use Relative Project Path", default=False)
queried_obj: bpy.props.PointerProperty(type=bpy.types.Object)
clipping_planes: bpy.props.CollectionProperty(type=ObjProperty)
+ clipping_planes_active: bpy.props.IntProperty(min=0, default=0, max=5)
+
+ @property
+ def clipping_planes_objs(self):
+ return list({cp.obj for cp in self.clipping_planes if cp.obj})
def get_library_element_index(self, lib_element):
return next((i for i in range(len(self.library_elements)) if self.library_elements[i] == lib_element))
diff --git a/src/blenderbim/blenderbim/bim/module/project/ui.py b/src/blenderbim/blenderbim/bim/module/project/ui.py
index 1a73ef061b..e961bf7499 100644
--- a/src/blenderbim/blenderbim/bim/module/project/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/project/ui.py
@@ -267,10 +267,9 @@ class BIM_PT_project_library(Panel):
)
row.operator("bim.append_library_element_by_query", text="", icon="APPEND_BLEND")
row.operator("bim.save_library_file", text="", icon="EXPORT")
+ self.draw_library_ul()
else:
row.label(text="No Library Loaded", icon="ASSET_MANAGER")
- if IfcStore.library_file:
- self.draw_library_ul()
def draw_library_ul(self):
if not self.props.library_elements:
@@ -319,11 +318,16 @@ class BIM_PT_links(Panel):
row = self.layout.row(align=True)
row.label(text="Object Culling Enabled", icon="MOD_TRIANGULATE")
- if not LinksData.linked_data:
- row = self.layout.row(align=True)
- row.label(text="No Object Selected", icon="QUESTION")
+ if not LinksData.linked_data or not self.props.links:
+ if self.props.links:
+ row = self.layout.row(align=True)
+ row.label(text="No Object Selected", icon="QUESTION")
return
+ row = self.layout.row(align=True)
+ row.label(text="")
+ row.operator("bim.append_inspected_linked_element", icon="APPEND_BLEND", text="")
+
for name, value in LinksData.linked_data["attributes"].items():
row = self.layout.row(align=True)
row.label(text=name)
diff --git a/src/blenderbim/blenderbim/bim/module/pset/data.py b/src/blenderbim/blenderbim/bim/module/pset/data.py
index 739b1d7c97..0c3093f3a5 100644
--- a/src/blenderbim/blenderbim/bim/module/pset/data.py
+++ b/src/blenderbim/blenderbim/bim/module/pset/data.py
@@ -69,6 +69,7 @@ class ObjectPsetsData(Data):
@classmethod
def load(cls):
cls.data = {
+ "is_occurrence": cls.is_occurrence(),
"psets": cls.psetqtos(tool.Ifc.get_entity(bpy.context.active_object), psets_only=True),
"inherited_psets": cls.inherited_psets(),
"pset_name": cls.pset_name(),
@@ -76,14 +77,18 @@ class ObjectPsetsData(Data):
}
cls.is_loaded = True
+ @classmethod
+ def is_occurrence(cls):
+ return not tool.Ifc.get_entity(bpy.context.active_object).is_a("IfcTypeObject")
+
@classmethod
def inherited_psets(cls):
element = tool.Ifc.get_entity(bpy.context.active_object)
if element.is_a("IfcTypeObject"):
- return
+ return []
element_type = ifcopenshell.util.element.get_type(element)
if element_type:
- return cls.psetqtos(element_type)
+ return cls.psetqtos(element_type, psets_only=True)
@classmethod
def pset_name(cls):
@@ -125,9 +130,26 @@ class ObjectQtosData(Data):
@classmethod
def load(cls):
- cls.data = {"qtos": cls.psetqtos(tool.Ifc.get_entity(bpy.context.active_object), qtos_only=True)}
+ cls.data = {
+ "is_occurrence": cls.is_occurrence(),
+ "qtos": cls.psetqtos(tool.Ifc.get_entity(bpy.context.active_object), qtos_only=True),
+ "inherited_qsets": cls.inherited_qsets(),
+ }
cls.is_loaded = True
+ @classmethod
+ def is_occurrence(cls):
+ return not tool.Ifc.get_entity(bpy.context.active_object).is_a("IfcTypeObject")
+
+ @classmethod
+ def inherited_qsets(cls):
+ element = tool.Ifc.get_entity(bpy.context.active_object)
+ if element.is_a("IfcTypeObject"):
+ return []
+ element_type = ifcopenshell.util.element.get_type(element)
+ if element_type:
+ return cls.psetqtos(element_type, qtos_only=True)
+
class MaterialPsetsData(Data):
data = {}
diff --git a/src/blenderbim/blenderbim/bim/module/pset/operator.py b/src/blenderbim/blenderbim/bim/module/pset/operator.py
index 66d0701697..c699a63d52 100644
--- a/src/blenderbim/blenderbim/bim/module/pset/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/pset/operator.py
@@ -85,7 +85,6 @@ class EnablePsetEditing(bpy.types.Operator):
def execute(self, context):
self.props = get_pset_props(context, self.obj, self.obj_type)
self.props.properties.clear()
- ifc_definition_id = blenderbim.bim.helper.get_obj_ifc_definition_id(context, self.obj, self.obj_type)
if self.pset_id:
pset = tool.Ifc.get().by_id(self.pset_id)
@@ -250,7 +249,7 @@ class DisablePsetEditing(bpy.types.Operator, Operator):
props = get_pset_props(context, self.obj, self.obj_type)
if props.active_pset_id:
pset = tool.Ifc.get().by_id(props.active_pset_id)
- ifc_definition_id = blenderbim.bim.helper.get_obj_ifc_definition_id(context, self.obj, self.obj_type)
+ ifc_definition_id = tool.Blender.get_obj_ifc_definition_id(self.obj, self.obj_type, context)
if tool.Pset.is_pset_empty(pset):
ifcopenshell.api.run(
"pset.remove_pset", tool.Ifc.get(), product=tool.Ifc.get().by_id(ifc_definition_id), pset=pset
@@ -272,7 +271,7 @@ class EditPset(bpy.types.Operator, Operator):
def _execute(self, context):
self.file = IfcStore.get_file()
props = get_pset_props(context, self.obj, self.obj_type)
- ifc_definition_id = blenderbim.bim.helper.get_obj_ifc_definition_id(context, self.obj, self.obj_type)
+ ifc_definition_id = tool.Blender.get_obj_ifc_definition_id(self.obj, self.obj_type, context)
element = tool.Ifc.get().by_id(ifc_definition_id)
properties = {}
@@ -343,7 +342,7 @@ class RemovePset(bpy.types.Operator, Operator):
pset_name = tool.Ifc.get().by_id(self.pset_id).Name
for obj in objects:
props = get_pset_props(context, obj, self.obj_type)
- ifc_definition_id = blenderbim.bim.helper.get_obj_ifc_definition_id(context, obj, self.obj_type)
+ ifc_definition_id = tool.Blender.get_obj_ifc_definition_id(obj, self.obj_type, context)
element = tool.Ifc.get().by_id(ifc_definition_id)
pset = ifcopenshell.util.element.get_psets(element, should_inherit=False).get(pset_name, None)
if pset:
@@ -374,7 +373,7 @@ class AddQto(bpy.types.Operator, Operator):
def _execute(self, context):
self.file = IfcStore.get_file()
props = get_pset_props(context, self.obj, self.obj_type)
- ifc_definition_id = blenderbim.bim.helper.get_obj_ifc_definition_id(context, self.obj, self.obj_type)
+ ifc_definition_id = tool.Blender.get_obj_ifc_definition_id(self.obj, self.obj_type, context)
element = tool.Ifc.get().by_id(ifc_definition_id)
bpy.ops.bim.enable_pset_editing(
pset_id=0, pset_name=props.qto_name, pset_type="QTO", obj=self.obj, obj_type=self.obj_type
diff --git a/src/blenderbim/blenderbim/bim/module/pset/qto_calculator.py b/src/blenderbim/blenderbim/bim/module/pset/qto_calculator.py
index 2452708fbe..713f549d81 100644
--- a/src/blenderbim/blenderbim/bim/module/pset/qto_calculator.py
+++ b/src/blenderbim/blenderbim/bim/module/pset/qto_calculator.py
@@ -25,8 +25,16 @@ from shapely.geometry import Polygon
from shapely.ops import unary_union
import blenderbim.tool as tool
import ifcopenshell
+import ifcopenshell.geom
+import ifcopenshell.util.element
from blenderbim.bim.module.pset.calc_quantity_function_mapper import mapper
import blenderbim.bim
+from typing import Literal, Union, Optional
+
+
+AxisType = Literal["x", "y", "z"]
+VectorTuple = tuple[float, float, float]
+QuanityTypes = Literal["Q_LENGTH", "Q_AREA", "Q_VOLUME"]
class QtoCalculator:
@@ -45,7 +53,7 @@ class QtoCalculator:
else:
self.mapping_dict[key][item] = None
- def calculate_quantity(self, qto_name, quantity_name, obj):
+ def calculate_quantity(self, qto_name: str, quantity_name: str, obj: bpy.types.Object) -> float:
"""calculates the value of the quantity in the project units"""
string = "self.mapping_dict[qto_name][quantity_name](obj"
if isinstance(mapper[qto_name][quantity_name], dict):
@@ -54,11 +62,13 @@ class QtoCalculator:
args = ""
string += args
string += ")"
- value = eval(string)
+ value: float = eval(string)
return tool.Qto.convert_to_project_units(value, qto_name, quantity_name) or value
- def guess_quantity(self, prop_name, alternative_prop_names, obj):
+ def guess_quantity(
+ self, prop_name: str, alternative_prop_names: list[str], obj: bpy.types.Object
+ ) -> Union[float, None]:
"""guess the value of the quantity by name, returns the value in the project units"""
prop_name = prop_name.lower()
alternative_prop_names = [p.lower() for p in alternative_prop_names]
@@ -89,7 +99,7 @@ class QtoCalculator:
if value is None:
return
- unit_type_keywords = {
+ unit_type_keywords: dict[str, QuanityTypes] = {
"length": "Q_LENGTH",
"width": "Q_LENGTH",
"height": "Q_LENGTH",
@@ -102,10 +112,10 @@ class QtoCalculator:
unit_type = next(unit_type_keywords[k] for k in unit_type_keywords if k in prop_name)
return tool.Qto.convert_to_project_units(value, quantity_type=unit_type) or value
- def get_units(self, o, vg_index):
+ def get_units(self, o: bpy.types.Object, vg_index: int) -> int:
return len([v for v in o.data.vertices if vg_index in [g.group for g in v.groups]])
- def get_linear_length(self, o):
+ def get_linear_length(self, o: bpy.types.Object) -> float:
"""_summary_: Returns the length of the longest edge of the object bounding box
:param blender-object o: Blender Object
@@ -116,7 +126,7 @@ class QtoCalculator:
z = (Vector(o.bound_box[1]) - Vector(o.bound_box[0])).length
return max(x, y, z)
- def get_length(self, o, vg_index=None, main_axis: str = ""):
+ def get_length(self, o: bpy.types.Object, vg_index: Optional[int] = None, main_axis: str = "") -> float:
if vg_index is None:
x = (Vector(o.bound_box[4]) - Vector(o.bound_box[0])).length
y = (Vector(o.bound_box[3]) - Vector(o.bound_box[0])).length
@@ -141,23 +151,23 @@ class QtoCalculator:
length += self.get_edge_distance(o, e)
return length
- def get_stair_length(self, obj):
+ def get_stair_length(self, obj: bpy.types.Object) -> float:
length = self.get_length(obj)
height = self.get_height(obj)
stair_length = math.sqrt(pow(length, 2) + pow(height, 2))
return stair_length
- def get_net_stair_area(self, obj):
+ def get_net_stair_area(self, obj: bpy.types.Object) -> float:
OBB_obj = self.get_OBB_object(obj)
OBB_net_footprint_area = self.get_net_footprint_area(OBB_obj)
return OBB_net_footprint_area
- def get_gross_stair_area(self, obj):
+ def get_gross_stair_area(self, obj: bpy.types.Object) -> float:
OBB_obj = self.get_OBB_object(obj)
OBB_gross_footprint_area = self.get_gross_footprint_area(OBB_obj)
return OBB_gross_footprint_area
- def get_parametric_axis(self, obj):
+ def get_parametric_axis(self, obj: bpy.types.Object) -> Literal["AXIS2", "AXIS3", None]:
relating_type = ifcopenshell.util.element.get_type(tool.Ifc.get_entity(obj))
if relating_type:
parametric = ifcopenshell.util.element.get_psets(relating_type).get("EPset_Parametric")
@@ -172,7 +182,7 @@ class QtoCalculator:
return None
return None
- def get_covering_gross_area(self, obj):
+ def get_covering_gross_area(self, obj: bpy.types.Object) -> float:
get_parametric_axis = self.get_parametric_axis(obj)
if not get_parametric_axis:
return self.get_gross_footprint_area(obj)
@@ -181,7 +191,7 @@ class QtoCalculator:
elif get_parametric_axis == "AXIS3":
return self.get_gross_footprint_area(obj)
- def get_covering_net_area(self, obj):
+ def get_covering_net_area(self, obj: bpy.types.Object) -> float:
get_parametric_axis = self.get_parametric_axis(obj)
if not get_parametric_axis:
return self.get_net_footprint_area(obj)
@@ -190,7 +200,7 @@ class QtoCalculator:
elif get_parametric_axis == "AXIS3":
return self.get_net_footprint_area(obj)
- def get_covering_width(self, obj):
+ def get_covering_width(self, obj: bpy.types.Object) -> float:
get_parametric_axis = self.get_parametric_axis(obj)
if not get_parametric_axis:
return self.get_height(obj)
@@ -199,7 +209,7 @@ class QtoCalculator:
elif get_parametric_axis == "AXIS3":
return self.get_height(obj)
- def get_width(self, o):
+ def get_width(self, o: bpy.types.Object) -> float:
"""_summary_: Returns the width of the object bounding box
:param blender-object o: blender object
@@ -209,7 +219,7 @@ class QtoCalculator:
y = (Vector(o.bound_box[3]) - Vector(o.bound_box[0])).length
return min(x, y)
- def get_height(self, o):
+ def get_height(self, o: bpy.types.Object) -> float:
"""_summary_: Returns the height of the object bounding box
:param blender-object o: blender object
@@ -217,64 +227,77 @@ class QtoCalculator:
"""
return (Vector(o.bound_box[1]) - Vector(o.bound_box[0])).length
- def get_opening_height(self, obj):
+ def get_opening_height(self, obj: bpy.types.Object) -> float:
if self.is_opening_horizontal(obj):
return self.get_width(obj)
else:
return self.get_height(obj)
- def get_opening_depth(self, obj):
+ def get_opening_depth(self, obj: bpy.types.Object) -> float:
if self.is_opening_horizontal(obj):
return self.get_height(obj)
else:
return self.get_width(obj)
- def get_opening_mapping_area(self, obj):
+ def get_opening_mapping_area(self, obj: bpy.types.Object) -> float:
if self.is_opening_horizontal(obj):
return self.get_net_footprint_area(obj)
else:
return self.get_net_side_area(obj)
- def get_finish_ceiling_height(self, obj):
- space_height = self.get_height(obj)
+ def get_finish_ceiling_height(self, obj: bpy.types.Object) -> float:
floor_height = self.get_finish_floor_height(obj)
ceiling_height = self.get_ceiling_height(obj)
- finish_ceiling_height = space_height - floor_height - ceiling_height
+ finish_ceiling_height = ceiling_height - floor_height
return finish_ceiling_height
- def get_finish_floor_height(self, obj):
+ def get_max_global_z(self, obj: bpy.types.Object) -> float:
+ z_values = [(obj.matrix_world @ Vector(co))[2] for co in obj.bound_box]
+ return max(z_values)
+
+ def get_min_global_z(self, obj: bpy.types.Object) -> float:
+ z_values = [(obj.matrix_world @ Vector(co))[2] for co in obj.bound_box]
+ return min(z_values)
+
+ def get_finish_floor_height(self, obj: bpy.types.Object) -> float:
+ space_min_z_value = self.get_min_global_z(obj)
+ space_max_z_value = self.get_max_global_z(obj)
+
element = tool.Ifc.get_entity(obj)
decompositions = ifcopenshell.util.element.get_decomposition(element)
- finish_floor_height = 0
+ flooring_max_z_value = space_min_z_value
for decomposition in decompositions:
if (
- decomposition.get_info()["PredefinedType"] == "FLOORING"
- and decomposition.get_info()["type"] == "IfcCovering"
+ decomposition.is_a() == "IfcCovering"
+ and ifcopenshell.util.element.get_predefined_type(decomposition) == "FLOORING"
):
- floor_obj = tool.Ifc.get_object(decomposition)
- new_finish_floor_height = self.get_height(floor_obj)
- if new_finish_floor_height > finish_floor_height:
- finish_floor_height = new_finish_floor_height
+ flooring_obj = tool.Ifc.get_object(decomposition)
+ flooring_z_value = self.get_max_global_z(flooring_obj)
+ if flooring_z_value > space_min_z_value:
+ flooring_max_z_value = flooring_z_value
- return finish_floor_height
+ return flooring_max_z_value - space_min_z_value
+
+ def get_ceiling_height(self, obj: bpy.types.Object) -> float:
+ space_min_z_value = self.get_min_global_z(obj)
+ space_max_z_value = self.get_max_global_z(obj)
- def get_ceiling_height(self, obj):
element = tool.Ifc.get_entity(obj)
decompositions = ifcopenshell.util.element.get_decomposition(element)
- finish_ceiling_height = 0
+ ceiling_min_z_value = space_max_z_value
for decomposition in decompositions:
if (
- decomposition.get_info()["PredefinedType"] == "CEILING"
- and decomposition.get_info()["type"] == "IfcCovering"
+ decomposition.is_a() == "IfcCovering"
+ and ifcopenshell.util.element.get_predefined_type(decomposition) == "CEILING"
):
ceiling_obj = tool.Ifc.get_object(decomposition)
- new_finish_ceiling_height = self.get_height(ceiling_obj)
- if new_finish_ceiling_height > finish_ceiling_height:
- finish_ceiling_height = new_finish_ceiling_height
+ ceiling_z_value = self.get_min_global_z(ceiling_obj)
+ if ceiling_z_value < space_max_z_value:
+ ceiling_min_z_value = ceiling_z_value
- return finish_ceiling_height
+ return ceiling_min_z_value - space_min_z_value
- def get_net_perimeter(self, o):
+ def get_net_perimeter(self, o: bpy.types.Object) -> float:
parsed_edges = []
shared_edges = []
perimeter = 0
@@ -289,7 +312,7 @@ class QtoCalculator:
perimeter -= self.get_edge_key_distance(o, edge_key)
return perimeter
- def get_gross_perimeter(self, o):
+ def get_gross_perimeter(self, o: bpy.types.Object) -> float:
element = tool.Ifc.get_entity(o)
mesh = self.get_gross_element_mesh(element)
gross_obj = bpy.data.objects.new("GrossObj", mesh)
@@ -297,15 +320,15 @@ class QtoCalculator:
self.delete_obj(gross_obj)
return gross_perimeter
- def get_space_net_perimeter(self, obj):
+ def get_space_net_perimeter(self, obj: bpy.types.Object) -> float:
pass
- def get_rectangular_perimeter(self, obj):
+ def get_rectangular_perimeter(self, obj: bpy.types.Object) -> float:
length = self.get_length(obj, main_axis="x")
height = self.get_height(obj)
return (length + height) * 2
- def get_lowest_polygons(self, o):
+ def get_lowest_polygons(self, o: bpy.types.Object) -> list[bpy.types.MeshPolygon]:
lowest_polygons = []
lowest_z = None
for polygon in o.data.polygons:
@@ -321,7 +344,7 @@ class QtoCalculator:
lowest_z = z
return lowest_polygons
- def get_highest_polygons(self, o):
+ def get_highest_polygons(self, o: bpy.types.Object) -> list[bpy.types.MeshPolygon]:
highest_polygons = []
highest_z = None
for polygon in o.data.polygons:
@@ -337,13 +360,13 @@ class QtoCalculator:
highest_z = z
return highest_polygons
- def get_edge_key_distance(self, obj, edge_key):
+ def get_edge_key_distance(self, obj: bpy.types.Object, edge_key: tuple[int, int]) -> float:
return (obj.data.vertices[edge_key[1]].co - obj.data.vertices[edge_key[0]].co).length
- def get_edge_distance(self, obj, edge):
+ def get_edge_distance(self, obj: bpy.types.Object, edge: bpy.types.MeshEdge) -> float:
return (obj.data.vertices[edge.vertices[1]].co - obj.data.vertices[edge.vertices[0]].co).length
- def get_net_floor_area(self, obj):
+ def get_net_floor_area(self, obj: bpy.types.Object) -> float:
decompositions = self.get_obj_decompositions(obj)
if not decompositions:
return self.get_gross_footprint_area(obj)
@@ -359,7 +382,7 @@ class QtoCalculator:
return total_net_floor_area
- def get_gross_ceiling_area(self, obj):
+ def get_gross_ceiling_area(self, obj: bpy.types.Object) -> float:
decompositions = self.get_obj_decompositions(obj)
if not decompositions:
return self.get_gross_top_area(obj)
@@ -367,15 +390,15 @@ class QtoCalculator:
total_gross_ceiling_area = 0
for decomposition in decompositions:
- decomposition_type = decomposition.get_info()["type"]
- decomposition_predefined_type = decomposition.get_info()["PredefinedType"]
- if decomposition_type == "IfcCovering" and decomposition_predefined_type == "CEILING":
+ decomposition_class = decomposition.is_a()
+ decomposition_predefined_type = ifcopenshell.util.element.get_predefined_type(decomposition)
+ if decomposition_class == "IfcCovering" and decomposition_predefined_type == "CEILING":
decomposition_obj = tool.Ifc.get_object(decomposition)
total_gross_ceiling_area += self.get_gross_footprint_area(decomposition_obj)
return total_gross_ceiling_area
- def get_net_ceiling_area(self, obj):
+ def get_net_ceiling_area(self, obj: bpy.types.Object) -> float:
decompositions = self.get_obj_decompositions(obj)
if not decompositions:
return self.get_net_top_area(obj)
@@ -383,19 +406,19 @@ class QtoCalculator:
total_net_ceiling_area = 0
for decomposition in decompositions:
- decomposition_type = decomposition.get_info()["type"]
- decomposition_predefined_type = decomposition.get_info()["PredefinedType"]
- if decomposition_type == "IfcCovering" and decomposition_predefined_type == "CEILING":
+ decomposition_class = decomposition.is_a()
+ decomposition_predefined_type = ifcopenshell.util.element.get_predefined_type(decomposition)
+ if decomposition_class == "IfcCovering" and decomposition_predefined_type == "CEILING":
decomposition_obj = tool.Ifc.get_object(decomposition)
total_net_ceiling_area += self.get_net_footprint_area(decomposition_obj)
- if decomposition_type == "IfcWall" or decomposition_type == "IfcColumn":
+ if decomposition_class == "IfcWall" or decomposition_class == "IfcColumn":
decomposition_obj = tool.Ifc.get_object(decomposition)
total_net_ceiling_area -= self.get_net_roofprint_area(decomposition_obj)
return total_net_ceiling_area
- def get_space_net_volume(self, obj):
+ def get_space_net_volume(self, obj: bpy.types.Object) -> float:
decompositions = self.get_obj_decompositions(obj)
if not decompositions:
return self.get_gross_volume(obj)
@@ -410,7 +433,7 @@ class QtoCalculator:
return total_space_net_volume
- def get_net_footprint_area(self, o):
+ def get_net_footprint_area(self, o: bpy.types.Object) -> float:
"""_summary_: Returns the area of the footprint of the object, excluding any holes
:param blender-object o: blender object
@@ -421,7 +444,7 @@ class QtoCalculator:
area += polygon.area
return area
- def get_gross_footprint_area(self, o):
+ def get_gross_footprint_area(self, o: bpy.types.Object) -> float:
"""_summary_: Returns the area of the footprint of the object, without related opening and excluding any holes
:param blender-object o: blender object
@@ -437,7 +460,7 @@ class QtoCalculator:
self.delete_mesh(mesh)
return gross_footprint_area
- def get_net_roofprint_area(self, o):
+ def get_net_roofprint_area(self, o: bpy.types.Object) -> float:
# Is roofprint the right word? Couldn't think of anything better - vulevukusej
"""_summary_: Returns the area of the net roofprint of the object, excluding any holes
@@ -449,7 +472,7 @@ class QtoCalculator:
area += polygon.area
return area
- def get_side_area(self, o):
+ def get_side_area(self, o: bpy.types.Object) -> float:
# There are a few dumb options for this, but this seems the dumbest
# until I get more practical experience on what works best.
x = (Vector(o.bound_box[4]) - Vector(o.bound_box[0])).length
@@ -457,8 +480,7 @@ class QtoCalculator:
z = (Vector(o.bound_box[1]) - Vector(o.bound_box[0])).length
return max(x * z, y * z)
- def get_cross_section_area(self, obj):
- element = tool.Ifc.get_entity(obj)
+ def get_cross_section_area(self, obj: bpy.types.Object) -> float:
representation = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
item = representation.Items[0]
while True:
@@ -474,7 +496,7 @@ class QtoCalculator:
return area
# TODO handle other types of sections, and then fall back to mesh parsing
- def get_gross_surface_area(self, o, vg_index=None):
+ def get_gross_surface_area(self, o: bpy.types.Object, vg_index: Optional[int] = None) -> float:
if vg_index is None:
if not self.has_openings(o):
return self.get_net_surface_area(o)
@@ -492,29 +514,29 @@ class QtoCalculator:
area += polygon.area
return area
- def get_net_surface_area(self, obj):
+ def get_net_surface_area(self, obj: bpy.types.Object) -> float:
return self.get_mesh_area(obj.data)
- def get_mesh_area(self, mesh):
+ def get_mesh_area(self, mesh: bpy.types.Mesh) -> float:
area = 0
for polygon in mesh.polygons:
area += polygon.area
return area
- def is_polygon_in_vg(self, polygon, vertices_in_vg):
+ def is_polygon_in_vg(self, polygon: bpy.types.MeshPolygon, vertices_in_vg: list[bpy.types.MeshVertex]) -> bool:
for v in polygon.vertices:
if v not in vertices_in_vg:
return False
return True
- def get_net_volume(self, o):
+ def get_net_volume(self, o: bpy.types.Object) -> float:
o_mesh = bmesh.new()
o_mesh.from_mesh(o.data)
volume = o_mesh.calc_volume()
o_mesh.free()
return volume
- def get_gross_volume(self, o):
+ def get_gross_volume(self, o: bpy.types.Object) -> float:
if not self.has_openings(o):
return self.get_net_volume(o)
@@ -529,16 +551,18 @@ class QtoCalculator:
return gross_volume
- def has_openings(self, obj):
+ def has_openings(
+ self, obj: bpy.types.Object
+ ) -> Union[ifcopenshell.entity_instance, list[ifcopenshell.entity_instance]]:
element = tool.Ifc.get_entity(obj)
return element and getattr(element, "HasOpenings", [])
- def get_obj_decompositions(self, obj):
+ def get_obj_decompositions(self, obj: bpy.types.Object) -> list[ifcopenshell.entity_instance]:
element = tool.Ifc.get_entity(obj)
decompositions = ifcopenshell.util.element.get_decomposition(element)
return decompositions
- def get_gross_weight(self, obj):
+ def get_gross_weight(self, obj: bpy.types.Object) -> Union[float, None]:
obj_mass_density = self.get_obj_mass_density(obj)
if not obj_mass_density:
return
@@ -546,7 +570,7 @@ class QtoCalculator:
gross_weight = obj_mass_density * gross_volume
return gross_weight
- def get_net_weight(self, obj):
+ def get_net_weight(self, obj: bpy.types.Object) -> Union[float, None]:
obj_mass_density = self.get_obj_mass_density(obj)
if not obj_mass_density:
return
@@ -554,7 +578,7 @@ class QtoCalculator:
net_weight = obj_mass_density * net_volume
return net_weight
- def get_obj_mass_density(self, obj):
+ def get_obj_mass_density(self, obj: bpy.types.Object) -> Union[float, None]:
entity = tool.Ifc.get_entity(obj)
material = ifcopenshell.util.element.get_material(entity)
if material is None:
@@ -629,7 +653,7 @@ class QtoCalculator:
# volume += v1.dot(v2.cross(v3)) / 6.0
# return volume
- def get_opening_type(self, opening, obj):
+ def get_opening_type(self, opening: bpy.types.Object, obj: bpy.types.Object) -> Literal["OPENING", "RECESS"]:
"""_summary_: Returns the opening type - OPENING / RECESS
:param blender-object opening: blender opening object
@@ -649,14 +673,22 @@ class QtoCalculator:
return "OPENING" if ray_intersections % 2 == 0 else "RECESS"
def get_opening_area(
- self, obj, angle_z1: int = 45, angle_z2: int = 135, min_area: int = 0, ignore_recesses: bool = False
- ):
+ self,
+ obj: bpy.types.Object,
+ angle_z1: int = 45,
+ angle_z2: int = 135,
+ min_area: int = 0,
+ ignore_recesses: bool = False,
+ ) -> float:
"""_summary_: Returns the lateral area of the openings in the object.
:param obj: blender object
- :param int angle_z1: Angle measured from the positive z-axis to the normal-vector of the opening area. Openings with a normal_vector lower than this value will be ignored, defaults to 45
- :param int angle_z2: Angle measured from the positive z-axis to the normal-vector of the opening area. Openings with a normal_vector greater than this value will be ignored,defaults to 135
- :param float min_area: Minimum opening area to consider. Values lower than this will be ignored, defaults to 0
+ :param int angle_z1: Angle measured from the positive z-axis to the normal-vector of the opening area.
+ Openings with a normal_vector lower than this value will be ignored, defaults to 45
+ :param int angle_z2: Angle measured from the positive z-axis to the normal-vector of the opening area.
+ Openings with a normal_vector greater than this value will be ignored,defaults to 135
+ :param float min_area: Minimum opening area to consider. Values lower than this will be ignored,
+ defaults to 0
:param bool ignore_recesses: Toggle whether recess areas should be considered, defaults to False
:return float: Opening Area
"""
@@ -702,14 +734,14 @@ class QtoCalculator:
def get_lateral_area(
self,
- obj,
+ obj: bpy.types.Object,
subtract_openings: bool = True,
exclude_end_areas: bool = False,
exclude_side_areas: bool = False,
angle_z1: int = 45,
angle_z2: int = 135,
main_axis: str = "",
- ):
+ ) -> float:
"""_summary_
:param blender-object obj: blender object, bpy.types.Object
@@ -760,7 +792,7 @@ class QtoCalculator:
area += polygon.area
return area + total_opening_area
- def get_gross_side_area(self, obj):
+ def get_gross_side_area(self, obj: bpy.types.Object) -> float:
if not self.has_openings(obj):
return self.get_net_side_area(obj)
@@ -768,15 +800,15 @@ class QtoCalculator:
return gross_side_area
- def get_net_side_area(self, obj):
+ def get_net_side_area(self, obj: bpy.types.Object) -> float:
net_side_area = self.get_lateral_area(obj, exclude_end_areas=True, main_axis="x") / 2
return net_side_area
- def get_outer_surface_area(self, obj):
+ def get_outer_surface_area(self, obj: bpy.types.Object) -> float:
outer_surface_area = self.get_lateral_area(obj, exclude_end_areas=True, angle_z1=0, angle_z2=360)
return outer_surface_area
- def get_end_area(self, obj):
+ def get_end_area(self, obj: bpy.types.Object) -> float:
element = tool.Ifc.get_entity(obj)
gross_mesh = self.get_gross_element_mesh(element)
gross_obj = bpy.data.objects.new("MyObject", gross_mesh)
@@ -790,7 +822,7 @@ class QtoCalculator:
return end_area
- def get_gross_top_area(self, obj, angle: int = 45):
+ def get_gross_top_area(self, obj: bpy.types.Object, angle: int = 45) -> float:
"""_summary_: Returns the gross top area of the object.
:param blender-object obj: blender object
@@ -827,12 +859,14 @@ class QtoCalculator:
return area + opening_area
# curently net top area is larger then projected area, because its taking into account internal polygons, or window sills
- def get_net_top_area(self, obj, angle: int = 45, ignore_internal: bool = True):
+ def get_net_top_area(self, obj: bpy.types.Object, angle: int = 45, ignore_internal: bool = True) -> float:
"""_summary_: Returns the net top area of the object.
:param blender-object obj: blender object
- :param int angle: Angle measured from the positive z-axis to the normal-vector of the area. Values lower than this will be ignored, defaults to 45
- :param bool ignore_internal: Toggle whether internal areas should be subtracted (Like window sills), defaults to True
+ :param int angle: Angle measured from the positive z-axis to the normal-vector of the area.
+ Values lower than this will be ignored, defaults to 45
+ :param bool ignore_internal: Toggle whether internal areas should be subtracted (Like window sills),
+ defaults to True
:return float: Net Top Area
"""
z_axis = (0, 0, 1)
@@ -852,11 +886,11 @@ class QtoCalculator:
return area
- def get_projected_area(self, obj, projection_axis: str = "z", is_gross: bool = True):
+ def get_projected_area(self, obj, projection_axis: AxisType = "z", is_gross: bool = True) -> float:
"""_summary_: Returns the projected area of the object.
:param blender-object obj: blender object
- :param str projection_axis: Axis to project the area onto. Can be "X", "Y" or "Z"
+ :param str projection_axis: Axis to project the area onto. Can be "x", "y" or "z"
:param bool is_gross: if True, the projected area will include openings, if False, the projected area will exclude openings
:return float: Projected Area
"""
@@ -891,7 +925,7 @@ class QtoCalculator:
return projected_polygon.area + void_area
return projected_polygon.area
- def get_OBB_object(self, obj):
+ def get_OBB_object(self, obj: bpy.types.Object) -> bpy.types.Object:
"""_summary_: Returns the Oriented-Bounding-Box (OBB) of the object.
:param blender-object obj: Blender Object
@@ -932,7 +966,7 @@ class QtoCalculator:
return new_OBB_object
- def get_AABB_object(self, obj):
+ def get_AABB_object(self, obj: bpy.types.Object) -> bpy.types.Object:
"""_summary_: Returns the Axis-Aligned-Bounding-Box (AABB) of the object.
:param blender-object obj: Blender Object
@@ -988,12 +1022,12 @@ class QtoCalculator:
def get_bisected_obj(
self,
- obj,
- plane_co_pos,
- plane_no_pos,
- plane_co_neg,
- plane_no_neg,
- ):
+ obj: bpy.types.Object,
+ plane_co_pos: VectorTuple,
+ plane_no_pos: VectorTuple,
+ plane_co_neg: VectorTuple,
+ plane_no_neg: VectorTuple,
+ ) -> bpy.types.Object:
"""_summary_: Returns the object bisected by two planes.
:param blender-object obj: Blender Object
@@ -1031,11 +1065,12 @@ class QtoCalculator:
return bis_obj
- def get_total_contact_area(self, obj, class_filter: str = ["IfcElement"]):
+ def get_total_contact_area(self, obj: bpy.types.Object, class_filter: list[str] = ["IfcElement"]) -> float:
"""_summary_: Returns the total contact area of the object with other objects.
:param blender-object obj: Blender Object
- :param list [] class_filter: A list of classes used to filter the objects to be considered for the calculation. Example: ["IfcWall"] or ["IfcWall", "IfcSlab"]
+ :param list [] class_filter: A list of classes used to filter the objects
+ to be considered for the calculation. Example: ["IfcWall"] or ["IfcWall", "IfcSlab"]
:return float: Total contact area of the object with other objects.
"""
total_contact_area = 0
@@ -1046,11 +1081,12 @@ class QtoCalculator:
return total_contact_area
- def get_touching_objects(self, obj, class_filter):
+ def get_touching_objects(self, obj: bpy.types.Object, class_filter: list[str]) -> list[bpy.types.Object]:
"""_summary_: Returns a list of objects that are touching the object.
:param blender-object obj: Blender Object
- :param list [] class_filter: A list of classes used to filter the objects to be considered for the calculation. Example: ["IfcWall"] or ["IfcWall", "IfcSlab"]
+ :param list [] class_filter: A list of classes used to filter the objects
+ to be considered for the calculation. Example: ["IfcWall"] or ["IfcWall", "IfcSlab"]
:return list: List of touching objects
"""
# rotate the object ever so slightly, otherwise bvhtree.overlap won't work properly. https://blender.stackexchange.com/a/275244/130742
@@ -1094,7 +1130,7 @@ class QtoCalculator:
return touching_objects
- def get_contact_area(self, object1, object2):
+ def get_contact_area(self, object1: bpy.types.Object, object2: bpy.types.Object) -> float:
"""_summary_: Returns the contact area between two objects.
:param blender-object obj: Blender Object
@@ -1109,7 +1145,13 @@ class QtoCalculator:
total_area += self.get_intersection_between_polygons(object1, poly1, object2, poly2)
return total_area
- def get_intersection_between_polygons(self, object1, poly1, object2, poly2):
+ def get_intersection_between_polygons(
+ self,
+ object1: bpy.types.Object,
+ poly1: bpy.types.MeshPolygon,
+ object2: bpy.types.Object,
+ poly2: bpy.types.MeshPolygon,
+ ) -> float:
"""_summary_: Returns the intersection between two polygons.
:param blender-object object1: Blender Object
@@ -1152,7 +1194,9 @@ class QtoCalculator:
# TopologicalError - Generated Geometry might be invalid
return 0
- def create_shapely_polygon(self, obj, polygon, trans_matrix):
+ def create_shapely_polygon(
+ self, obj: bpy.types.Object, polygon: bpy.types.MeshPolygon, trans_matrix: Matrix
+ ) -> Polygon:
"""_summary_: Create a shapely polygon
:param blender-object obj: Blender Object
@@ -1171,12 +1215,14 @@ class QtoCalculator:
polygon_tuples.append((x, y))
return Polygon(polygon_tuples)
- def get_gross_element_mesh(self, element):
+ def get_gross_element_mesh(self, element: ifcopenshell.entity_instance) -> bpy.types.Mesh:
settings = ifcopenshell.geom.settings()
settings.set(settings.DISABLE_OPENING_SUBTRACTIONS, True)
return self.create_mesh_from_shape(element, settings)
- def create_mesh_from_shape(self, element, settings=None):
+ def create_mesh_from_shape(
+ self, element: ifcopenshell.entity_instance, settings: Optional[ifcopenshell.geom.settings] = None
+ ) -> bpy.types.Mesh:
if settings is None:
settings = ifcopenshell.geom.settings()
shape = ifcopenshell.geom.create_shape(settings, element)
@@ -1203,12 +1249,12 @@ class QtoCalculator:
mesh.update()
return mesh
- def get_bmesh_from_mesh(self, mesh):
+ def get_bmesh_from_mesh(self, mesh: bpy.types.Mesh) -> bmesh.types.BMesh:
bm = bmesh.new()
bm.from_mesh(mesh)
return bm
- def get_object_main_axis(self, o):
+ def get_object_main_axis(self, o: bpy.types.Object) -> AxisType:
"""_summary_: Returns the main object axis. Useful for profile-defined objects.
:param blender-object o: Blender Object
@@ -1227,18 +1273,18 @@ class QtoCalculator:
else:
return "x"
- def is_opening_horizontal(self, o):
+ def is_opening_horizontal(self, o: bpy.types.Object) -> bool:
x = (Vector(o.bound_box[4]) - Vector(o.bound_box[0])).length
y = (Vector(o.bound_box[3]) - Vector(o.bound_box[0])).length
z = (Vector(o.bound_box[1]) - Vector(o.bound_box[0])).length
return z < x and z < y
- def delete_mesh(self, mesh):
+ def delete_mesh(self, mesh: bpy.types.Mesh) -> None:
mesh.user_clear()
bpy.data.meshes.remove(mesh)
- def delete_obj(self, obj):
+ def delete_obj(self, obj: bpy.types.Object) -> None:
bpy.data.objects.remove(obj, do_unlink=True)
diff --git a/src/blenderbim/blenderbim/bim/module/pset/ui.py b/src/blenderbim/blenderbim/bim/module/pset/ui.py
index 549a9fbe18..8a715e4718 100644
--- a/src/blenderbim/blenderbim/bim/module/pset/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/pset/ui.py
@@ -33,7 +33,6 @@ from blenderbim.bim.module.pset.data import (
GroupPsetData,
ProfilePsetsData,
WorkSchedulePsetsData,
-
)
@@ -91,7 +90,7 @@ def get_active_pset_obj_name(context, obj_type):
return ""
-def draw_psetqto_ui(context, pset_id, pset, props, layout, obj_type):
+def draw_psetqto_ui(context, pset_id, pset, props, layout, obj_type, allow_removing=True):
box = layout.box()
row = box.row(align=True)
if "is_expanded" not in pset:
@@ -115,16 +114,20 @@ def draw_psetqto_ui(context, pset_id, pset, props, layout, obj_type):
op.pset_id = pset_id
op.obj = obj_name
op.obj_type = obj_type
- op = row.operator("bim.remove_pset", icon="X", text="")
+ remove_pset_row = row.row(align=True)
+ op = remove_pset_row.operator("bim.remove_pset", icon="X", text="")
op.pset_id = pset_id
op.obj = obj_name
op.obj_type = obj_type
+ remove_pset_row.enabled = allow_removing
elif props.active_pset_id != pset_id:
row.label(text=pset["Name"], icon="COPY_ID")
- op = row.operator("bim.remove_pset", icon="X", text="")
+ remove_pset_row = row.row(align=True)
+ op = remove_pset_row.operator("bim.remove_pset", icon="X", text="")
op.pset_id = pset_id
op.obj = obj_name
op.obj_type = obj_type
+ remove_pset_row.enabled = allow_removing
if pset["is_expanded"]:
if props.active_pset_id == pset_id:
for prop in props.properties:
@@ -172,7 +175,7 @@ def draw_psetqto_editable_ui(box, props, prop):
class BIM_PT_object_psets(Panel):
- bl_label = "Object Property Sets"
+ bl_label = "Property Sets"
bl_idname = "BIM_PT_object_psets"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
@@ -204,17 +207,22 @@ class BIM_PT_object_psets(Panel):
if not props.active_pset_id and props.active_pset_name and props.active_pset_type == "PSET":
draw_psetqto_ui(context, 0, {}, props, self.layout, "Object")
- for pset in ObjectPsetsData.data["psets"]:
- draw_psetqto_ui(context, pset["id"], pset, props, self.layout, "Object")
+ if ObjectPsetsData.data["psets"]:
+ if ObjectPsetsData.data["is_occurrence"]:
+ self.layout.label(text="Occurrence Properties:")
+ else:
+ self.layout.label(text="Type Properties:")
+ for pset in ObjectPsetsData.data["psets"]:
+ draw_psetqto_ui(context, pset["id"], pset, props, self.layout, "Object")
if ObjectPsetsData.data["inherited_psets"]:
- self.layout.label(text="Inherited Psets:", icon="FILE_PARENT")
+ self.layout.label(text="Inherited Type Properties:", icon="CON_CHILDOF")
for pset in ObjectPsetsData.data["inherited_psets"]:
- draw_psetqto_ui(context, pset["id"], pset, props, self.layout, "Object")
+ draw_psetqto_ui(context, pset["id"], pset, props, self.layout, "Object", allow_removing=False)
class BIM_PT_object_qtos(Panel):
- bl_label = "Object Quantity Sets"
+ bl_label = "Quantity Sets"
bl_idname = "BIM_PT_object_qtos"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
@@ -246,8 +254,18 @@ class BIM_PT_object_qtos(Panel):
if not props.active_pset_id and props.active_pset_name and props.active_pset_type == "QTO":
draw_psetqto_ui(context, 0, {}, props, self.layout, "Object")
- for qto in ObjectQtosData.data["qtos"]:
- draw_psetqto_ui(context, qto["id"], qto, props, self.layout, "Object")
+ if ObjectQtosData.data["qtos"]:
+ if ObjectQtosData.data["is_occurrence"]:
+ self.layout.label(text="Occurrence Quantities:")
+ else:
+ self.layout.label(text="Type Quantities:")
+ for qto in ObjectQtosData.data["qtos"]:
+ draw_psetqto_ui(context, qto["id"], qto, props, self.layout, "Object")
+
+ if ObjectQtosData.data["inherited_qsets"]:
+ self.layout.label(text="Inherited Type Quantities:", icon="CON_CHILDOF")
+ for qset in ObjectQtosData.data["inherited_qsets"]:
+ draw_psetqto_ui(context, qset["id"], qset, props, self.layout, "Object", allow_removing=False)
class BIM_PT_material_psets(Panel):
@@ -541,8 +559,6 @@ class BIM_PT_group_psets(Panel):
draw_psetqto_ui(context, pset["id"], pset, props, self.layout, "Group")
-
-
class BIM_PT_profile_psets(Panel):
bl_label = "Profile Property Sets"
bl_idname = "BIM_PT_profile_psets"
diff --git a/src/blenderbim/blenderbim/bim/module/pset_template/__init__.py b/src/blenderbim/blenderbim/bim/module/pset_template/__init__.py
index 766045f8af..e121c9a24f 100644
--- a/src/blenderbim/blenderbim/bim/module/pset_template/__init__.py
+++ b/src/blenderbim/blenderbim/bim/module/pset_template/__init__.py
@@ -34,6 +34,7 @@ classes = (
operator.RemovePropTemplate,
operator.RemovePsetTemplate,
operator.RemovePsetTemplateFile,
+ operator.SavePsetTemplateFile,
prop.PsetTemplate,
prop.EnumerationValues,
prop.PropTemplate,
diff --git a/src/blenderbim/blenderbim/bim/module/root/data.py b/src/blenderbim/blenderbim/bim/module/root/data.py
index 6b91e18ab8..18f7fbffcd 100644
--- a/src/blenderbim/blenderbim/bim/module/root/data.py
+++ b/src/blenderbim/blenderbim/bim/module/root/data.py
@@ -42,6 +42,7 @@ class IfcClassData:
cls.data["contexts"] = cls.contexts()
cls.data["has_entity"] = cls.has_entity()
cls.data["name"] = cls.name()
+ cls.data["has_inherited_predefined_type"] = cls.has_inherited_predefined_type()
cls.data["ifc_class"] = cls.ifc_class()
cls.data["ifc_predefined_types"] = cls.ifc_predefined_types()
cls.data["can_reassign_class"] = cls.can_reassign_class()
@@ -162,6 +163,16 @@ class IfcClassData:
name += f"[{predefined_type}]"
return name
+ @classmethod
+ def has_inherited_predefined_type(cls):
+ element = tool.Ifc.get_entity(bpy.context.view_layer.objects.active)
+ if not element:
+ return
+ if element_type := ifcopenshell.util.element.get_type(element):
+ # Allow for None due to https://github.com/buildingSMART/IFC4.3.x-development/issues/818
+ return ifcopenshell.util.element.get_predefined_type(element_type) not in ("NOTDEFINED", None)
+ return False
+
@classmethod
def ifc_class(cls):
element = tool.Ifc.get_entity(bpy.context.view_layer.objects.active)
diff --git a/src/blenderbim/blenderbim/bim/module/root/operator.py b/src/blenderbim/blenderbim/bim/module/root/operator.py
index fdeb0d9c34..ed1985c5bd 100644
--- a/src/blenderbim/blenderbim/bim/module/root/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/root/operator.py
@@ -215,7 +215,7 @@ class UnlinkObject(bpy.types.Operator):
bl_idname = "bim.unlink_object"
bl_label = "Unlink Object"
bl_options = {"REGISTER", "UNDO"}
- obj: bpy.props.StringProperty()
+ obj: bpy.props.StringProperty(name="Object Name")
should_delete: bpy.props.BoolProperty(name="Delete IFC Element", default=True)
def execute(self, context):
@@ -227,6 +227,7 @@ class UnlinkObject(bpy.types.Operator):
else:
objects = context.selected_objects
+ objects: list[bpy.types.Object]
for obj in objects:
was_active_object = obj == context.active_object
@@ -242,6 +243,27 @@ class UnlinkObject(bpy.types.Operator):
if obj.data:
obj_copy.data = obj.data.copy()
+ # prevent unlinking materials that might be used elsewhere
+ replacements: dict[bpy.types.Material, bpy.types.Material] = dict()
+ for material_slot in obj_copy.material_slots:
+ material = material_slot.material
+ if material is None:
+ continue
+
+ if material in replacements:
+ material_replacement = replacements[material]
+
+ # no need to copy non-ifc materials as unlinking won't do anything to them
+ elif tool.Ifc.get_entity(material) is None and tool.Style.get_style(material) is None:
+ replacements[material] = material
+ continue
+
+ else:
+ material_replacement = material.copy()
+ replacements[material] = material_replacement
+
+ material_slot.material = material_replacement
+
tool.Geometry.delete_ifc_object(obj)
obj = obj_copy
diff --git a/src/blenderbim/blenderbim/bim/module/root/ui.py b/src/blenderbim/blenderbim/bim/module/root/ui.py
index b80c733ee5..a57bec2e40 100644
--- a/src/blenderbim/blenderbim/bim/module/root/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/root/ui.py
@@ -61,7 +61,10 @@ class BIM_PT_class(Panel):
self.layout.prop(context.scene.BIMRootProperties, "relating_class_object", icon="COPYDOWN")
else:
row = self.layout.row(align=True)
- row.label(text=IfcClassData.data["name"])
+ row.label(
+ text=IfcClassData.data["name"],
+ icon="CON_CHILDOF" if IfcClassData.data["has_inherited_predefined_type"] else "NONE",
+ )
row.operator("bim.select_ifc_class", text="", icon="RESTRICT_SELECT_OFF")
row.operator("bim.unlink_object", icon="UNLINKED", text="")
if IfcClassData.data["can_reassign_class"]:
diff --git a/src/blenderbim/blenderbim/bim/module/search/__init__.py b/src/blenderbim/blenderbim/bim/module/search/__init__.py
index 606ed092c8..2abf6b132c 100644
--- a/src/blenderbim/blenderbim/bim/module/search/__init__.py
+++ b/src/blenderbim/blenderbim/bim/module/search/__init__.py
@@ -24,21 +24,15 @@ classes = (
operator.ActivateIfcClassFilter,
operator.AddFilter,
operator.AddFilterGroup,
- operator.AddToIfcGroup,
operator.ColourByProperty,
operator.EditFilterQuery,
- operator.FilterModelElements,
- operator.IfcSelector,
operator.LoadColourscheme,
- operator.LoadQuery,
operator.LoadSearch,
- operator.OpenQueryLibrary,
operator.RemoveFilter,
operator.RemoveFilterGroup,
operator.ResetObjectColours,
operator.SaveColourscheme,
operator.SaveSearch,
- operator.SaveSelectorQuery,
operator.Search,
operator.SelectByProperty,
operator.SelectFilterElements,
@@ -51,11 +45,6 @@ classes = (
prop.BIMFilterClasses,
prop.BIMFilterBuildingStoreys,
prop.BIMSearchProperties,
- prop.SearchCollection,
- prop.SearchQueryFilter,
- prop.SearchQuery,
- prop.SearchQueryGroup,
- prop.IfcSelectorProperties,
ui.BIM_PT_search,
ui.BIM_PT_filter,
ui.BIM_PT_colour_by_property,
@@ -63,15 +52,12 @@ classes = (
ui.BIM_UL_colourscheme,
ui.BIM_UL_ifc_class_filter,
ui.BIM_UL_ifc_building_storey_filter,
- ui.BIM_PT_IFCSelector,
)
def register():
bpy.types.Scene.BIMSearchProperties = bpy.props.PointerProperty(type=prop.BIMSearchProperties)
- bpy.types.Scene.IfcSelectorProperties = bpy.props.PointerProperty(type=prop.IfcSelectorProperties)
def unregister():
del bpy.types.Scene.BIMSearchProperties
- del bpy.types.Scene.IfcSelectorProperties
diff --git a/src/blenderbim/blenderbim/bim/module/search/operator.py b/src/blenderbim/blenderbim/bim/module/search/operator.py
index 2a3f710471..6ffe447470 100644
--- a/src/blenderbim/blenderbim/bim/module/search/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/search/operator.py
@@ -131,6 +131,7 @@ class SelectFilterElements(bpy.types.Operator):
text_data = bpy.data.texts.new(name)
text_data.from_string(",".join(global_ids))
filter_groups[self.group_index].filters[self.index].value = f"bpy.data.texts['{name}']"
+ self.report({"INFO"}, f'List of Global Ids was saved to the text file "{name}" in the current .blend file')
else:
filter_groups[self.group_index].filters[self.index].value = ",".join(global_ids)
return {"FINISHED"}
@@ -234,7 +235,7 @@ class LoadSearch(Operator, tool.Ifc.Operator):
def _execute(self, context):
filter_groups = tool.Search.get_filter_groups(self.module)
group = tool.Ifc.get().by_id(int(context.scene.BIMSearchProperties.saved_searches))
- query = tool.Search.import_filter_query(tool.Search.get_group_query(group), filter_groups)
+ tool.Search.import_filter_query(tool.Search.get_group_query(group), filter_groups)
def draw(self, context):
props = context.scene.BIMSearchProperties
@@ -270,25 +271,29 @@ class ColourByProperty(Operator):
colourscheme = {}
if len(props.colourscheme):
- colourscheme = {cs.name: cs.colour[0:3] for cs in props.colourscheme}
+ colourscheme = {cs.name: {"colour": cs.colour[0:3], "total": 0} for cs in props.colourscheme}
for obj in context.visible_objects:
element = tool.Ifc.get_entity(obj)
if not element:
continue
value = str(ifcopenshell.util.selector.get_element_value(element, query))
- if value not in colourscheme:
- colourscheme[value] = next(colours)[0:3]
- obj.color = (*colourscheme[value], 1)
+ if value in colourscheme:
+ colourscheme[value]["total"] += 1
+ else:
+ colourscheme[value] = {"colour": next(colours)[0:3], "total": 1}
+ obj.color = (*colourscheme[value]["colour"], 1)
areas = [a for a in context.screen.areas if a.type == "VIEW_3D"]
if areas:
areas[0].spaces[0].shading.color_type = "OBJECT"
props.colourscheme.clear()
- for value, colour in colourscheme.items():
+ for value in sorted(colourscheme.keys()):
+ data = colourscheme[value]
new = props.colourscheme.add()
new.name = str(value)
- new.colour = colour[0:3]
+ new.total = data["total"]
+ new.colour = data["colour"][0:3]
return {"FINISHED"}
def store_state(self, context):
@@ -350,16 +355,16 @@ class SaveColourscheme(Operator, tool.Ifc.Operator):
query = props.colourscheme_query
group = [g for g in tool.Ifc.get().by_type("IfcGroup") if g.Name == self.name]
- coulour_scheme = {cs.name: cs.colour[0:3] for cs in props.colourscheme}
+ colourscheme = {cs.name: {"colour": cs.colour[0:3], "total": cs.total} for cs in props.colourscheme}
if group:
group = group[0]
description = json.loads(group.Description)
- description["colourscheme"] = coulour_scheme
+ description["colourscheme"] = colourscheme
description["colourscheme_query"] = query
group.Description = json.dumps(description)
else:
description = json.dumps(
- {"type": "BBIM_Search", "colourscheme": coulour_scheme, "colourscheme_query": query}
+ {"type": "BBIM_Search", "colourscheme": colourscheme, "colourscheme_query": query}
)
group = ifcopenshell.api.run("group.add_group", tool.Ifc.get(), Name=self.name, Description=description)
@@ -379,10 +384,11 @@ class LoadColourscheme(Operator, tool.Ifc.Operator):
description = json.loads(group.Description)
props.colourscheme_query = description.get("colourscheme_query")
props.colourscheme.clear()
- for name, colour in description.get("colourscheme", {}).items():
+ for name, data in description.get("colourscheme", {}).items():
new = props.colourscheme.add()
new.name = name
- new.colour = colour
+ new.total = data["total"]
+ new.colour = data["colour"]
def draw(self, context):
props = context.scene.BIMSearchProperties
@@ -538,9 +544,11 @@ class ActivateIfcClassFilter(Operator):
"filter_classes",
context.scene.BIMSearchProperties,
"filter_classes_index",
- rows=20
- if len(bpy.context.scene.BIMSearchProperties.filter_classes) > 20
- else len(bpy.context.scene.BIMSearchProperties.filter_classes),
+ rows=(
+ 20
+ if len(bpy.context.scene.BIMSearchProperties.filter_classes) > 20
+ else len(bpy.context.scene.BIMSearchProperties.filter_classes)
+ ),
)
row = self.layout.row(align=True)
row.operator("bim.toggle_filter_selection", text="Select All").action = "SELECT"
@@ -595,9 +603,11 @@ class ActivateContainerFilter(Operator):
"filter_container",
context.scene.BIMSearchProperties,
"filter_container_index",
- rows=20
- if len(bpy.context.scene.BIMSearchProperties.filter_container) > 20
- else len(bpy.context.scene.BIMSearchProperties.filter_container),
+ rows=(
+ 20
+ if len(bpy.context.scene.BIMSearchProperties.filter_container) > 20
+ else len(bpy.context.scene.BIMSearchProperties.filter_container)
+ ),
)
row = self.layout.row(align=True)
row.operator("bim.toggle_filter_selection", text="Select All").action = "SELECT"
@@ -618,223 +628,6 @@ class ShowAllElements(Operator):
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):
- selection_props = context.scene.IfcSelectorProperties
- selection = (
- selection_props.selector_query_syntax
- if selection_props.manual_override
- else self.add_groups(selection_props)
- )
- selection_props.selector_query_syntax = selection
- r = core.search(tool.Search, tool.Spatial, query=selection, action=self.option)
- if isinstance(r, str):
- self.report({"WARNING"}, r)
- return {"FINISHED"}
-
- def add_groups(self, selector: str) -> str:
- 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: str, group: str) -> str:
- 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: str, query: str) -> str:
- for f_index, f in enumerate(query.filters):
- if f_index != 0:
- selection += " & " if f.and_or == "and" else " | "
- selection += f".{query.active_option}"
-
- if f.value in ("True", "False"):
- value = f.value
- elif f.value.isnumeric() and str(float(f.value))[0] == f.value[0]:
- value = f.value
- else:
- value = f'"{f.value}"'
-
- selection += "["
-
- if f.selector == "IfcPropertySet":
- selection += f'{f.active_option.split(": ")[1]}.{f.active_sub_option.split(": ")[1]} {"!" if f.negation else ""}{f.comparison} {value}'
- elif f.selector == "Attribute":
- # we're using the prop_search functionality in blender which returns the index of the option. Sometimes the user can override this and enter a value that doesn't exist in the list. In this case there is no index and we need to handle it. @vulevukusej
- pattern = re.compile(r"^[0-9]+:")
- match = pattern.search(f.active_option)
- selection += f'{f.active_option.split(": ")[1] if match else f.active_option} {"!" if f.negation else ""}{f.comparison} {value}'
- selection += "]"
- return selection
-
-
-# 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"}
-
-
class SelectSimilar(Operator, tool.Ifc.Operator):
bl_idname = "bim.select_similar"
bl_label = "Select Similar"
diff --git a/src/blenderbim/blenderbim/bim/module/search/prop.py b/src/blenderbim/blenderbim/bim/module/search/prop.py
index 22d0e80173..c8c5580e6e 100644
--- a/src/blenderbim/blenderbim/bim/module/search/prop.py
+++ b/src/blenderbim/blenderbim/bim/module/search/prop.py
@@ -78,7 +78,7 @@ def update_is_container_selected(self, context):
else:
for obj in context.selected_objects:
container = tool.Spatial.get_container(tool.Ifc.get_entity(obj))
- if (container and container.Name == self.name) or (not container and self.name== "None"):
+ if (container and container.Name == self.name) or (not container and self.name == "None"):
obj.select_set(False)
new = self.unselected_objects.add()
new.obj = obj
@@ -100,6 +100,7 @@ class BIMFilterBuildingStoreys(PropertyGroup):
class BIMColour(PropertyGroup):
name: StringProperty(name="Name")
+ total: IntProperty(name="Total")
colour: FloatVectorProperty(name="Colour", subtype="COLOR", default=(1, 0, 0), min=0.0, max=1.0)
@@ -138,139 +139,3 @@ def get_classes(self, 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 self.selector == "Attribute":
- attributes = ["GlobalId", "Name", "Description", "ObjectType", "Tag", "PredefinedType"]
- for a in attributes:
- options.append((a, a, ""))
-
- 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="-",
- )
-
-
-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")
diff --git a/src/blenderbim/blenderbim/bim/module/search/ui.py b/src/blenderbim/blenderbim/bim/module/search/ui.py
index b8c733c848..8117bc9f52 100644
--- a/src/blenderbim/blenderbim/bim/module/search/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/search/ui.py
@@ -125,7 +125,7 @@ class BIM_UL_colourscheme(bpy.types.UIList):
if not item:
return
row = layout.row(align=True)
- row.label(text=item.name)
+ row.label(text=f"{item.name} ({item.total})")
row.prop(item, "colour", text="")
@@ -159,153 +159,3 @@ class BIM_UL_ifc_building_storey_filter(bpy.types.UIList):
split = split.column()
split.scale_x = 0.5
split.label(text=str(item.total))
-
-
-class BIM_PT_IFCSelector(Panel):
- bl_label = "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_tab_sandbox"
-
- 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"
- unhide = row.operator("bim.filter_model_elements", text="", icon="HIDE_OFF")
- unhide.option = "unhide"
-
- 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")
-
- row = layout.row(align=True)
- row.operator("bim.show_scene_elements", text="Show All Elements", icon="HIDE_OFF")
-
- 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_search(f, "active_option", f, "options", text="", results_are_suggestions=True)
- 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
diff --git a/src/blenderbim/blenderbim/bim/module/spatial/workspace.py b/src/blenderbim/blenderbim/bim/module/spatial/workspace.py
index 8e91081ffb..c2e08bd1d0 100644
--- a/src/blenderbim/blenderbim/bim/module/spatial/workspace.py
+++ b/src/blenderbim/blenderbim/bim/module/spatial/workspace.py
@@ -48,6 +48,7 @@ class SpatialTool(WorkSpaceTool):
("bim.spatial_hotkey", {"type": "B", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_B")]}),
("bim.spatial_hotkey", {"type": "A", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_A")]}),
("bim.spatial_hotkey", {"type": "B", "value": "PRESS", "alt": True}, {"properties": [("hotkey", "A_B")]}),
+ ("bim.spatial_hotkey", {"type": "V", "value": "PRESS", "alt": True}, {"properties": [("hotkey", "A_V")]}),
("bim.spatial_hotkey", {"type": "T", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_T")]}),
("bim.spatial_hotkey", {"type": "G", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_G")]}),
("bim.spatial_hotkey", {"type": "H", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_H")]}),
@@ -92,36 +93,34 @@ class SpatialToolUI:
if context.active_object and context.selected_objects:
cls.draw_selected_object_interface(context)
+ add_layout_hotkey(cls.layout, "Decorate Boundaries", "A_V", bpy.ops.bim.decorate_boundaries.__doc__)
+
@classmethod
def draw_default_interface(cls, context):
row = cls.layout.row(align=True)
row.prop(data=cls.model_props, property="rl3", text="RL")
row = cls.layout.row(align=True)
- row.label(text="", icon="EVENT_SHIFT")
- row.label(text="", icon="EVENT_A")
- row.operator("bim.generate_spaces_from_walls")
- row = cls.layout.row(align=True)
- row.label(text="", icon="EVENT_SHIFT")
- row.label(text="", icon="EVENT_A")
- row.operator("bim.generate_space")
- row = cls.layout.row(align=True)
- row.label(text="", icon="EVENT_SHIFT")
- row.label(text="", icon="EVENT_T")
- row.operator("bim.toggle_space_visibility")
- row = cls.layout.row(align=True)
- row.label(text="", icon="EVENT_SHIFT")
- row.label(text="", icon="EVENT_H")
- row.operator("bim.toggle_hide_spaces")
+ op_name = lambda op: op.get_rna_type().name
+ add_layout_hotkey(
+ cls.layout,
+ op_name(bpy.ops.bim.generate_spaces_from_walls),
+ "S_A",
+ bpy.ops.bim.generate_spaces_from_walls.__doc__,
+ )
+ add_layout_hotkey(cls.layout, op_name(bpy.ops.bim.generate_space), "S_A", bpy.ops.bim.generate_space.__doc__)
+ add_layout_hotkey(
+ cls.layout, op_name(bpy.ops.bim.toggle_space_visibility), "S_T", bpy.ops.bim.toggle_space_visibility.__doc__
+ )
+ add_layout_hotkey(
+ cls.layout, op_name(bpy.ops.bim.toggle_hide_spaces), "S_H", bpy.ops.bim.toggle_hide_spaces.__doc__
+ )
@classmethod
def draw_selected_object_interface(cls, context):
active_obj = bpy.context.active_object
element = tool.Ifc.get_entity(active_obj)
if element and bpy.context.selected_objects and element.is_a("IfcSpace"):
- row = cls.layout.row(align=True)
- row.label(text="", icon="EVENT_SHIFT")
- row.label(text="", icon="EVENT_G")
- row.operator("bim.generate_space", text="Regen")
+ add_layout_hotkey(cls.layout, "Regen", "S_G", bpy.ops.bim.generate_space.__doc__)
row = cls.layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
@@ -129,7 +128,7 @@ class SpatialToolUI:
row.prop(cls.model_props, "boundary_class", text="")
row.operator("bim.add_boundary", text="Add Boundary")
- add_layout_hotkey(cls.layout, "Boundaries", "A_B", "Toggle boundaries")
+ add_layout_hotkey(cls.layout, "Boundaries", "A_B", "Load/unload boundaries on selected objects")
@classmethod
def draw_type_selection_interface(cls, context):
@@ -175,6 +174,8 @@ class Hotkey(bpy.types.Operator, Operator):
bpy.ops.bim.add_boundary()
def hotkey_A_B(self):
+ if not AuthoringData.is_loaded:
+ AuthoringData.load()
if not bpy.context.selected_objects:
return
if AuthoringData.data["has_visible_boundaries"]:
@@ -182,6 +183,9 @@ class Hotkey(bpy.types.Operator, Operator):
else:
bpy.ops.bim.show_boundaries()
+ def hotkey_A_V(self):
+ bpy.ops.bim.decorate_boundaries()
+
def hotkey_S_T(self):
bpy.ops.bim.toggle_space_visibility()
diff --git a/src/blenderbim/blenderbim/bim/module/structural/operator.py b/src/blenderbim/blenderbim/bim/module/structural/operator.py
index 3662397056..8415771d2b 100644
--- a/src/blenderbim/blenderbim/bim/module/structural/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/structural/operator.py
@@ -473,7 +473,7 @@ class AssignStructuralLoadCase(bpy.types.Operator, tool.Ifc.Operator):
self.file,
**{
"relating_object": self.file.by_id(self.work_plan),
- "product": self.file.by_id(self.load_case),
+ "products": [self.file.by_id(self.load_case)],
},
)
return {"FINISHED"}
@@ -492,7 +492,7 @@ class UnassignStructuralLoadCase(bpy.types.Operator, tool.Ifc.Operator):
self.file,
**{
"relating_object": self.file.by_id(self.work_plan),
- "product": self.file.by_id(self.load_case),
+ "products": [self.file.by_id(self.load_case)],
},
)
return {"FINISHED"}
diff --git a/src/blenderbim/blenderbim/bim/module/style/operator.py b/src/blenderbim/blenderbim/bim/module/style/operator.py
index 7dc2b64d58..db679a6cf8 100644
--- a/src/blenderbim/blenderbim/bim/module/style/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/style/operator.py
@@ -330,16 +330,21 @@ class ActivateExternalStyle(bpy.types.Operator, tool.Ifc.Operator):
style_path = Path(tool.Ifc.resolve_uri(external_style.Location))
if style_path.suffix != ".blend":
- self.report({"ERROR"}, f"Error loading external style - only Blender external styles are supported")
+ self.report(
+ {"ERROR"},
+ f"Error loading external style for \"{material.name}\" - only Blender external styles are supported",
+ )
return {"CANCELLED"}
if not style_path.exists():
- self.report({"ERROR"}, f"Error loading external style - cannot find file: '{style_path}'")
+ self.report(
+ {"ERROR"}, f"Error loading external style for \"{material.name}\" - cannot find file: '{style_path}'"
+ )
return {"CANCELLED"}
db = tool.Blender.append_data_block(str(style_path), data_block_type, data_block)
if not db["data_block"]:
- self.report({"ERROR"}, f"Error loading external style - {db['msg']}")
+ self.report({"ERROR"}, f"Error loading external style for \"{material.name}\" - {db['msg']}")
return {"CANCELLED"}
self.copy_material_attributes(db["data_block"], material)
diff --git a/src/blenderbim/blenderbim/bim/module/style/prop.py b/src/blenderbim/blenderbim/bim/module/style/prop.py
index cf17b2ace2..e195c1472a 100644
--- a/src/blenderbim/blenderbim/bim/module/style/prop.py
+++ b/src/blenderbim/blenderbim/bim/module/style/prop.py
@@ -259,7 +259,7 @@ def update_shading_style(self, context):
try:
bpy.ops.bim.activate_external_style(material_name=blender_material.name)
except RuntimeError as error:
- if str(error).startswith("Error: Error loading external style - "):
+ if str(error).startswith("Error: Error loading external style for "):
return
raise error
diff --git a/src/blenderbim/blenderbim/bim/module/tester/operator.py b/src/blenderbim/blenderbim/bim/module/tester/operator.py
index 4819c05d50..8560462eb2 100644
--- a/src/blenderbim/blenderbim/bim/module/tester/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/tester/operator.py
@@ -27,7 +27,7 @@ import ifctester.reporter
import ifcopenshell
import blenderbim.tool as tool
import blenderbim.bim.handler
-from blenderbim.bim.module.tester.data import TesterData
+from pathlib import Path
class ExecuteIfcTester(bpy.types.Operator, tool.Ifc.Operator):
@@ -44,7 +44,7 @@ class ExecuteIfcTester(bpy.types.Operator, tool.Ifc.Operator):
with tempfile.TemporaryDirectory() as dirpath:
start = time.time()
- output = os.path.join(dirpath, "{}.html".format(props.specs))
+ output = Path(os.path.join(dirpath, "{}.html".format(props.specs)))
filepath = None
if props.should_load_from_memory and tool.Ifc.get():
@@ -65,8 +65,15 @@ class ExecuteIfcTester(bpy.types.Operator, tool.Ifc.Operator):
if props.generate_html_report:
engine = ifctester.reporter.Html(specs)
engine.report()
- engine.to_file(output)
- webbrowser.open("file://" + output)
+ output_path = output.as_posix()
+ engine.to_file(output_path)
+ webbrowser.open(f"file://{output_path}")
+
+ if props.generate_ods_report:
+ engine = ifctester.reporter.Ods(specs)
+ engine.report()
+ output_path = output.with_suffix(".ods").as_posix()
+ engine.to_file(output_path)
report = None
report = ifctester.reporter.Json(specs).report()["specifications"]
diff --git a/src/blenderbim/blenderbim/bim/module/tester/prop.py b/src/blenderbim/blenderbim/bim/module/tester/prop.py
index 05bb2054ad..a82fb233ce 100644
--- a/src/blenderbim/blenderbim/bim/module/tester/prop.py
+++ b/src/blenderbim/blenderbim/bim/module/tester/prop.py
@@ -52,6 +52,7 @@ class IfcTesterProperties(PropertyGroup):
ifc_file: StringProperty(default="", name="IFC File")
should_load_from_memory: BoolProperty(default=False, name="Load from Memory")
generate_html_report: BoolProperty(default=False, name="Generate HTML report")
+ generate_ods_report: BoolProperty(default=False, name="Generate ODS report")
flag: BoolProperty(default=False, name="Flag Failed Entities")
active_specification_index: IntProperty(name="Active Specification Index", update=update_active_specification_index)
old_index: IntProperty(name="", default=0)
diff --git a/src/blenderbim/blenderbim/bim/module/tester/ui.py b/src/blenderbim/blenderbim/bim/module/tester/ui.py
index 899d52415d..b4ae0bcdad 100644
--- a/src/blenderbim/blenderbim/bim/module/tester/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/tester/ui.py
@@ -44,6 +44,8 @@ class BIM_PT_tester(Panel):
row = self.layout.row()
row.prop(props, "generate_html_report")
row = self.layout.row()
+ row.prop(props, "generate_ods_report")
+ row = self.layout.row()
row.prop(props, "flag")
if not tool.Ifc.get() or not props.should_load_from_memory:
diff --git a/src/blenderbim/blenderbim/bim/module/type/operator.py b/src/blenderbim/blenderbim/bim/module/type/operator.py
index 43abc6e47b..899dfe4ec1 100644
--- a/src/blenderbim/blenderbim/bim/module/type/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/type/operator.py
@@ -18,7 +18,9 @@
import bpy
import bmesh
+import ifcopenshell.util.element
import ifcopenshell.util.schema
+import ifcopenshell.util.representation
import ifcopenshell.util.type
import ifcopenshell.api
import blenderbim.tool as tool
@@ -76,7 +78,7 @@ class UnassignType(bpy.types.Operator):
element = tool.Ifc.get_entity(obj)
if not element or element.is_a("IfcElementType"):
continue
- ifcopenshell.api.run("type.unassign_type", self.file, related_object=element)
+ ifcopenshell.api.run("type.unassign_type", self.file, related_objects=[element])
active_representation = tool.Geometry.get_active_representation(obj)
active_context = active_representation.ContextOfItems
@@ -171,15 +173,22 @@ class SelectSimilarType(bpy.types.Operator):
def execute(self, context):
self.file = IfcStore.get_file()
objects = bpy.context.selected_objects
+
+ # store relating types to avoid selecting same elements multiple times
+ relating_types = set()
+
for related_object in objects:
relating_type = ifcopenshell.util.element.get_type(tool.Ifc.get_entity(related_object))
if not relating_type:
related_object.select_set(False)
continue
+ relating_types.add(relating_type)
+
+ for relating_type in relating_types:
related_objects = ifcopenshell.util.element.get_types(relating_type)
- for obj in context.visible_objects:
- element = tool.Ifc.get_entity(obj)
- if element and element in related_objects:
+ for element in related_objects:
+ obj = tool.Ifc.get_object(element)
+ if obj and obj in context.visible_objects:
obj.select_set(True)
return {"FINISHED"}
@@ -274,7 +283,7 @@ class AddType(bpy.types.Operator, tool.Ifc.Operator):
else:
material = self.add_default_material()
rel = ifcopenshell.api.run(
- "material.assign_material", ifc_file, product=element, type="IfcMaterialLayerSet"
+ "material.assign_material", ifc_file, products=[element], type="IfcMaterialLayerSet"
)
layer_set = rel.RelatingMaterial
layer = ifcopenshell.api.run("material.add_layer", ifc_file, layer_set=layer_set, material=material)
@@ -352,7 +361,7 @@ class AddType(bpy.types.Operator, tool.Ifc.Operator):
)
rel = ifcopenshell.api.run(
- "material.assign_material", ifc_file, product=element, type="IfcMaterialProfileSet"
+ "material.assign_material", ifc_file, products=[element], type="IfcMaterialProfileSet"
)
profile_set = rel.RelatingMaterial
material_profile = ifcopenshell.api.run(
diff --git a/src/blenderbim/blenderbim/bim/operator.py b/src/blenderbim/blenderbim/bim/operator.py
index ca3b1d4aa3..55032392f7 100644
--- a/src/blenderbim/blenderbim/bim/operator.py
+++ b/src/blenderbim/blenderbim/bim/operator.py
@@ -357,7 +357,7 @@ class OpenUpstream(bpy.types.Operator):
if self.page == "home":
webbrowser.open("https://blenderbim.org/")
elif self.page == "docs":
- webbrowser.open("https://blenderbim.org/docs/")
+ webbrowser.open("https://docs.blenderbim.org/")
elif self.page == "wiki":
webbrowser.open("https://wiki.osarch.org/index.php?title=Category:BlenderBIM_Add-on")
elif self.page == "community":
@@ -936,6 +936,31 @@ class BIM_OT_enum_property_search(bpy.types.Operator):
)
+class BIM_OT_select_object(bpy.types.Operator):
+ bl_idname = "bim.select_object"
+ bl_label = "Select Object"
+ bl_options = {"REGISTER", "UNDO"}
+ obj_name: bpy.props.StringProperty(description="Object Name To Select")
+
+ def execute(self, context):
+ obj = bpy.data.objects[self.obj_name]
+ tool.Blender.set_objects_selection(context, obj, [obj], clear_previous_selection=True)
+ return {"FINISHED"}
+
+
+class BIM_OT_delete_object(bpy.types.Operator):
+ bl_idname = "bim.delete_object"
+ bl_label = "Delete Object"
+ bl_options = {"REGISTER", "UNDO"}
+ obj_name: bpy.props.StringProperty(description="Object Name To Delete")
+
+ def execute(self, context):
+ obj = bpy.data.objects[self.obj_name]
+ with context.temp_override(selected_objects=[obj], active_object=obj):
+ bpy.ops.bim.override_object_delete(is_batch=False)
+ return {"FINISHED"}
+
+
class EditBlenderCollection(bpy.types.Operator):
bl_idname = "bim.edit_blender_collection"
bl_label = "Add or Remove Blender Collection Item"
diff --git a/src/blenderbim/blenderbim/bim/prop.py b/src/blenderbim/blenderbim/bim/prop.py
index d2309ac97b..5bb6591ff1 100644
--- a/src/blenderbim/blenderbim/bim/prop.py
+++ b/src/blenderbim/blenderbim/bim/prop.py
@@ -273,6 +273,8 @@ class Attribute(PropertyGroup):
def get_value(self):
if self.is_optional and self.is_null:
return None
+ if self.data_type == "string":
+ return self.string_value.replace("\\n", "\n")
return getattr(self, str(self.get_value_name()), None)
def get_value_default(self):
diff --git a/src/blenderbim/blenderbim/bim/schema/classifications/CCI(EN).ifc b/src/blenderbim/blenderbim/bim/schema/classifications/CCI(EN).ifc
new file mode 100644
index 0000000000..5049175e34
--- /dev/null
+++ b/src/blenderbim/blenderbim/bim/schema/classifications/CCI(EN).ifc
@@ -0,0 +1,1288 @@
+ISO-10303-21;
+HEADER;
+FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1');
+FILE_NAME('','2024-04-02T18:00:00',(),(),'IfcOpenShell v0.7.0-f7c03db75','IfcOpenShell v0.7.0-f7c03db75','');
+FILE_SCHEMA(('IFC4'));
+ENDSEC;
+DATA;
+#1=IFCPROJECTLIBRARY($,$,'CCI',$,$,$,$,$,$);
+#2=IFCCLASSIFICATION('CCIC','2022-11-09','2022-11-09','CCI','The CCI classification tables','https://cci-collaboration.org/the-standard',$);
+#3=IFCRELASSOCIATESCLASSIFICATION('30b4e$ts223eGUqY6ZfKw5',$,$,$,(#1),#2);
+#100=IFCCLASSIFICATIONREFERENCE($,'CC - Construction complex',' ',#2,' ',$);
+#101=IFCCLASSIFICATIONREFERENCE($,'[CC]A','Residential complex',#100,'construction complex for dwelling',$);
+#102=IFCCLASSIFICATIONREFERENCE($,'[CC]B','Health and care complex',#100,'construction complex for promotion of wellness and/or treatment of illness',$);
+#103=IFCCLASSIFICATIONREFERENCE($,'[CC]C','Administration, trading, or service complex',#100,'construction complex for immaterial production',$);
+#104=IFCCLASSIFICATIONREFERENCE($,'[CC]D','Education or science complex',#100,'construction complex for knowledge production and/or transfer',$);
+#105=IFCCLASSIFICATIONREFERENCE($,'[CC]E','Protection or defence complex',#100,'construction complex for protection and defence',$);
+#106=IFCCLASSIFICATIONREFERENCE($,'[CC]F','Culture complex',#100,'construction complex for cultural activities',$);
+#107=IFCCLASSIFICATIONREFERENCE($,'[CC]G','Recreational and sports complex',#100,'construction complex for social and physical activities and/or mental stimulation',$);
+#108=IFCCLASSIFICATIONREFERENCE($,'[CC]L','Energy supply complex',#100,'construction complex for conversion of energy',$);
+#109=IFCCLASSIFICATIONREFERENCE($,'[CC]M','Distribution complex',#100,'construction complex for continuous transport of matter, electricity or signals',$);
+#110=IFCCLASSIFICATIONREFERENCE($,'[CC]N','Production complex',#100,'construction complex for raw materials extraction or manufacturing',$);
+#111=IFCCLASSIFICATIONREFERENCE($,'[CC]P','Processing complex',#100,'construction complex for treating matter or electricity',$);
+#112=IFCCLASSIFICATIONREFERENCE($,'[CC]Q','Storage complex',#100,'construction complex for accumulations of matter or electricity',$);
+#113=IFCCLASSIFICATIONREFERENCE($,'[CC]R','Traffic complex',#100,'construction complex for discrete transport of people and/or matter',$);
+#114=IFCCLASSIFICATIONREFERENCE($,'[CC]T','Land use complex',#100,'construction complex for land-based production or management',$);
+#115=IFCCLASSIFICATIONREFERENCE($,'[CC]U','Water use complex',#100,'construction complex for water-based production or management',$);
+#116=IFCCLASSIFICATIONREFERENCE($,'[CC]X','Ruins complex',#100,'construction complex being remains',$);
+#200=IFCCLASSIFICATIONREFERENCE($,'CE - Construction entity',' ',#2,' ',$);
+#201=IFCCLASSIFICATIONREFERENCE($,'[CE]A?','Residential building',#200,'construction entity for human living',$);
+#202=IFCCLASSIFICATIONREFERENCE($,'[CE]AA','Single-dwelling house',#201,'residential building for one household',$);
+#203=IFCCLASSIFICATIONREFERENCE($,'[CE]AB','Dual-dwelling building',#201,'residential building for two households',$);
+#204=IFCCLASSIFICATIONREFERENCE($,'[CE]AC','Multi-dwelling building',#201,'residential building for three or more households',$);
+#205=IFCCLASSIFICATIONREFERENCE($,'[CE]AD','Lodging building',#201,'residential building for temporary living',$);
+#206=IFCCLASSIFICATIONREFERENCE($,'[CE]B?','Physical needs building',#200,'construction entity for human or animal wellbeing',$);
+#207=IFCCLASSIFICATIONREFERENCE($,'[CE]BA','Care building',#206,'physical needs building for treatment of persons',$);
+#208=IFCCLASSIFICATIONREFERENCE($,'[CE]BB','Care home building',#206,'physical needs building for long-term care',$);
+#209=IFCCLASSIFICATIONREFERENCE($,'[CE]BD','Animal hospital',#206,'physical needs building for treatment of animals',$);
+#210=IFCCLASSIFICATIONREFERENCE($,'[CE]BE','Toilet building',#206,'physical needs building for persons to relieve themselves',$);
+#211=IFCCLASSIFICATIONREFERENCE($,'[CE]BF','Baths building',#206,'physical needs building for persons to wash themselves',$);
+#212=IFCCLASSIFICATIONREFERENCE($,'[CE]C?','Administration, trading, or service building',#200,'construction entity for immaterial production',$);
+#213=IFCCLASSIFICATIONREFERENCE($,'[CE]CA','Office building',#212,'administration building for production or interpretation of information',$);
+#214=IFCCLASSIFICATIONREFERENCE($,'[CE]CB','Service building',#212,'service building for providing non-acute assistance or advice',$);
+#215=IFCCLASSIFICATIONREFERENCE($,'[CE]CC','Emergency service building',#212,'service building for providing acute assistance or advice',$);
+#216=IFCCLASSIFICATIONREFERENCE($,'[CE]CD','Shop building',#212,'service building for sale of goods',$);
+#217=IFCCLASSIFICATIONREFERENCE($,'[CE]CE','Exhibition hall',#212,'trading building for display of goods and for assemblies',$);
+#218=IFCCLASSIFICATIONREFERENCE($,'[CE]CF','Restaurant building',#212,'service building for serving food and/or beverages',$);
+#219=IFCCLASSIFICATIONREFERENCE($,'[CE]CG','Operations building',#212,'service building for monitoring and controlling of processes',$);
+#220=IFCCLASSIFICATIONREFERENCE($,'[CE]D?','Education or research building',#200,'construction entity for knowledge production and transfer',$);
+#221=IFCCLASSIFICATIONREFERENCE($,'[CE]DA','Preschool building',#220,'education building for small children',$);
+#222=IFCCLASSIFICATIONREFERENCE($,'[CE]DB','School building',#220,'education building for basic education',$);
+#223=IFCCLASSIFICATIONREFERENCE($,'[CE]DC','Course centre building',#220,'education building for series of lectures or lessons in a particular subject',$);
+#224=IFCCLASSIFICATIONREFERENCE($,'[CE]DD','University building',#220,'education building for further education',$);
+#225=IFCCLASSIFICATIONREFERENCE($,'[CE]DE','Laboratory building',#220,'research building for experiments',$);
+#226=IFCCLASSIFICATIONREFERENCE($,'[CE]F?','Cultural building',#200,'construction entity for artistic activities',$);
+#227=IFCCLASSIFICATIONREFERENCE($,'[CE]FA','Theatre building',#226,'cultural building for oral performances',$);
+#228=IFCCLASSIFICATIONREFERENCE($,'[CE]FB','Concert building',#226,'cultural building for musical performances',$);
+#229=IFCCLASSIFICATIONREFERENCE($,'[CE]FC','Exhibition building',#226,'cultural building for display of art',$);
+#230=IFCCLASSIFICATIONREFERENCE($,'[CE]FD','Planetarium',#226,'cultural building for displaying the night sky',$);
+#231=IFCCLASSIFICATIONREFERENCE($,'[CE]FE','Cinema building',#226,'cultural building for displaying films',$);
+#232=IFCCLASSIFICATIONREFERENCE($,'[CE]FF','Library building',#226,'cultural building providing books and other media',$);
+#233=IFCCLASSIFICATIONREFERENCE($,'[CE]FG','Cultural center',#226,'cultural building for multiple use',$);
+#234=IFCCLASSIFICATIONREFERENCE($,'[CE]FH','Casino',#226,'cultural building for gambling',$);
+#235=IFCCLASSIFICATIONREFERENCE($,'[CE]FJ','Studio',#226,'cultural building for production of art and entertainment',$);
+#236=IFCCLASSIFICATIONREFERENCE($,'[CE]G?','Recreational facility',#200,'construction entity for social activities and/or mental stimulation',$);
+#237=IFCCLASSIFICATIONREFERENCE($,'[CE]GA','Allotment building',#236,'recreational facility for gardening tools',$);
+#238=IFCCLASSIFICATIONREFERENCE($,'[CE]GB','Gazebo',#236,'recreational facility for garden retreat',$);
+#239=IFCCLASSIFICATIONREFERENCE($,'[CE]GC','Cantilever roof',#236,'recreational facility for protection from sun and precipitation',$);
+#240=IFCCLASSIFICATIONREFERENCE($,'[CE]GD','Playhouse',#236,'recreational facility for child play',$);
+#241=IFCCLASSIFICATIONREFERENCE($,'[CE]GE','Wind shelter',#236,'recreational facility for protection from wind',$);
+#242=IFCCLASSIFICATIONREFERENCE($,'[CE]GF','Observation tower',#236,'recreational facility for raised viewing position',$);
+#243=IFCCLASSIFICATIONREFERENCE($,'[CE]GG','Hunting tower',#236,'recreational facility for raised shooting position',$);
+#244=IFCCLASSIFICATIONREFERENCE($,'[CE]GH','Amusement facility',#236,'recreational facility for physical exaltation',$);
+#245=IFCCLASSIFICATIONREFERENCE($,'[CE]GJ','Garden',#236,'recreational facility for private outdoor activities',$);
+#246=IFCCLASSIFICATIONREFERENCE($,'[CE]GK','Park',#236,'recreational facility for public outdoor activities',$);
+#247=IFCCLASSIFICATIONREFERENCE($,'[CE]GL','Playground',#236,'recreational facility for children''s activities for enjoyment and recreation',$);
+#248=IFCCLASSIFICATIONREFERENCE($,'[CE]GM','Campsite',#236,'recreational facility for tents and recreational vehicles',$);
+#249=IFCCLASSIFICATIONREFERENCE($,'[CE]GN','Bathing site',#236,'recreational facility for outdoor water activities',$);
+#250=IFCCLASSIFICATIONREFERENCE($,'[CE]GP','Changing building',#236,'recreational facility for persons to change clothing',$);
+#251=IFCCLASSIFICATIONREFERENCE($,'[CE]H?','Sports facility',#200,'construction entity for physical activities',$);
+#252=IFCCLASSIFICATIONREFERENCE($,'[CE]HA','Multi-purpose sports building',#251,'sports facility for multiple activities',$);
+#253=IFCCLASSIFICATIONREFERENCE($,'[CE]HB','Swimming hall',#251,'sports facility for indoor water activities',$);
+#254=IFCCLASSIFICATIONREFERENCE($,'[CE]HC','Riding school',#251,'sports facility for equestrian activities',$);
+#255=IFCCLASSIFICATIONREFERENCE($,'[CE]HD','Diving tower',#251,'sports facility for diving',$);
+#256=IFCCLASSIFICATIONREFERENCE($,'[CE]HE','Bowling hall',#251,'sports facility for bowling',$);
+#257=IFCCLASSIFICATIONREFERENCE($,'[CE]HF','Skating rink',#251,'sports facility for ice skating',$);
+#258=IFCCLASSIFICATIONREFERENCE($,'[CE]HG','Ball sport building',#251,'sports facility for boll sports',$);
+#259=IFCCLASSIFICATIONREFERENCE($,'[CE]HH','Stadium',#251,'sports facility with spectator facilities',$);
+#260=IFCCLASSIFICATIONREFERENCE($,'[CE]HJ','Playing field',#251,'sports facility for ball games',$);
+#261=IFCCLASSIFICATIONREFERENCE($,'[CE]HK','Racetrack',#251,'sports facility for racing competitions',$);
+#262=IFCCLASSIFICATIONREFERENCE($,'[CE]HL','Track and field area',#251,'sports facility for track and field',$);
+#263=IFCCLASSIFICATIONREFERENCE($,'[CE]HM','Activity park',#251,'sports facility for multiple or specific outdoor activity(ies)',$);
+#264=IFCCLASSIFICATIONREFERENCE($,'[CE]HN','Downhill skiing slope',#251,'sports facility for downhill skiing',$);
+#265=IFCCLASSIFICATIONREFERENCE($,'[CE]HP','Exercise track',#251,'sports facility for running',$);
+#266=IFCCLASSIFICATIONREFERENCE($,'[CE]HQ','Foot path',#251,'sports facility for walking',$);
+#267=IFCCLASSIFICATIONREFERENCE($,'[CE]HR','Golf course',#251,'sports facility for golf',$);
+#268=IFCCLASSIFICATIONREFERENCE($,'[CE]J?','Assembly facility',#200,'construction entity for groups of people',$);
+#269=IFCCLASSIFICATIONREFERENCE($,'[CE]JA','Activity centre',#268,'assembly facility for common activities',$);
+#270=IFCCLASSIFICATIONREFERENCE($,'[CE]JB','Community hall',#268,'assembly facility for an association',$);
+#271=IFCCLASSIFICATIONREFERENCE($,'[CE]JC','Ceremonial building',#268,'assembly facility for a religious community',$);
+#272=IFCCLASSIFICATIONREFERENCE($,'[CE]JD','Stand',#268,'assembly facility for an audience',$);
+#273=IFCCLASSIFICATIONREFERENCE($,'[CE]JE','Personnel building',#268,'assembly facility for staff',$);
+#274=IFCCLASSIFICATIONREFERENCE($,'[CE]JF','Square',#268,'assembly facility for outdoors urban activities',$);
+#275=IFCCLASSIFICATIONREFERENCE($,'[CE]JG','Training site',#268,'assembly facility for practicing competencies',$);
+#276=IFCCLASSIFICATIONREFERENCE($,'[CE]JH','School yard',#268,'assembly facility for school persons',$);
+#277=IFCCLASSIFICATIONREFERENCE($,'[CE]K?','Access or barrier facility',#200,'construction entity for allowing or preventing movement',$);
+#278=IFCCLASSIFICATIONREFERENCE($,'[CE]KA','Passageway',#277,'access facility for horizontal movement between two or more buildings',$);
+#279=IFCCLASSIFICATIONREFERENCE($,'[CE]KB','Staircase tower',#277,'access facility for non-automated vertical movement between two or more storeys',$);
+#280=IFCCLASSIFICATIONREFERENCE($,'[CE]KC','Lift tower',#277,'access facility for automated vertical movement between two or more levels',$);
+#281=IFCCLASSIFICATIONREFERENCE($,'[CE]KD','Gatehouse',#277,'access facility to an open area',$);
+#282=IFCCLASSIFICATIONREFERENCE($,'[CE]KE','Tollgate',#277,'access facility to a road',$);
+#283=IFCCLASSIFICATIONREFERENCE($,'[CE]KF','Barrier',#277,'barrier facility',$);
+#284=IFCCLASSIFICATIONREFERENCE($,'[CE]L?','Energy supply facility',#200,'construction entity for conversion of energy',$);
+#285=IFCCLASSIFICATIONREFERENCE($,'[CE]LA','Power station',#284,'energy supply facility for electrical power',$);
+#286=IFCCLASSIFICATIONREFERENCE($,'[CE]LB','Heating plant',#284,'energy supply facility for hot liquid',$);
+#287=IFCCLASSIFICATIONREFERENCE($,'[CE]LC','Combined heat and power station',#284,'energy supply facility for hot liquid and electrical power',$);
+#288=IFCCLASSIFICATIONREFERENCE($,'[CE]LD','Cooling plant',#284,'energy supply facility for cold liquid',$);
+#289=IFCCLASSIFICATIONREFERENCE($,'[CE]M?','Distribution facility',#200,'construction entity for distribution and communication between persons or systems',$);
+#290=IFCCLASSIFICATIONREFERENCE($,'[CE]MA','Control tower',#289,'distribution facility for control, regulation, and monitoring',$);
+#291=IFCCLASSIFICATIONREFERENCE($,'[CE]MB','Aerial mast',#289,'distribution facility for electromagnetic waves',$);
+#292=IFCCLASSIFICATIONREFERENCE($,'[CE]MC','Lighthouse',#289,'distribution facility for optical signals',$);
+#293=IFCCLASSIFICATIONREFERENCE($,'[CE]MD','Bell tower',#289,'distribution facility for audio signals',$);
+#294=IFCCLASSIFICATIONREFERENCE($,'[CE]ME','Overhead power line',#289,'distribution facility for electrical power above ground',$);
+#295=IFCCLASSIFICATIONREFERENCE($,'[CE]MF','Power cabling',#289,'distribution facility for electrical power in ground',$);
+#296=IFCCLASSIFICATIONREFERENCE($,'[CE]MG','Overhead signal line',#289,'distribution facility for signals above ground',$);
+#297=IFCCLASSIFICATIONREFERENCE($,'[CE]MH','Signal cabling',#289,'distribution facility for signals in ground',$);
+#298=IFCCLASSIFICATIONREFERENCE($,'[CE]MJ','Piping',#289,'distribution facility for fluids in a closed environment',$);
+#299=IFCCLASSIFICATIONREFERENCE($,'[CE]MK','Canal',#289,'distribution facility for fluids in an open environment',$);
+#300=IFCCLASSIFICATIONREFERENCE($,'[CE]ML','Heating piping',#289,'distribution facility for heating fluid',$);
+#301=IFCCLASSIFICATIONREFERENCE($,'[CE]MM','Cooling piping',#289,'distribution facility for cooling fluid',$);
+#302=IFCCLASSIFICATIONREFERENCE($,'[CE]MN','Distribution node facility',#289,'distribution facility for connecting parts of the network',$);
+#303=IFCCLASSIFICATIONREFERENCE($,'[CE]MP','Conveyor',#289,'distribution facility for moving matter over larger distances',$);
+#304=IFCCLASSIFICATIONREFERENCE($,'[CE]MQ','Cable car',#289,'distribution facility for moving people over larger distances',$);
+#305=IFCCLASSIFICATIONREFERENCE($,'[CE]MR','Garbage suction facility',#289,'distribution facility for solid waste',$);
+#306=IFCCLASSIFICATIONREFERENCE($,'[CE]N?','Production facility',#200,'construction entity for raw materials extraction or for manufacturing',$);
+#307=IFCCLASSIFICATIONREFERENCE($,'[CE]NA','Mine',#306,'production facility for solid matter used for further refinement',$);
+#308=IFCCLASSIFICATIONREFERENCE($,'[CE]NB','Gas or liquid extraction facility',#306,'production facility for fluid matter used for further refinement',$);
+#309=IFCCLASSIFICATIONREFERENCE($,'[CE]NC','Refinement facility',#306,'production facility for matter refinement',$);
+#310=IFCCLASSIFICATIONREFERENCE($,'[CE]ND','Workshop',#306,'production facility for fabrication or repair of products',$);
+#311=IFCCLASSIFICATIONREFERENCE($,'[CE]NE','Greenhouse',#306,'production facility for indoor plant production',$);
+#312=IFCCLASSIFICATIONREFERENCE($,'[CE]P?','Processing facility',#200,'construction entity for treating matter or electricity',$);
+#313=IFCCLASSIFICATIONREFERENCE($,'[CE]PA','Pumping station',#312,'processing facility to increase pressure',$);
+#314=IFCCLASSIFICATIONREFERENCE($,'[CE]PB','Treatment facility',#312,'processing facility for treating matter',$);
+#315=IFCCLASSIFICATIONREFERENCE($,'[CE]PC','Incinerator facility',#312,'processing facility for destruction by burning',$);
+#316=IFCCLASSIFICATIONREFERENCE($,'[CE]PD','Washing facility',#312,'processing facility for cleaning objects',$);
+#317=IFCCLASSIFICATIONREFERENCE($,'[CE]PE','Print shop',#312,'processing facility for printing',$);
+#318=IFCCLASSIFICATIONREFERENCE($,'[CE]PF','Mail sorting terminal',#312,'processing facility for mail sorting',$);
+#319=IFCCLASSIFICATIONREFERENCE($,'[CE]PG','Measuring station',#312,'processing facility for surveillance and/or data capture',$);
+#320=IFCCLASSIFICATIONREFERENCE($,'[CE]PH','Electrical power treatment facility',#312,'processing facility for treating electrical power',$);
+#321=IFCCLASSIFICATIONREFERENCE($,'[CE]Q?','Storage facility',#200,'construction entity for accumulations of matter or energy',$);
+#322=IFCCLASSIFICATIONREFERENCE($,'[CE]QA','Raw materials depot',#321,'storage facility for matter used for further refinement',$);
+#323=IFCCLASSIFICATIONREFERENCE($,'[CE]QB','Products storage',#321,'storage facility for products',$);
+#324=IFCCLASSIFICATIONREFERENCE($,'[CE]QC','Equipment building',#321,'storage facility for moveable technical equipment',$);
+#325=IFCCLASSIFICATIONREFERENCE($,'[CE]QD','Garage',#321,'storage facility for four-wheeled or larger road vehicles',$);
+#326=IFCCLASSIFICATIONREFERENCE($,'[CE]QE','Bicycle parking',#321,'storage facility for two-wheeled road vehicles',$);
+#327=IFCCLASSIFICATIONREFERENCE($,'[CE]QF','Train garage',#321,'storage facility for railway vehicles',$);
+#328=IFCCLASSIFICATIONREFERENCE($,'[CE]QG','Hangar',#321,'storage facility for aircraft',$);
+#329=IFCCLASSIFICATIONREFERENCE($,'[CE]QH','Boathouse',#321,'storage facility for watercraft',$);
+#330=IFCCLASSIFICATIONREFERENCE($,'[CE]QJ','Container space',#321,'storage facility for containers',$);
+#331=IFCCLASSIFICATIONREFERENCE($,'[CE]QK','Tank',#321,'storage facility for liquid, gas or other masses in sealed units',$);
+#332=IFCCLASSIFICATIONREFERENCE($,'[CE]QL','Landfill site',#321,'storage facility for loosened earth material',$);
+#333=IFCCLASSIFICATIONREFERENCE($,'[CE]QM','Geological repository',#321,'storage facility for hazardous material underground',$);
+#334=IFCCLASSIFICATIONREFERENCE($,'[CE]QN','Animal house',#321,'storage facility for animals',$);
+#335=IFCCLASSIFICATIONREFERENCE($,'[CE]QP','Data storage',#321,'storage facility for data',$);
+#336=IFCCLASSIFICATIONREFERENCE($,'[CE]QQ','Energy storage',#321,'storage facility for energy',$);
+#337=IFCCLASSIFICATIONREFERENCE($,'[CE]QZ','Burial site',#321,'storage facility for the deceased',$);
+#338=IFCCLASSIFICATIONREFERENCE($,'[CE]R?','Traffic facility',#200,'construction entity for discrete transport of people and/or matter',$);
+#339=IFCCLASSIFICATIONREFERENCE($,'[CE]RA','Road',#338,'traffic facility on land for non-track bound vehicles',$);
+#340=IFCCLASSIFICATIONREFERENCE($,'[CE]RB','Railroad',#338,'traffic facility for long distance transport with rail bound vehicles, with level crossings with other traffic',$);
+#341=IFCCLASSIFICATIONREFERENCE($,'[CE]RC','Light rail transit (LRT)',#338,'traffic facility for urban transport with rail bound vehicles, with level crossings with other traffic',$);
+#342=IFCCLASSIFICATIONREFERENCE($,'[CE]RD','Rapid transit',#338,'traffic facility for urban transport with rail bound vehicles, without level crossings with other traffic',$);
+#343=IFCCLASSIFICATIONREFERENCE($,'[CE]RE','Funicular',#338,'traffic facility for hillside railway traffic',$);
+#344=IFCCLASSIFICATIONREFERENCE($,'[CE]RF','Footpath',#338,'traffic facility for pedestrian traffic',$);
+#345=IFCCLASSIFICATIONREFERENCE($,'[CE]RG','Foot and bicycle path',#338,'traffic facility for bicycle and pedestrian traffic',$);
+#346=IFCCLASSIFICATIONREFERENCE($,'[CE]RH','Bicycle path',#338,'traffic facility for bicycle traffic',$);
+#347=IFCCLASSIFICATIONREFERENCE($,'[CE]RJ','Airport way',#338,'traffic facility for air traffic starting, landing and ground movements',$);
+#348=IFCCLASSIFICATIONREFERENCE($,'[CE]RK','Bridge',#338,'traffic facility to cross over an obstacle',$);
+#349=IFCCLASSIFICATIONREFERENCE($,'[CE]RL','Transport tunnel',#338,'traffic facility to cross under or through an obstacle',$);
+#350=IFCCLASSIFICATIONREFERENCE($,'[CE]RM','Transport canal',#338,'traffic facility for water-borne traffic in ground',$);
+#351=IFCCLASSIFICATIONREFERENCE($,'[CE]RN','Aqueduct',#338,'traffic facility for water-borne traffic above ground',$);
+#352=IFCCLASSIFICATIONREFERENCE($,'[CE]RP','Bridleway',#338,'traffic facility for equestrian traffic',$);
+#353=IFCCLASSIFICATIONREFERENCE($,'[CE]RQ','Wildlife crossing',#338,'traffic facility for wildlife bypassing man-made entities',$);
+#354=IFCCLASSIFICATIONREFERENCE($,'[CE]S?','Traffic transfer facility',#200,'construction entity for connecting traffic facilities',$);
+#355=IFCCLASSIFICATIONREFERENCE($,'[CE]SA','Train station',#354,'traffic transfer facility between rail transport and other traffic',$);
+#356=IFCCLASSIFICATIONREFERENCE($,'[CE]SB','Railway platform',#354,'traffic transfer facility for embarking a rail bound vehicle',$);
+#357=IFCCLASSIFICATIONREFERENCE($,'[CE]SC','Level crossing',#354,'traffic transfer facility between railroads and roads or paths',$);
+#358=IFCCLASSIFICATIONREFERENCE($,'[CE]SD','Travel centre',#354,'traffic transfer facility between multiple means of transport',$);
+#359=IFCCLASSIFICATIONREFERENCE($,'[CE]SE','Airport terminal',#354,'traffic transfer facility between air transport and other traffic',$);
+#360=IFCCLASSIFICATIONREFERENCE($,'[CE]SF','Bus station',#354,'traffic transfer facility between bus transport and other traffic',$);
+#361=IFCCLASSIFICATIONREFERENCE($,'[CE]SG','Truck terminal',#354,'traffic transfer facility between truck transport and other traffic',$);
+#362=IFCCLASSIFICATIONREFERENCE($,'[CE]SH','Taxi stand',#354,'traffic transfer facility between taxi transport and other traffic',$);
+#363=IFCCLASSIFICATIONREFERENCE($,'[CE]SJ','Sea terminal',#354,'traffic transfer facility between water-borne and other traffic',$);
+#364=IFCCLASSIFICATIONREFERENCE($,'[CE]T?','Land use facility',#200,'construction entity for land-based production',$);
+#365=IFCCLASSIFICATIONREFERENCE($,'[CE]TA','Field',#364,'land use facility for annual yield',$);
+#366=IFCCLASSIFICATIONREFERENCE($,'[CE]TB','Forest',#364,'land use facility for long-term yield',$);
+#367=IFCCLASSIFICATIONREFERENCE($,'[CE]U?','Water control facility',#200,'construction entity for water management',$);
+#368=IFCCLASSIFICATIONREFERENCE($,'[CE]UA','Dam',#367,'water control facility to retain a water level',$);
+#369=IFCCLASSIFICATIONREFERENCE($,'[CE]UB','Barrage',#367,'water control facility to retain a water level with an overflow',$);
+#370=IFCCLASSIFICATIONREFERENCE($,'[CE]UC','Sluice',#367,'water control facility to adjust water level',$);
+#371=IFCCLASSIFICATIONREFERENCE($,'[CE]UD','Mole',#367,'water control facility to dampen wave movements',$);
+#372=IFCCLASSIFICATIONREFERENCE($,'[CE]UF','Dry dock',#367,'water control facility to lower the water level to zero',$);
+#373=IFCCLASSIFICATIONREFERENCE($,'[CE]UG','Dyke',#367,'water control facility to prevent overflow',$);
+#374=IFCCLASSIFICATIONREFERENCE($,'[CE]UH','Gate',#367,'water control facility to control a water flow',$);
+#375=IFCCLASSIFICATIONREFERENCE($,'[CE]X?','Ruins',#200,'construction entity permanently lacking original use',$);
+#376=IFCCLASSIFICATIONREFERENCE($,'[CE]XA','Contemporary building ruins',#375,'ruins lacking cultural and historic value',$);
+#377=IFCCLASSIFICATIONREFERENCE($,'[CE]XB','Cultural heritage building ruins',#375,'ruins of cultural and historic value of a building',$);
+#378=IFCCLASSIFICATIONREFERENCE($,'[CE]XC','Cultural heritage site ruins',#375,'ruins of cultural and historic value of a site',$);
+#379=IFCCLASSIFICATIONREFERENCE($,'[CE]XD','Cultural heritage grave ruins',#375,'ruins of cultural and historic value of a grave or grave site',$);
+#380=IFCCLASSIFICATIONREFERENCE($,'[CE]Y?','Ornamental entity',#200,'construction entity with symbolic or artistic meaning',$);
+#381=IFCCLASSIFICATIONREFERENCE($,'[CE]YA','Monument',#380,'ornamental entity for remembrance of person or event',$);
+#382=IFCCLASSIFICATIONREFERENCE($,'[CE]YB','Fountain',#380,'ornamental entity with water',$);
+#383=IFCCLASSIFICATIONREFERENCE($,'[CE]YC','Statue',#380,'ornamental entity in freestanding sculptural form',$);
+#384=IFCCLASSIFICATIONREFERENCE($,'[CE]Z?','Unknown construction entity',#200,'construction entity with unknown use',$);
+#385=IFCCLASSIFICATIONREFERENCE($,'[CE]ZZ','Unknown construction entity',#384,'unknown construction entity',$);
+#400=IFCCLASSIFICATIONREFERENCE($,'CS - Built space',' ',#2,' ',$);
+#401=IFCCLASSIFICATIONREFERENCE($,'[CS]A??','Space for human dwelling',#400,'space designed for human dwelling and related activities',$);
+#402=IFCCLASSIFICATIONREFERENCE($,'[CS]AA?','Occupiable space',#401,'space for human dwelling for rest, relaxation, recreation or waiting',$);
+#403=IFCCLASSIFICATIONREFERENCE($,'[CS]AAA','Room',#402,'occupiable space for personal use',$);
+#404=IFCCLASSIFICATIONREFERENCE($,'[CS]AAB','Living space',#402,'occupiable space for shared use',$);
+#405=IFCCLASSIFICATIONREFERENCE($,'[CS]AAC','Waiting space',#402,'occupiable space for waiting',$);
+#406=IFCCLASSIFICATIONREFERENCE($,'[CS]AAD','Courtyard',#402,'occupiable space outdoors entirely or partly surrounded by buildings',$);
+#407=IFCCLASSIFICATIONREFERENCE($,'[CS]AB?','Personal hygiene space',#401,'space for human dwelling for personal hygiene use',$);
+#408=IFCCLASSIFICATIONREFERENCE($,'[CS]ABA','Bathroom',#407,'personal hygiene space enabling persons to wash and possibly relieve themselves',$);
+#409=IFCCLASSIFICATIONREFERENCE($,'[CS]ABB','Toilet',#407,'personal hygiene space enabling persons to relieve themselves',$);
+#410=IFCCLASSIFICATIONREFERENCE($,'[CS]ABC','Baby changing room',#407,'personal hygiene space for young children',$);
+#411=IFCCLASSIFICATIONREFERENCE($,'[CS]ABD','Changing room',#407,'personal hygiene space for changing clothes',$);
+#412=IFCCLASSIFICATIONREFERENCE($,'[CS]ABE','Shower',#407,'personal hygiene space enabling persons to wash themselves',$);
+#413=IFCCLASSIFICATIONREFERENCE($,'[CS]ABF','Sauna',#407,'personal hygiene space enabling persons to take a sweat-inducing bath',$);
+#414=IFCCLASSIFICATIONREFERENCE($,'[CS]AC?','Isolation space',#401,'space for human dwelling for isolating persons',$);
+#415=IFCCLASSIFICATIONREFERENCE($,'[CS]ACA','Shelter',#414,'isolation space to protect persons from hazardous of dangerous situations',$);
+#416=IFCCLASSIFICATIONREFERENCE($,'[CS]ACB','Cell',#414,'isolation space to separate persons from other persons',$);
+#417=IFCCLASSIFICATIONREFERENCE($,'[CS]B??','Space for human activity',#400,'space designed for humans’ leisure and business activities',$);
+#418=IFCCLASSIFICATIONREFERENCE($,'[CS]BA?','Workspace',#417,'space for human activity for producing an immaterial result',$);
+#419=IFCCLASSIFICATIONREFERENCE($,'[CS]BAA','Office',#418,'workspace for the production, processing and interpretation of information',$);
+#420=IFCCLASSIFICATIONREFERENCE($,'[CS]BAB','Meeting space',#418,'workspace for meetings',$);
+#421=IFCCLASSIFICATIONREFERENCE($,'[CS]BAC','Reception',#418,'workspace for receiving guests and goods',$);
+#422=IFCCLASSIFICATIONREFERENCE($,'[CS]BAD','Sales area',#418,'workspace for selling goods and services',$);
+#423=IFCCLASSIFICATIONREFERENCE($,'[CS]BAE','Teaching space',#418,'workspace conveying knowledge and competencies',$);
+#424=IFCCLASSIFICATIONREFERENCE($,'[CS]BAF','Study',#418,'workspace for reading',$);
+#425=IFCCLASSIFICATIONREFERENCE($,'[CS]BAG','Laundry',#418,'workspace for laundering clothes and linen',$);
+#426=IFCCLASSIFICATIONREFERENCE($,'[CS]BAH','Treatment space',#418,'workspace for health-related and other treatment and care of individuals',$);
+#427=IFCCLASSIFICATIONREFERENCE($,'[CS]BAJ','Examining space',#418,'workspace for diagnosis',$);
+#428=IFCCLASSIFICATIONREFERENCE($,'[CS]BAK','Observation space',#418,'workspace for monitoring individuals',$);
+#429=IFCCLASSIFICATIONREFERENCE($,'[CS]BAL','Viewing space',#418,'workspace for observing processes and persons in adjacent space',$);
+#430=IFCCLASSIFICATIONREFERENCE($,'[CS]BAM','Media workshop',#418,'workspace for recording and editing media',$);
+#431=IFCCLASSIFICATIONREFERENCE($,'[CS]BB?','Production space',#417,'space for human activity for producing a material result',$);
+#432=IFCCLASSIFICATIONREFERENCE($,'[CS]BBA','Kitchen',#431,'production space for working with foodstuffs',$);
+#433=IFCCLASSIFICATIONREFERENCE($,'[CS]BBB','Materials workshop',#431,'production space for working with materials',$);
+#434=IFCCLASSIFICATIONREFERENCE($,'[CS]BBC','Machine shop',#431,'production space for working with machinery and equipment',$);
+#435=IFCCLASSIFICATIONREFERENCE($,'[CS]BBD','Studio',#431,'production space for working on art and crafts',$);
+#436=IFCCLASSIFICATIONREFERENCE($,'[CS]BBE','Packing space',#431,'production space for sorting and packing objects',$);
+#437=IFCCLASSIFICATIONREFERENCE($,'[CS]BC?','Laboratory',#417,'space for human activity for performing analyses and tests',$);
+#438=IFCCLASSIFICATIONREFERENCE($,'[CS]BCA','Technical laboratory',#437,'laboratory for technical analyses',$);
+#439=IFCCLASSIFICATIONREFERENCE($,'[CS]BCB','Materials laboratory',#437,'laboratory for studying materials',$);
+#440=IFCCLASSIFICATIONREFERENCE($,'[CS]BCC','Biology laboratory',#437,'laboratory for biological analysis',$);
+#441=IFCCLASSIFICATIONREFERENCE($,'[CS]BD?','Practice space',#417,'space for human activity of sport or culture',$);
+#442=IFCCLASSIFICATIONREFERENCE($,'[CS]BDA','Sports hall',#441,'practice space for sports',$);
+#443=IFCCLASSIFICATIONREFERENCE($,'[CS]BDB','Training room',#441,'practice space for training strength, condition and balance',$);
+#444=IFCCLASSIFICATIONREFERENCE($,'[CS]BDC','Rehearsal room',#441,'practice space for practicing artistic competencies',$);
+#445=IFCCLASSIFICATIONREFERENCE($,'[CS]BDD','Stage',#441,'practice space for performance artistic works',$);
+#446=IFCCLASSIFICATIONREFERENCE($,'[CS]BDE','Playroom',#441,'practice space for children',$);
+#447=IFCCLASSIFICATIONREFERENCE($,'[CS]BE?','Assembly space',#417,'space for human activity for mass assemblies of people/persons',$);
+#448=IFCCLASSIFICATIONREFERENCE($,'[CS]BEA','Dining space',#447,'assembly space for consumption of food',$);
+#449=IFCCLASSIFICATIONREFERENCE($,'[CS]BEB','Hall',#447,'assembly space for social events',$);
+#450=IFCCLASSIFICATIONREFERENCE($,'[CS]BEC','Gallery',#447,'assembly space for displaying objects',$);
+#451=IFCCLASSIFICATIONREFERENCE($,'[CS]BED','Religious faith space',#447,'assembly space for religious activities',$);
+#452=IFCCLASSIFICATIONREFERENCE($,'[CS]C??','Storage space',#400,'space designed for storage of materials, equipment and organisms',$);
+#453=IFCCLASSIFICATIONREFERENCE($,'[CS]CA?','Material storage space',#452,'storage space for resources',$);
+#454=IFCCLASSIFICATIONREFERENCE($,'[CS]CAA','Refuse storage space',#453,'material storage space for refused and rejected materials or objects',$);
+#455=IFCCLASSIFICATIONREFERENCE($,'[CS]CAB','Chemicals storage space',#453,'material storage space for chemicals',$);
+#456=IFCCLASSIFICATIONREFERENCE($,'[CS]CAC','Medicine storage space',#453,'material storage space for medicines',$);
+#457=IFCCLASSIFICATIONREFERENCE($,'[CS]CAD','Archive storage space',#453,'material storage space for physical documents or media',$);
+#458=IFCCLASSIFICATIONREFERENCE($,'[CS]CAE','Fluid storage space',#453,'material storage space for fluid',$);
+#459=IFCCLASSIFICATIONREFERENCE($,'[CS]CAF','Raw materials storage space',#453,'material storage space for substances or materials for use in production processes',$);
+#460=IFCCLASSIFICATIONREFERENCE($,'[CS]CAG','Food storage space',#453,'material storage space for food and drinks',$);
+#461=IFCCLASSIFICATIONREFERENCE($,'[CS]CAH','Secure storage space',#453,'material storage space for valuable objects',$);
+#462=IFCCLASSIFICATIONREFERENCE($,'[CS]CAJ','Packaging storage space',#453,'material storage space of materials for packaging',$);
+#463=IFCCLASSIFICATIONREFERENCE($,'[CS]CAK','Clothes storage space',#453,'material storage space for clothing, shoes and linen',$);
+#464=IFCCLASSIFICATIONREFERENCE($,'[CS]CAL','Building materials storage space',#453,'material storage space for building materials',$);
+#465=IFCCLASSIFICATIONREFERENCE($,'[CS]CAM','Explosives space',#453,'material storage space for explosives',$);
+#466=IFCCLASSIFICATIONREFERENCE($,'[CS]CAN','Stockroom',#453,'material storage space for products, goods and cargo',$);
+#467=IFCCLASSIFICATIONREFERENCE($,'[CS]CB?','Equipment storage space',#452,'storage space for moveable technical equipment',$);
+#468=IFCCLASSIFICATIONREFERENCE($,'[CS]CBA','Furniture storage space',#467,'equipment storage space of objects for fitting out (of spaces)',$);
+#469=IFCCLASSIFICATIONREFERENCE($,'[CS]CBB','Tool storage space',#467,'equipment storage space for tools',$);
+#470=IFCCLASSIFICATIONREFERENCE($,'[CS]CBC','Instrument storage space',#467,'equipment storage space for technical instruments',$);
+#471=IFCCLASSIFICATIONREFERENCE($,'[CS]CBD','Vehicle storage space',#467,'equipment storage space for modes of transport',$);
+#472=IFCCLASSIFICATIONREFERENCE($,'[CS]CC?','Animal enclosure',#452,'storage space for enclosing animals',$);
+#473=IFCCLASSIFICATIONREFERENCE($,'[CS]CCA','Cage',#472,'animal enclosure for keeping animals locked up',$);
+#474=IFCCLASSIFICATIONREFERENCE($,'[CS]CCB','Stall',#472,'animal enclosure for keeping animals fenced in',$);
+#475=IFCCLASSIFICATIONREFERENCE($,'[CS]CCC','Stable',#472,'animal enclosure for providing shelter',$);
+#476=IFCCLASSIFICATIONREFERENCE($,'[CS]CCD','Water enclosure',#472,'animal enclosure for keeping animals in water',$);
+#477=IFCCLASSIFICATIONREFERENCE($,'[CS]CD?','Planting space',#452,'storage space for plants',$);
+#478=IFCCLASSIFICATIONREFERENCE($,'[CS]CDA','Indoor planting space',#477,'planting space for growing plants indoor',$);
+#479=IFCCLASSIFICATIONREFERENCE($,'[CS]CDB','Outdoor planting space',#477,'planting space for growing plants outdoor',$);
+#480=IFCCLASSIFICATIONREFERENCE($,'[CS]CE?','Dead body storage',#452,'storage space for dead bodies',$);
+#481=IFCCLASSIFICATIONREFERENCE($,'[CS]CEA','Chapel of rest',#480,'dead body storage for interim storage',$);
+#482=IFCCLASSIFICATIONREFERENCE($,'[CS]CEB','Crypt',#480,'dead body storage for permanent storage',$);
+#483=IFCCLASSIFICATIONREFERENCE($,'[CS]D??','Space for technical systems',#400,'space designed for active technical equipment',$);
+#484=IFCCLASSIFICATIONREFERENCE($,'[CS]DA?','Installation space',#483,'space for technical systems which support the function of construction entities',$);
+#485=IFCCLASSIFICATIONREFERENCE($,'[CS]DAA','Electric installation space',#484,'installation space for stationary electric equipment',$);
+#486=IFCCLASSIFICATIONREFERENCE($,'[CS]DAB','Information technology (IT) equipment space',#484,'installation space for stationary electronic equipment',$);
+#487=IFCCLASSIFICATIONREFERENCE($,'[CS]DAC','Mechanical installation space',#484,'installation space for stationary mechanical equipment',$);
+#488=IFCCLASSIFICATIONREFERENCE($,'[CS]DAD','Shared installation space',#484,'installation space for stationary technical equipment',$);
+#489=IFCCLASSIFICATIONREFERENCE($,'[CS]DB?','Equipment space',#483,'space for technical systems which support human activities',$);
+#490=IFCCLASSIFICATIONREFERENCE($,'[CS]DBA','Control equipment space',#489,'equipment space of controlling, regulating and monitoring',$);
+#491=IFCCLASSIFICATIONREFERENCE($,'[CS]DBB','Production plant space',#489,'equipment space of fabricating and preparing products',$);
+#492=IFCCLASSIFICATIONREFERENCE($,'[CS]DBC','Service equipment space',#489,'equipment space of service functions',$);
+#493=IFCCLASSIFICATIONREFERENCE($,'[CS]DBD','Analysis equipment space',#489,'equipment space of analyses',$);
+#494=IFCCLASSIFICATIONREFERENCE($,'[CS]DC?','Equipment operation space',#483,'space for technical systems which provides space for equipment operation',$);
+#495=IFCCLASSIFICATIONREFERENCE($,'[CS]DCA','Work envelope',#494,'equipment operation space providing equipment movement space',$);
+#496=IFCCLASSIFICATIONREFERENCE($,'[CS]DCB','Clearance space',#494,'equipment operation space providing clearance around equipment',$);
+#497=IFCCLASSIFICATIONREFERENCE($,'[CS]E??','Space for infrastructure',#400,'space designed to create links between activity spaces',$);
+#498=IFCCLASSIFICATIONREFERENCE($,'[CS]EA?','Circulation space',#497,'space for infrastructure providing access',$);
+#499=IFCCLASSIFICATIONREFERENCE($,'[CS]EAA','Vestibule',#498,'circulation space between outdoor and indoor spaces',$);
+#500=IFCCLASSIFICATIONREFERENCE($,'[CS]EAB','Air lock',#498,'circulation space for regulating passage between spaces',$);
+#501=IFCCLASSIFICATIONREFERENCE($,'[CS]EAC','Corridor',#498,'circulation space horizontally between two or more spaces',$);
+#502=IFCCLASSIFICATIONREFERENCE($,'[CS]EAD','Stairwell',#498,'circulation space vertically and non-automated between two or more storeys or levels',$);
+#503=IFCCLASSIFICATIONREFERENCE($,'[CS]EAE','Lift space',#498,'circulation space vertically and automated between two or more levels',$);
+#504=IFCCLASSIFICATIONREFERENCE($,'[CS]EAF','Portico',#498,'circulation space through a building',$);
+#505=IFCCLASSIFICATIONREFERENCE($,'[CS]EB?','Services space',#497,'space for infrastructure for distributing technical services or media',$);
+#506=IFCCLASSIFICATIONREFERENCE($,'[CS]EBA','Services tunnel',#505,'services space with horizontal access for persons',$);
+#507=IFCCLASSIFICATIONREFERENCE($,'[CS]EBB','Shaft',#505,'services space with vertical access for persons',$);
+#508=IFCCLASSIFICATIONREFERENCE($,'[CS]EBC','Duct',#505,'services space without access for persons',$);
+#509=IFCCLASSIFICATIONREFERENCE($,'[CS]F??','Traffic space',#400,'space for traffic',$);
+#510=IFCCLASSIFICATIONREFERENCE($,'[CS]FA?','Vehicle traffic space',#509,'traffic space for vehicles',$);
+#511=IFCCLASSIFICATIONREFERENCE($,'[CS]FAA','Carriageway',#510,'vehicle traffic space intended for vehicles',$);
+#512=IFCCLASSIFICATIONREFERENCE($,'[CS]FAB','Traffic lane',#510,'vehicle traffic space for one line of traffic',$);
+#513=IFCCLASSIFICATIONREFERENCE($,'[CS]FAC','Shoulder',#510,'vehicle traffic space along a carriageway for emergency and intermediate use',$);
+#514=IFCCLASSIFICATIONREFERENCE($,'[CS]FAD','Lay-by out lane',#510,'vehicle traffic space for stopping of vehicles',$);
+#515=IFCCLASSIFICATIONREFERENCE($,'[CS]FAE','Passing place',#510,'vehicle traffic space for meeting of vehicles',$);
+#516=IFCCLASSIFICATIONREFERENCE($,'[CS]FAF','Turning place',#510,'vehicle traffic space for turning of vehicles',$);
+#517=IFCCLASSIFICATIONREFERENCE($,'[CS]FB?','Active transportation space',#509,'traffic space for persons, animals or bicycles',$);
+#518=IFCCLASSIFICATIONREFERENCE($,'[CS]FBA','Walkway',#517,'active transportation spaces for pedestrians',$);
+#519=IFCCLASSIFICATIONREFERENCE($,'[CS]FBB','Cycle path',#517,'active transportation space for bicycles',$);
+#520=IFCCLASSIFICATIONREFERENCE($,'[CS]FBC','Cycle- and footpath',#517,'active transportation space for bicycles and pedestrians',$);
+#521=IFCCLASSIFICATIONREFERENCE($,'[CS]FBD','Animal path',#517,'active transportation space for animals',$);
+#522=IFCCLASSIFICATIONREFERENCE($,'[CS]FC','Aircraft traffic space',#509,'traffic space for aircrafts',$);
+#523=IFCCLASSIFICATIONREFERENCE($,'[CS]FCA','Runway',#522,'aircraft traffic space for take-off and landing',$);
+#524=IFCCLASSIFICATIONREFERENCE($,'[CS]FCB','Taxiway',#522,'aircraft traffic space for taxiing',$);
+#525=IFCCLASSIFICATIONREFERENCE($,'[CS]FD?','Track space',#509,'traffic space for track bound objects',$);
+#526=IFCCLASSIFICATIONREFERENCE($,'[CS]FDA','Rail track',#525,'track space not intended for crossing',$);
+#527=IFCCLASSIFICATIONREFERENCE($,'[CS]FDB','Railway crossing',#525,'track space intended for crossing',$);
+#528=IFCCLASSIFICATIONREFERENCE($,'[CS]FE?','Vessel traffic space',#509,'traffic space for vessels',$);
+#529=IFCCLASSIFICATIONREFERENCE($,'[CS]FEA','Fairway',#528,'vessel track space for horizontal traffic',$);
+#530=IFCCLASSIFICATIONREFERENCE($,'[CS]FEB','Sluice',#528,'vessel track space for vertical traffic',$);
+#531=IFCCLASSIFICATIONREFERENCE($,'[CS]FF?','Traffic management space',#509,'traffic space for separation and management',$);
+#532=IFCCLASSIFICATIONREFERENCE($,'[CS]FFA','Dividing strip',#531,'traffic management space separating traffic spaces',$);
+#533=IFCCLASSIFICATIONREFERENCE($,'[CS]FFB','Platform space',#531,'traffic management space for change of transport mode',$);
+#534=IFCCLASSIFICATIONREFERENCE($,'[CS]FFC','Traffic information space',#531,'traffic management space providing information',$);
+#535=IFCCLASSIFICATIONREFERENCE($,'[CS]FFD','Clearance profile',#531,'traffic management space providing clearance to other objects',$);
+#536=IFCCLASSIFICATIONREFERENCE($,'[CS]FFE','Prohibited area',#531,'traffic management space being part of read or street not meant for vehicle traffic',$);
+#537=IFCCLASSIFICATIONREFERENCE($,'[CS]P??','Physical phenomenon space',#400,'space designed for the occurrence of physical phenomena',$);
+#538=IFCCLASSIFICATIONREFERENCE($,'[CS]PA?','Matter flow space',#537,'physical phenomenon space for flow or occurrence of matter',$);
+#539=IFCCLASSIFICATIONREFERENCE($,'[CS]PAA','Debris space',#538,'matter flow space for objects',$);
+#540=IFCCLASSIFICATIONREFERENCE($,'[CS]PAB','Solid material flow space',#538,'matter flow space for solid materials',$);
+#541=IFCCLASSIFICATIONREFERENCE($,'[CS]PAC','Gas flow spaces',#538,'matter flow space for gas',$);
+#542=IFCCLASSIFICATIONREFERENCE($,'[CS]PAD','Liquid flow spaces',#538,'matter flow space for liquid',$);
+#543=IFCCLASSIFICATIONREFERENCE($,'[CS]PAE','Plasma flow space',#538,'matter flow space for plasma',$);
+#544=IFCCLASSIFICATIONREFERENCE($,'[CS]PAF','Smoke occurrence space',#538,'matter flow space for multiple matter kinds',$);
+#545=IFCCLASSIFICATIONREFERENCE($,'[CS]PB?','Energy flow space',#537,'physical phenomenon space for flow of energy',$);
+#546=IFCCLASSIFICATIONREFERENCE($,'[CS]PBA','Electrical energy flow space',#545,'energy flow spaces for electrical energy',$);
+#547=IFCCLASSIFICATIONREFERENCE($,'[CS]PBB','Thermal energy flow space',#545,'energy flow spaces for thermal energy',$);
+#548=IFCCLASSIFICATIONREFERENCE($,'[CS]PBC','Mechanical energy flow space',#545,'energy flow spaces for mechanical energy',$);
+#549=IFCCLASSIFICATIONREFERENCE($,'[CS]PBD','Explosion space',#545,'energy flow spaces for explosion',$);
+#550=IFCCLASSIFICATIONREFERENCE($,'[CS]PBE','Fire zone',#545,'energy flow spaces for combustion',$);
+
+#600=IFCCLASSIFICATIONREFERENCE($,'CF - Functional system',' ',#2,' ',$);
+#601=IFCCLASSIFICATIONREFERENCE($,'[CF]A','Ground system',#600,'space system which terminates a construction entity downwards',$);
+#602=IFCCLASSIFICATIONREFERENCE($,'[CF]B','Wall system',#600,'space system which forms and separates space vertically',$);
+#603=IFCCLASSIFICATIONREFERENCE($,'[CF]C','Slab system',#600,'space system which forms and separates space horizontally',$);
+#604=IFCCLASSIFICATIONREFERENCE($,'[CF]D','Roof system',#600,'space system which terminates a construction entity upwards',$);
+#605=IFCCLASSIFICATIONREFERENCE($,'[CF]E','Gas and air system',#600,'installation system which supplies technical gases or technical air',$);
+#606=IFCCLASSIFICATIONREFERENCE($,'[CF]F','Water and fluid system',#600,'installation system which supplies domestic water, technical water or other liquid',$);
+#607=IFCCLASSIFICATIONREFERENCE($,'[CF]G','Drainage and waste system',#600,'installation system which discharges liquid or disposes of waste',$);
+#608=IFCCLASSIFICATIONREFERENCE($,'[CF]H','Cooling and/or heating system',#600,'installation system which supplies cooling and/or heating',$);
+#609=IFCCLASSIFICATIONREFERENCE($,'[CF]J','Ventilation system',#600,'installation system which supplies air exchange',$);
+#610=IFCCLASSIFICATIONREFERENCE($,'[CF]K','Electrical system',#600,'installation system which supplies electrical energy',$);
+#611=IFCCLASSIFICATIONREFERENCE($,'[CF]L','Automation system',#600,'installation system which controls, adjusts and monitors technical systems in a context',$);
+#612=IFCCLASSIFICATIONREFERENCE($,'[CF]M','Information and communication system',#600,'installation system which provides communications between persons or technical systems',$);
+#613=IFCCLASSIFICATIONREFERENCE($,'[CF]N','Transportation system',#600,'installation system which transports goods or persons',$);
+#614=IFCCLASSIFICATIONREFERENCE($,'[CF]P','Security and safety system',#600,'installation system which protects any object from danger and damage',$);
+#615=IFCCLASSIFICATIONREFERENCE($,'[CF]Q','Lighting system',#600,'installation system for light management',$);
+#616=IFCCLASSIFICATIONREFERENCE($,'[CF]R','Railroad system',#600,'installation system guiding and directing track-bound vehicles',$);
+#617=IFCCLASSIFICATIONREFERENCE($,'[CF]S','Arrangement system',#600,'fit-out system which equips a construction entity and its spaces with fittings and equipment',$);
+#700=IFCCLASSIFICATIONREFERENCE($,'CT - Technical system',' ',#2,' ',$);
+#701=IFCCLASSIFICATIONREFERENCE($,'[CT]A?','Assembly system',#700,'technical system which constitutes a layered construction',$);
+#702=IFCCLASSIFICATIONREFERENCE($,'[CT]AA','Pavement construction',#701,'assembly system forming areas for transport',$);
+#703=IFCCLASSIFICATIONREFERENCE($,'[CT]AB','Foundation construction',#701,'assembly system forming separation towards underground',$);
+#704=IFCCLASSIFICATIONREFERENCE($,'[CT]AC','Slab construction',#701,'assembly system forming horizontal separation',$);
+#705=IFCCLASSIFICATIONREFERENCE($,'[CT]AD','Wall construction',#701,'assembly system forming vertical separation',$);
+#706=IFCCLASSIFICATIONREFERENCE($,'[CT]AE','Roof construction',#701,'assembly system terminating a construction entity upwards',$);
+#707=IFCCLASSIFICATIONREFERENCE($,'[CT]AF','Stairway construction',#701,'assembly system establishing a step-wise link between two or more levels',$);
+#708=IFCCLASSIFICATIONREFERENCE($,'[CT]AG','Ramp construction',#701,'assembly system establishing a step-free link between two or more levels',$);
+#709=IFCCLASSIFICATIONREFERENCE($,'[CT]AH','Balcony',#701,'assembly system that creates an adjoining outer user space built into wall system or roof system',$);
+#710=IFCCLASSIFICATIONREFERENCE($,'[CT]AJ','Bay window',#701,'assembly system that creates an extended inner user space built into wall system',$);
+#711=IFCCLASSIFICATIONREFERENCE($,'[CT]AK','Dormer',#701,'assembly system that creates an extended inner user space built into roof system',$);
+#712=IFCCLASSIFICATIONREFERENCE($,'[CT]AL','Roof tower',#701,'assembly system that creates an extended inner user space built on top of roof system or construction entity',$);
+#713=IFCCLASSIFICATIONREFERENCE($,'[CT]AM','Light well',#701,'assembly system that creates an extended space for light transmission',$);
+#714=IFCCLASSIFICATIONREFERENCE($,'[CT]B?','Structural system',#700,'technical system which forms structural construction',$);
+#715=IFCCLASSIFICATIONREFERENCE($,'[CT]BA','Groundworks structure',#714,'structural system forming a regulated volume of underground',$);
+#716=IFCCLASSIFICATIONREFERENCE($,'[CT]BB','Foundation structure',#714,'structural system connecting a construction entity with the underground',$);
+#717=IFCCLASSIFICATIONREFERENCE($,'[CT]BC','Slab structure',#714,'structural system in a horizontal separation',$);
+#718=IFCCLASSIFICATIONREFERENCE($,'[CT]BD','Wall structure',#714,'structural system in a vertical separation',$);
+#719=IFCCLASSIFICATIONREFERENCE($,'[CT]BE','Roof structure',#714,'structural system terminating a construction entity upwards',$);
+#720=IFCCLASSIFICATIONREFERENCE($,'[CT]BF','Floor structure',#714,'structural system terminating a space downwards',$);
+#721=IFCCLASSIFICATIONREFERENCE($,'[CT]BG','Ceiling structure',#714,'structural system terminating a space upwards',$);
+#722=IFCCLASSIFICATIONREFERENCE($,'[CT]BH','Routing structure',#714,'structural system supporting supply or distribution system',$);
+#723=IFCCLASSIFICATIONREFERENCE($,'[CT]C?','Ground surface construction system',#700,'technical construction system forming ground-based areas',$);
+#724=IFCCLASSIFICATIONREFERENCE($,'[CT]CA','Subgrade construction',#723,'ground surface construction system which gives support to other constructions',$);
+#725=IFCCLASSIFICATIONREFERENCE($,'[CT]CB','Ground superstructure construction',#723,'ground surface construction system for hard covered outdoor space',$);
+#726=IFCCLASSIFICATIONREFERENCE($,'[CT]CC','Traffic island construction',#723,'ground surface construction system for traffic separation area',$);
+#727=IFCCLASSIFICATIONREFERENCE($,'[CT]CD','Side area construction',#723,'ground surface construction system for safety zone for traffic',$);
+#728=IFCCLASSIFICATIONREFERENCE($,'[CT]CE','Railway embankment construction',#723,'ground surface construction system for railway track support',$);
+#729=IFCCLASSIFICATIONREFERENCE($,'[CT]CF','Vegetation area construction',#723,'ground surface construction system for vegetation areas',$);
+#730=IFCCLASSIFICATIONREFERENCE($,'[CT]CG','Water management construction',#723,'ground surface construction system for transport, storage or infiltration of water, or for space along water or over water',$);
+#731=IFCCLASSIFICATIONREFERENCE($,'[CT]CH','Pipe ditch system',#723,'ground surface construction system for refilled media excavation',$);
+#732=IFCCLASSIFICATIONREFERENCE($,'[CT]CJ','Foundation and ground support construction',#723,'ground surface construction system which carries vertical or horizontal load',$);
+#733=IFCCLASSIFICATIONREFERENCE($,'[CT]CK','Terrain stairway / terrain ramp construction',#723,'ground surface construction system which connects spaces at different levels',$);
+#734=IFCCLASSIFICATIONREFERENCE($,'[CT]CL','Terrain wall construction',#723,'ground surface construction system which separates spaces',$);
+#735=IFCCLASSIFICATIONREFERENCE($,'[CT]D?','Railway track construction system',#700,'technical system which guides rail bound vehicles',$);
+#736=IFCCLASSIFICATIONREFERENCE($,'[CT]DA','Ballasted railway track construction',#735,'railway track construction system with two rails, on ballast',$);
+#737=IFCCLASSIFICATIONREFERENCE($,'[CT]DB','Non-ballasted railway track construction',#735,'railway track construction system with two rails, without ballast',$);
+#738=IFCCLASSIFICATIONREFERENCE($,'[CT]DC','Monorail construction',#735,'railway track construction system with one rail',$);
+#739=IFCCLASSIFICATIONREFERENCE($,'[CT]H?','Supply system',#700,'technical system supplying consumption',$);
+#740=IFCCLASSIFICATIONREFERENCE($,'[CT]HA','Gas and air supply system',#739,'supply system for process gas',$);
+#741=IFCCLASSIFICATIONREFERENCE($,'[CT]HB','Liquid supply system',#739,'supply system for liquid',$);
+#742=IFCCLASSIFICATIONREFERENCE($,'[CT]HC','Cooling supply system',#739,'supply system for cooling',$);
+#743=IFCCLASSIFICATIONREFERENCE($,'[CT]HD','Heating supply system',#739,'supply system for heating',$);
+#744=IFCCLASSIFICATIONREFERENCE($,'[CT]HE','Combined heating and cooling supply system',#739,'supply system for cooling and heating',$);
+#745=IFCCLASSIFICATIONREFERENCE($,'[CT]HF','Ventilation supply system',#739,'supply system for ventilation',$);
+#746=IFCCLASSIFICATIONREFERENCE($,'[CT]HG','Power supply system',#739,'supply system for electrical energy',$);
+#747=IFCCLASSIFICATIONREFERENCE($,'[CT]HH','Lighting system',#739,'supply system for lighting',$);
+#748=IFCCLASSIFICATIONREFERENCE($,'[CT]HJ','Signal supply system',#739,'supply system for signals',$);
+#749=IFCCLASSIFICATIONREFERENCE($,'[CT]J?','Transporting system',#700,'technical system which brings something from one place to another',$);
+#750=IFCCLASSIFICATIONREFERENCE($,'[CT]JA','Gas distribution system',#749,'transport system for gas',$);
+#751=IFCCLASSIFICATIONREFERENCE($,'[CT]JB','Water distribution system',#749,'transport system for water',$);
+#752=IFCCLASSIFICATIONREFERENCE($,'[CT]JC','Chemical distribution system',#749,'transport system for flammable or other chemical liquids',$);
+#753=IFCCLASSIFICATIONREFERENCE($,'[CT]JD','Liquids outflow system',#749,'transport system for liquid waste',$);
+#754=IFCCLASSIFICATIONREFERENCE($,'[CT]JE','Solids outflow system',#749,'transport system for solid waste',$);
+#755=IFCCLASSIFICATIONREFERENCE($,'[CT]JF','Cooling distribution system',#749,'transport system for cooling',$);
+#756=IFCCLASSIFICATIONREFERENCE($,'[CT]JG','Heating distribution system',#749,'transport system for heating',$);
+#757=IFCCLASSIFICATIONREFERENCE($,'[CT]JH','Combined heat and cooling distribution system',#749,'transport system for cooling and heating',$);
+#758=IFCCLASSIFICATIONREFERENCE($,'[CT]JJ','Air distribution system',#749,'transport system for air',$);
+#759=IFCCLASSIFICATIONREFERENCE($,'[CT]JK','Power distribution system',#749,'transport for electrical energy',$);
+#760=IFCCLASSIFICATIONREFERENCE($,'[CT]JL','Signal distribution system',#749,'transport system for signals',$);
+#761=IFCCLASSIFICATIONREFERENCE($,'[CT]JM','Passenger transportation system',#749,'transport system for persons',$);
+#762=IFCCLASSIFICATIONREFERENCE($,'[CT]JN','Goods transportation system',#749,'transport system for solid goods or packets',$);
+#763=IFCCLASSIFICATIONREFERENCE($,'[CT]K?','Treatment system',#700,'technical system which provides treatment',$);
+#764=IFCCLASSIFICATIONREFERENCE($,'[CT]KA','Solar screening system',#763,'treatment system limiting or excluding solar heat gain',$);
+#765=IFCCLASSIFICATIONREFERENCE($,'[CT]KB','Opening control system',#763,'treatment system for automatic control of openings',$);
+#766=IFCCLASSIFICATIONREFERENCE($,'[CT]KC','Filter system',#763,'treatment system for separating solid particles from liquid or air flow',$);
+#767=IFCCLASSIFICATIONREFERENCE($,'[CT]KD','Separator system',#763,'treatment system for separating substances',$);
+#768=IFCCLASSIFICATIONREFERENCE($,'[CT]KE','Mixing system',#763,'treatment system for adding substances',$);
+#769=IFCCLASSIFICATIONREFERENCE($,'[CT]KF','Pump system',#763,'treatment system for increasing pressure in a flow of liquid',$);
+#770=IFCCLASSIFICATIONREFERENCE($,'[CT]KG','Pressure and expansion system',#763,'treatment system for stabilizing pressure and volume in liquid or in gas flow',$);
+#771=IFCCLASSIFICATIONREFERENCE($,'[CT]KH','Transformer system',#763,'treatment system for transforming electrical energy',$);
+#772=IFCCLASSIFICATIONREFERENCE($,'[CT]KJ','Antenna system',#763,'treatment system for bidirectional transforming between radio waves and electrical signals',$);
+#773=IFCCLASSIFICATIONREFERENCE($,'[CT]KK','Weather station system',#763,'treatment system for transforming weather-related information into electrical signals',$);
+#774=IFCCLASSIFICATIONREFERENCE($,'[CT]KL','Access control system',#763,'treatment system for physical separation of a flow of persons or items',$);
+#775=IFCCLASSIFICATIONREFERENCE($,'[CT]L?','Monitoring and control system',#700,'technical system which monitors and/or controls events and processes',$);
+#776=IFCCLASSIFICATIONREFERENCE($,'[CT]LA','Gas alarm system',#775,'monitoring system which raises an alarm on the occurrence of hazardous gases',$);
+#777=IFCCLASSIFICATIONREFERENCE($,'[CT]LB','Fire alarm system',#775,'monitoring system which raises an alarm in the presence of smoke or fire',$);
+#778=IFCCLASSIFICATIONREFERENCE($,'[CT]LC','Automation system',#775,'monitoring system which automates processes in buildings and process plants in a central unit',$);
+#779=IFCCLASSIFICATIONREFERENCE($,'[CT]LD','Access monitor system',#775,'monitoring system which ensures authorized access for persons to an area or a construction entity',$);
+#780=IFCCLASSIFICATIONREFERENCE($,'[CT]LE','Alarm system',#775,'monitoring system which raises an alarm in the presence of dangerous or undesirable conditions',$);
+#781=IFCCLASSIFICATIONREFERENCE($,'[CT]LF','Video surveillance system',#775,'monitoring system which provides remote visual monitoring',$);
+#782=IFCCLASSIFICATIONREFERENCE($,'[CT]M?','Information presenting system',#700,'technical system which presents information',$);
+#783=IFCCLASSIFICATIONREFERENCE($,'[CT]MA','Warning system',#782,'information presenting system which produces a fixed sound or voice messages to warn of hazardous conditions',$);
+#784=IFCCLASSIFICATIONREFERENCE($,'[CT]MB','Audio-video system',#782,'information presenting system which produces images and/or sounds',$);
+#785=IFCCLASSIFICATIONREFERENCE($,'[CT]MC','Traffic control system',#782,'information presenting system which controls traffic',$);
+#786=IFCCLASSIFICATIONREFERENCE($,'[CT]MD','Time system',#782,'information presenting system for time',$);
+#787=IFCCLASSIFICATIONREFERENCE($,'[CT]ME','Sign system',#782,'information presenting system which provides instruction in a written or symbolic form',$);
+#788=IFCCLASSIFICATIONREFERENCE($,'[CT]P?','Protection system',#700,'self-acting technical system which protects against danger or unwanted conditions',$);
+#789=IFCCLASSIFICATIONREFERENCE($,'[CT]PA','Fire protection system',#788,'protection system by opening or closing doors and windows in the event of a fire',$);
+#790=IFCCLASSIFICATIONREFERENCE($,'[CT]PB','Firefighting system',#788,'protection system against fire by extinguishing',$);
+#791=IFCCLASSIFICATIONREFERENCE($,'[CT]PC','Earthing system',#788,'protection system against dangerous electrical currents by earthing of electrical systems',$);
+#792=IFCCLASSIFICATIONREFERENCE($,'[CT]PD','Lightning protection system',#788,'protection system against lightning by earthing of electrical systems',$);
+#793=IFCCLASSIFICATIONREFERENCE($,'[CT]PE','Cathodic protection system',#788,'protection system for metal constructions against corrosion by corrosion protection',$);
+#794=IFCCLASSIFICATIONREFERENCE($,'[CT]Q?','Storage system',#700,'technical system which stores information, energy or substances',$);
+#795=IFCCLASSIFICATIONREFERENCE($,'[CT]QA','Meter system',#794,'storage system for metered consumption information',$);
+#796=IFCCLASSIFICATIONREFERENCE($,'[CT]QB','Gas and air storage system',#794,'storage system for air and gases',$);
+#797=IFCCLASSIFICATIONREFERENCE($,'[CT]QC','Liquid storage system',#794,'storage system for liquids',$);
+#798=IFCCLASSIFICATIONREFERENCE($,'[CT]QD','Energy storage system',#794,'storage system for energy',$);
+#799=IFCCLASSIFICATIONREFERENCE($,'[CT]R?','Furnishing system',#700,'technical system which fits out space or construction elements in a construction entity',$);
+#800=IFCCLASSIFICATIONREFERENCE($,'[CT]RA','Planting system',#799,'furnishing system in the form of vegetation',$);
+#801=IFCCLASSIFICATIONREFERENCE($,'[CT]RB','Furniture system',#799,'furnishing system in the form of fixed or moveable construction elements',$);
+#802=IFCCLASSIFICATIONREFERENCE($,'[CT]RC','Equipment system',#799,'furnishing system in the form of tools used in a particular situation',$);
+#900=IFCCLASSIFICATIONREFERENCE($,'CO - Construction component',' ',#2,' ',$);
+#901=IFCCLASSIFICATIONREFERENCE($,'[CO]B??','Sensing object',#900,'object for picking up information and providing a representation',$);
+#902=IFCCLASSIFICATIONREFERENCE($,'[CO]BA?','Electric potential sensing object',#901,'sensing object for electric potential',$);
+#903=IFCCLASSIFICATIONREFERENCE($,'[CO]BAA','Voltage transformer',#902,'electric potential sensing object, with scalar output',$);
+#904=IFCCLASSIFICATIONREFERENCE($,'[CO]BAB','Voltage relay',#902,'electric potential sensing object, with Boolean output',$);
+#905=IFCCLASSIFICATIONREFERENCE($,'[CO]BB?','Resistivity sensing object',#901,'sensing object for resistivity or conductivity',$);
+#906=IFCCLASSIFICATIONREFERENCE($,'[CO]BBA','Electric resistivity sensor',#905,'resistivity sensing object for electric current flow, with scalar output',$);
+#907=IFCCLASSIFICATIONREFERENCE($,'[CO]BBB','Electric resistivity detector',#905,'resistivity sensing object for electric current flow, with Boolean output',$);
+#908=IFCCLASSIFICATIONREFERENCE($,'[CO]BBC','Thermal resistivity sensor',#905,'resistivity sensing object for thermal flow, with scalar output',$);
+#909=IFCCLASSIFICATIONREFERENCE($,'[CO]BBD','Thermal resistivity detector',#905,'resistivity sensing object for thermal flow, with Boolean output',$);
+#910=IFCCLASSIFICATIONREFERENCE($,'[CO]BC?','Electric current sensing object',#901,'sensing object for electric current',$);
+#911=IFCCLASSIFICATIONREFERENCE($,'[CO]BCA','Current transformer',#910,'electric current sensing object, with scalar output',$);
+#912=IFCCLASSIFICATIONREFERENCE($,'[CO]BCB','Current relay',#910,'electric current sensing object, with Boolean output',$);
+#913=IFCCLASSIFICATIONREFERENCE($,'[CO]BD?','Density sensing object',#901,'sensing object for density',$);
+#914=IFCCLASSIFICATIONREFERENCE($,'[CO]BDA','Density transmitter',#913,'density sensing object, with scalar output',$);
+#915=IFCCLASSIFICATIONREFERENCE($,'[CO]BDB','Density switch',#913,'density sensing object, with Boolean output',$);
+#916=IFCCLASSIFICATIONREFERENCE($,'[CO]BE?','Field sensing object',#901,'sensing object for field',$);
+#917=IFCCLASSIFICATIONREFERENCE($,'[CO]BEA','Electric field sensor',#916,'field sensing object for electric field, with scalar output',$);
+#918=IFCCLASSIFICATIONREFERENCE($,'[CO]BEB','Electric field detector',#916,'field sensing object for electric field, with Boolean output',$);
+#919=IFCCLASSIFICATIONREFERENCE($,'[CO]BEC','Magnetic field sensor',#916,'field sensing object for magnetic field, with scalar output',$);
+#920=IFCCLASSIFICATIONREFERENCE($,'[CO]BED','Magnetic field detector',#916,'field sensing object for magnetic field, with Boolean output',$);
+#921=IFCCLASSIFICATIONREFERENCE($,'[CO]BF?','Flow sensing object',#901,'sensing object for flow',$);
+#922=IFCCLASSIFICATIONREFERENCE($,'[CO]BFA','Flow transmitter',#921,'flow sensing object, with scalar output',$);
+#923=IFCCLASSIFICATIONREFERENCE($,'[CO]BFB','Flow switch',#921,'flow sensing object, with Boolean output',$);
+#924=IFCCLASSIFICATIONREFERENCE($,'[CO]BG?','Physical dimension sensing object',#901,'sensing object of spatial dimension and/or position',$);
+#925=IFCCLASSIFICATIONREFERENCE($,'[CO]BGA','Position transmitter',#924,'physical dimension sensing object of position, with scalar output',$);
+#926=IFCCLASSIFICATIONREFERENCE($,'[CO]BGB','Position switch',#924,'physical dimension sensing object of position, with Boolean output',$);
+#927=IFCCLASSIFICATIONREFERENCE($,'[CO]BGC','Distance transmitter',#924,'physical dimension sensing object of length, with scalar output',$);
+#928=IFCCLASSIFICATIONREFERENCE($,'[CO]BGD','Distance switch',#924,'physical dimension sensing object of length, with Boolean output',$);
+#929=IFCCLASSIFICATIONREFERENCE($,'[CO]BGE','Angle transmitter',#924,'physical dimension sensing object of angle or direction, with scalar output',$);
+#930=IFCCLASSIFICATIONREFERENCE($,'[CO]BGF','Angle switch',#924,'physical dimension sensing object of angle or direction, with Boolean output',$);
+#931=IFCCLASSIFICATIONREFERENCE($,'[CO]BGG','Object scanner',#924,'physical dimension sensing object of physical object',$);
+#932=IFCCLASSIFICATIONREFERENCE($,'[CO]BH?','Energy sensing object',#901,'sensing object for energy',$);
+#933=IFCCLASSIFICATIONREFERENCE($,'[CO]BHA','Flow energy meter',#932,'energy sensing object of flow with defined energy density',$);
+#934=IFCCLASSIFICATIONREFERENCE($,'[CO]BHB','Thermal energy meter',#932,'energy sensing object of thermal flow with defined heat capacity',$);
+#935=IFCCLASSIFICATIONREFERENCE($,'[CO]BHC','Electric energy meter',#932,'energy sensing object of electricity flow',$);
+#936=IFCCLASSIFICATIONREFERENCE($,'[CO]BJ?','Power sensing object',#901,'sensing object for power',$);
+#937=IFCCLASSIFICATIONREFERENCE($,'[CO]BJA','Power meter',#936,'power sensing object, with scalar output',$);
+#938=IFCCLASSIFICATIONREFERENCE($,'[CO]BJB','Power limit switch',#936,'power sensing object, with Boolean output',$);
+#939=IFCCLASSIFICATIONREFERENCE($,'[CO]BK?','Time sensing object',#901,'sensing object for time',$);
+#940=IFCCLASSIFICATIONREFERENCE($,'[CO]BKA','Time sensor',#939,'time sensing object, with scalar output',$);
+#941=IFCCLASSIFICATIONREFERENCE($,'[CO]BKB','Timer switch',#939,'time sensing object, with Boolean output',$);
+#942=IFCCLASSIFICATIONREFERENCE($,'[CO]BL?','Level sensing object',#901,'sensing object for level',$);
+#943=IFCCLASSIFICATIONREFERENCE($,'[CO]BLA','Level transmitter',#942,'level sensing object, with scalar output',$);
+#944=IFCCLASSIFICATIONREFERENCE($,'[CO]BLB','Level switch',#942,'level sensing object, with Boolean output',$);
+#945=IFCCLASSIFICATIONREFERENCE($,'[CO]BM?','Humidity sensing object',#901,'sensing object for humidity',$);
+#946=IFCCLASSIFICATIONREFERENCE($,'[CO]BMA','Humidity transmitter',#945,'humidity sensing object, with scalar output',$);
+#947=IFCCLASSIFICATIONREFERENCE($,'[CO]BMB','Humidity switch',#945,'humidity sensing object, with Boolean output',$);
+#948=IFCCLASSIFICATIONREFERENCE($,'[CO]BP?','Pressure sensing object',#901,'sensing object for pressure',$);
+#949=IFCCLASSIFICATIONREFERENCE($,'[CO]BPA','Absolute pressure transmitter',#948,'pressure sensing object utilizing one port for absolute pressure sensing, with scalar output',$);
+#950=IFCCLASSIFICATIONREFERENCE($,'[CO]BPB','Absolute Pressure Switch',#948,'pressure sensing object utilizing one port for absolute pressure sensing, with Boolean output',$);
+#951=IFCCLASSIFICATIONREFERENCE($,'[CO]BPC','Differential pressure transmitter',#948,'pressure sensing object utilizing two ports for differential pressure sensing, with scalar output',$);
+#952=IFCCLASSIFICATIONREFERENCE($,'[CO]BPD','Differential pressure switch',#948,'pressure sensing object utilizing two ports for differential pressure sensing, with Boolean output',$);
+#953=IFCCLASSIFICATIONREFERENCE($,'[CO]BQ?','Concentration sensing object',#901,'sensing object for substance concentration',$);
+#954=IFCCLASSIFICATIONREFERENCE($,'[CO]BQA','Gas sensor',#953,'concentration sensing object of a gas, with scalar output',$);
+#955=IFCCLASSIFICATIONREFERENCE($,'[CO]BQB','Gas detector',#953,'concentration sensing object of a gas, with Boolean output',$);
+#956=IFCCLASSIFICATIONREFERENCE($,'[CO]BQC','Liquid sensor',#953,'concentration sensing object of liquid, with scalar output',$);
+#957=IFCCLASSIFICATIONREFERENCE($,'[CO]BQD','Liquid detector',#953,'concentration sensing object of liquid, with Boolean output',$);
+#958=IFCCLASSIFICATIONREFERENCE($,'[CO]BQE','Solid matter sensor',#953,'concentration sensing object of solid matter, with scalar output',$);
+#959=IFCCLASSIFICATIONREFERENCE($,'[CO]BQF','Solid matter detector',#953,'concentration sensing object of solid matter, with Boolean output',$);
+#960=IFCCLASSIFICATIONREFERENCE($,'[CO]BR?','Radiation sensing object',#901,'sensing object for radiation',$);
+#961=IFCCLASSIFICATIONREFERENCE($,'[CO]BRA','Light sensor',#960,'radiation sensing object of visible electromagnetic waves, with scalar output',$);
+#962=IFCCLASSIFICATIONREFERENCE($,'[CO]BRB','Light detector',#960,'radiation sensing object of visible electromagnetic waves, with Boolean output',$);
+#963=IFCCLASSIFICATIONREFERENCE($,'[CO]BRC','Electromagnetic wave sensor',#960,'radiation sensing object for non-visible electromagnetic waves, with scalar output',$);
+#964=IFCCLASSIFICATIONREFERENCE($,'[CO]BRD','Electromagnetic wave detector',#960,'radiation sensing object for non-visible electromagnetic waves, with Boolean output',$);
+#965=IFCCLASSIFICATIONREFERENCE($,'[CO]BRE','Radiation counter',#960,'radiation sensing object of nuclear particles, with scalar output',$);
+#966=IFCCLASSIFICATIONREFERENCE($,'[CO]BRF','Radiation detector',#960,'radiation sensing object of nuclear particles, with Boolean output',$);
+#967=IFCCLASSIFICATIONREFERENCE($,'[CO]BS?','Time-rating object',#901,'sensing object for time-rate',$);
+#968=IFCCLASSIFICATIONREFERENCE($,'[CO]BSA','Cycle rate transmitter',#967,'time-rating object of non-rotational cycles, with scalar output',$);
+#969=IFCCLASSIFICATIONREFERENCE($,'[CO]BSB','Cycle rate detecting switch',#967,'time-rating object of non-rotational cycles, with Boolean output',$);
+#970=IFCCLASSIFICATIONREFERENCE($,'[CO]BSC','Speed sensing transmitter',#967,'time-rating object of distance, with scalar output',$);
+#971=IFCCLASSIFICATIONREFERENCE($,'[CO]BSD','Speed detecting switch',#967,'time-rating object of distance, with Boolean output',$);
+#972=IFCCLASSIFICATIONREFERENCE($,'[CO]BSE','Tachometer',#967,'time-rating object of rotation, with scalar output',$);
+#973=IFCCLASSIFICATIONREFERENCE($,'[CO]BSF','Rotation speed relay',#967,'time-rating object of rotation, with Boolean output',$);
+#974=IFCCLASSIFICATIONREFERENCE($,'[CO]BSG','Accelerometer transmitter',#967,'time-rating object of speed, with scalar output',$);
+#975=IFCCLASSIFICATIONREFERENCE($,'[CO]BSH','Accelerometer relay',#967,'time-rating object of speed, with Boolean output',$);
+#976=IFCCLASSIFICATIONREFERENCE($,'[CO]BT?','Temperature sensing object',#901,'sensing object for temperature',$);
+#977=IFCCLASSIFICATIONREFERENCE($,'[CO]BTA','Temperature transmitter',#976,'temperature sensing object, with scalar output',$);
+#978=IFCCLASSIFICATIONREFERENCE($,'[CO]BTB','Temperature switch',#976,'temperature sensing object, with Boolean output',$);
+#979=IFCCLASSIFICATIONREFERENCE($,'[CO]BU?','Multi-quantity sensing object',#901,'sensing object for multiple quantities',$);
+#980=IFCCLASSIFICATIONREFERENCE($,'[CO]BUA','Multi-sensor',#979,'multi-quantity sensing object, with scalar output(s)',$);
+#981=IFCCLASSIFICATIONREFERENCE($,'[CO]BUB','Multi-detector',#979,'multi-quantity sensing object, with Boolean output(s)',$);
+#982=IFCCLASSIFICATIONREFERENCE($,'[CO]BW?','Force sensing object',#901,'sensing object for weight, force or torque',$);
+#983=IFCCLASSIFICATIONREFERENCE($,'[CO]BWA','Weight transmitter',#982,'force sensing object of weight, with scalar output',$);
+#984=IFCCLASSIFICATIONREFERENCE($,'[CO]BWB','Weight detecting switch',#982,'force sensing object of weight, with Boolean output',$);
+#985=IFCCLASSIFICATIONREFERENCE($,'[CO]BWC','Force value transmitter',#982,'force sensing object of force, with scalar output',$);
+#986=IFCCLASSIFICATIONREFERENCE($,'[CO]BWD','Force detecting switch',#982,'force sensing object of force, with Boolean output',$);
+#987=IFCCLASSIFICATIONREFERENCE($,'[CO]BWE','Torque value transmitter',#982,'force sensing object of torque, with scalar output',$);
+#988=IFCCLASSIFICATIONREFERENCE($,'[CO]BWF','Torque switch',#982,'force sensing object of torque, with Boolean output',$);
+#989=IFCCLASSIFICATIONREFERENCE($,'[CO]BX?','Audio-visual sensing object',#901,'sensing object of sound and/or visual appearance',$);
+#990=IFCCLASSIFICATIONREFERENCE($,'[CO]BXA','Acoustic sensor',#989,'audio-visual sensing object of sound, with scalar output',$);
+#991=IFCCLASSIFICATIONREFERENCE($,'[CO]BXB','Acoustic detector',#989,'audio-visual sensing object of sound, with Boolean output',$);
+#992=IFCCLASSIFICATIONREFERENCE($,'[CO]BXC','Image sensor',#989,'audio-visual sensing object of visual appearance, with scalar output',$);
+#993=IFCCLASSIFICATIONREFERENCE($,'[CO]BXD','Image detector',#989,'audio-visual sensing object of visual appearance, with Boolean output',$);
+#994=IFCCLASSIFICATIONREFERENCE($,'[CO]BY?','Information sensing object',#901,'sensing object of stored information',$);
+#995=IFCCLASSIFICATIONREFERENCE($,'[CO]BYA','Chip reader',#994,'information sensing object by electrical connection',$);
+#996=IFCCLASSIFICATIONREFERENCE($,'[CO]BYB','Electromagnetic reader',#994,'information sensing object by electromagnetic field',$);
+#997=IFCCLASSIFICATIONREFERENCE($,'[CO]BYC','Optical reader',#994,'information sensing object by light',$);
+#998=IFCCLASSIFICATIONREFERENCE($,'[CO]BZ?','Incident sensing object',#901,'sensing object for incidents or amounts',$);
+#999=IFCCLASSIFICATIONREFERENCE($,'[CO]BZA','Counter',#998,'incident sensing object detecting the number of incidents, with scalar output',$);
+#1000=IFCCLASSIFICATIONREFERENCE($,'[CO]BZB','Threshold detector',#998,'incident sensing object detecting an incident with Boolean output',$);
+#1001=IFCCLASSIFICATIONREFERENCE($,'[CO]BZC','Person detector',#998,'incident sensing object detecting the presence of humans, with Boolean output',$);
+#1002=IFCCLASSIFICATIONREFERENCE($,'[CO]BZD','Matter detector',#998,'incident sensing object detecting the presence of matters, with Boolean output',$);
+#1003=IFCCLASSIFICATIONREFERENCE($,'[CO]C??','Storing object',#900,'object for storing for subsequent retrieval',$);
+#1004=IFCCLASSIFICATIONREFERENCE($,'[CO]CA?','Capacitive storing object',#1003,'storing object for electric energy within an electrostatic field',$);
+#1005=IFCCLASSIFICATIONREFERENCE($,'[CO]CAA','Capacitor',#1004,'capacitive storing object for electric charge',$);
+#1006=IFCCLASSIFICATIONREFERENCE($,'[CO]CB?','Inductive storing object',#1003,'storing object for electric energy within an electromagnetic field',$);
+#1007=IFCCLASSIFICATIONREFERENCE($,'[CO]CBA','Inductor',#1006,'inductive storing object for inductive charge',$);
+#1008=IFCCLASSIFICATIONREFERENCE($,'[CO]CC?','Electrochemical storing object',#1003,'storing object for electric energy within an electrochemical substance',$);
+#1009=IFCCLASSIFICATIONREFERENCE($,'[CO]CCA','Rechargeable battery',#1008,'electrochemical storing object by reversible reactions',$);
+#1010=IFCCLASSIFICATIONREFERENCE($,'[CO]CF?','Information storing object',#1003,'storing object for information',$);
+#1011=IFCCLASSIFICATIONREFERENCE($,'[CO]CFA','Information storing media',#1010,'information storing object on a carrier medium',$);
+#1012=IFCCLASSIFICATIONREFERENCE($,'[CO]CL?','Open stationary storing object',#1003,'storing object for matters or persons within a stationary open enclosure',$);
+#1013=IFCCLASSIFICATIONREFERENCE($,'[CO]CLA','Pool',#1012,'open stationary storing object for fluids',$);
+#1014=IFCCLASSIFICATIONREFERENCE($,'[CO]CLB','Shelf',#1012,'open stationary storing object for solid objects',$);
+#1015=IFCCLASSIFICATIONREFERENCE($,'[CO]CLC','Seat',#1012,'open stationary storing object for persons',$);
+#1016=IFCCLASSIFICATIONREFERENCE($,'[CO]CM?','Enclosed stationary storing object',#1003,'storing object for matters within a stationary closed enclosure',$);
+#1017=IFCCLASSIFICATIONREFERENCE($,'[CO]CMA','Tank',#1016,'enclosed stationary storing object for fluids',$);
+#1018=IFCCLASSIFICATIONREFERENCE($,'[CO]CMB','Box',#1016,'enclosed stationary storing object for solid matters',$);
+#1019=IFCCLASSIFICATIONREFERENCE($,'[CO]CN?','Moveable storing object',#1003,'storing object for matters or persons within a moveable closed enclosure',$);
+#1020=IFCCLASSIFICATIONREFERENCE($,'[CO]CNA','Container',#1019,'moveable storing object for solid matters or persons',$);
+#1021=IFCCLASSIFICATIONREFERENCE($,'[CO]CNB','Gas cylinder',#1019,'moveable storing object for gas',$);
+#1022=IFCCLASSIFICATIONREFERENCE($,'[CO]CNC','Drum',#1019,'moveable storing object for liquid',$);
+#1023=IFCCLASSIFICATIONREFERENCE($,'[CO]CP?','Thermal energy storing object',#1003,'storing object of thermal energy',$);
+#1024=IFCCLASSIFICATIONREFERENCE($,'[CO]CPA','Liquid Tank',#1023,'thermal energy storing object of liquid',$);
+#1025=IFCCLASSIFICATIONREFERENCE($,'[CO]CPB','Gas tank',#1023,'thermal energy storing object of gas',$);
+#1026=IFCCLASSIFICATIONREFERENCE($,'[CO]CPC','Matter tank',#1023,'thermal energy storing object of solid matter',$);
+#1027=IFCCLASSIFICATIONREFERENCE($,'[CO]CPD','Crystalline tank',#1023,'thermal energy storing object of state changing material',$);
+#1028=IFCCLASSIFICATIONREFERENCE($,'[CO]CQ?','Mechanical energy storing object',#1003,'storing object of mechanical energy',$);
+#1029=IFCCLASSIFICATIONREFERENCE($,'[CO]CQA','Flywheel',#1028,'mechanical energy storing object in the form of kinetic energy',$);
+#1030=IFCCLASSIFICATIONREFERENCE($,'[CO]CQB','Spring',#1028,'mechanical energy storing object in the form of elastic energy',$);
+#1031=IFCCLASSIFICATIONREFERENCE($,'[CO]CQC','Counterweight',#1028,'mechanical energy storing object in the form of gravitational energy',$);
+#1032=IFCCLASSIFICATIONREFERENCE($,'[CO]E??','Emitting object',#900,'object for emitting',$);
+#1033=IFCCLASSIFICATIONREFERENCE($,'[CO]EA?','Light object',#1032,'emitting object of light',$);
+#1034=IFCCLASSIFICATIONREFERENCE($,'[CO]EAA','Electric lamp',#1033,'light object by electricity',$);
+#1035=IFCCLASSIFICATIONREFERENCE($,'[CO]EAB','Gas lamp',#1033,'light object by gas combustion',$);
+#1036=IFCCLASSIFICATIONREFERENCE($,'[CO]EAC','Liqiud lamp',#1033,'light object by liquid combustion',$);
+#1037=IFCCLASSIFICATIONREFERENCE($,'[CO]EB?','Electric heating object',#1032,'emitting object of heat provided by electric energy',$);
+#1038=IFCCLASSIFICATIONREFERENCE($,'[CO]EBA','Electric boiler',#1037,'electric heating object delivered by fluid',$);
+#1039=IFCCLASSIFICATIONREFERENCE($,'[CO]EBB','Electric heating surface',#1037,'electric heating object delivered by a surface',$);
+#1040=IFCCLASSIFICATIONREFERENCE($,'[CO]EBC','Heating cable',#1037,'electric heating object delivered by a cable',$);
+#1041=IFCCLASSIFICATIONREFERENCE($,'[CO]EBD','Electric hot air blower',#1037,'electric heating object delivered by forced air',$);
+#1042=IFCCLASSIFICATIONREFERENCE($,'[CO]EBE','Arc heating object',#1037,'electric heating object delivered by an electric arc',$);
+#1043=IFCCLASSIFICATIONREFERENCE($,'[CO]EBF','Induction heater',#1037,'electric heating object delivered by a current flow',$);
+#1044=IFCCLASSIFICATIONREFERENCE($,'[CO]EBG','Infrared heater',#1037,'electric heating object delivered by infrared radiation',$);
+#1045=IFCCLASSIFICATIONREFERENCE($,'[CO]EBH','Electric oven',#1037,'electric heating object delivered by surrounding surfaces',$);
+#1046=IFCCLASSIFICATIONREFERENCE($,'[CO]EC?','Electric cooling object',#1032,'emitting object of coldness provided by electric energy',$);
+#1047=IFCCLASSIFICATIONREFERENCE($,'[CO]ECA','Electric cooling surface',#1046,'electric cooling object applying a cooling surface',$);
+#1048=IFCCLASSIFICATIONREFERENCE($,'[CO]ECB','Electric cold air blower',#1046,'electric cooling object applying forced air',$);
+#1049=IFCCLASSIFICATIONREFERENCE($,'[CO]ECC','Compression chiller',#1046,'electric cooling object utilizing phase changing',$);
+#1050=IFCCLASSIFICATIONREFERENCE($,'[CO]EE?','Wireless power object',#1032,'emitting object of wireless power',$);
+#1051=IFCCLASSIFICATIONREFERENCE($,'[CO]EEA','Inductive power antenna',#1050,'wireless power object utilizing inductive coupling',$);
+#1052=IFCCLASSIFICATIONREFERENCE($,'[CO]EEB','Magnetron',#1050,'wireless power object utilizing microwaves',$);
+#1053=IFCCLASSIFICATIONREFERENCE($,'[CO]EEC','X-ray source',#1050,'wireless power object utilizing x-rays',$);
+#1054=IFCCLASSIFICATIONREFERENCE($,'[CO]EED','Gamma ray source',#1050,'wireless power object utilizing gamma rays',$);
+#1055=IFCCLASSIFICATIONREFERENCE($,'[CO]EEE','Capacitive coupler',#1050,'wireless power object utilizing capacitive coupling',$);
+#1056=IFCCLASSIFICATIONREFERENCE($,'[CO]EG?','Thermal energy transfer object',#1032,'emitting object of heat and coldness provided by transfer of thermal energy',$);
+#1057=IFCCLASSIFICATIONREFERENCE($,'[CO]EGA','Heat pump',#1056,'thermal energy transfer object utilizing phase changing',$);
+#1058=IFCCLASSIFICATIONREFERENCE($,'[CO]EGB','Peltier element',#1056,'thermal energy transfer object utilizing Peltier effect',$);
+#1059=IFCCLASSIFICATIONREFERENCE($,'[CO]EGC','Heat exchanger',#1056,'thermal energy transfer object between flows by means of an intermediate material',$);
+#1060=IFCCLASSIFICATIONREFERENCE($,'[CO]EM?','Combustion heating object',#1032,'emitting object of heat provided by combustion',$);
+#1061=IFCCLASSIFICATIONREFERENCE($,'[CO]EMA','Stove',#1060,'combustion heating object for heating of space',$);
+#1062=IFCCLASSIFICATIONREFERENCE($,'[CO]EMB','Combustion boiler',#1060,'combustion heating object for heating of fluid',$);
+#1063=IFCCLASSIFICATIONREFERENCE($,'[CO]EMC','Burner',#1060,'combustion heating object for heating of solid matter',$);
+#1064=IFCCLASSIFICATIONREFERENCE($,'[CO]EP?','Thermal heating object',#1032,'emitting object of heat provided by thermal energy',$);
+#1065=IFCCLASSIFICATIONREFERENCE($,'[CO]EPA','Heating surface',#1064,'thermal heating object delivered by a permeable heated surface',$);
+#1066=IFCCLASSIFICATIONREFERENCE($,'[CO]EPB','Heating tube',#1064,'thermal heating object delivered by heated fluid',$);
+#1067=IFCCLASSIFICATIONREFERENCE($,'[CO]EPC','Heating panel',#1064,'thermal heating object delivered by a non-permeable heated surface',$);
+#1068=IFCCLASSIFICATIONREFERENCE($,'[CO]EPD','Hot air blower',#1064,'thermal heating object delivered by forced air',$);
+#1069=IFCCLASSIFICATIONREFERENCE($,'[CO]EQ?','Thermal cooling object',#1032,'emitting object of coldness provided by thermal energy',$);
+#1070=IFCCLASSIFICATIONREFERENCE($,'[CO]EQA','Cooling surface',#1069,'thermal cooling object applying a permeable cold surface',$);
+#1071=IFCCLASSIFICATIONREFERENCE($,'[CO]EQB','Cooling panel',#1069,'thermal cooling object applying a non-permeable cold surface',$);
+#1072=IFCCLASSIFICATIONREFERENCE($,'[CO]EQC','Chiller',#1069,'thermal cooling object applying forced air',$);
+#1073=IFCCLASSIFICATIONREFERENCE($,'[CO]EQD','Cooler',#1069,'thermal cooling object providing cold fluid',$);
+#1074=IFCCLASSIFICATIONREFERENCE($,'[CO]EQE','Cooling tube',#1069,'thermal cooling object applying chilled liquid in tubes',$);
+#1075=IFCCLASSIFICATIONREFERENCE($,'[CO]ET?','Nuclear powered heating object',#1032,'emitting object of heat provided by nuclear fission',$);
+#1076=IFCCLASSIFICATIONREFERENCE($,'[CO]ETA','Boiling water reactor',#1075,'nuclear powered heating object providing hot water',$);
+#1077=IFCCLASSIFICATIONREFERENCE($,'[CO]ETB','Pressurized water reactor',#1075,'nuclear powered heating object providing steam fluid',$);
+#1078=IFCCLASSIFICATIONREFERENCE($,'[CO]ETC','High-temperature gas-cooled reactor',#1075,'nuclear powered heating object providing hot gas',$);
+#1079=IFCCLASSIFICATIONREFERENCE($,'[CO]EU?','Particle emitting object',#1032,'emitting object of sub-atomic particles',$);
+#1080=IFCCLASSIFICATIONREFERENCE($,'[CO]EUA','Plasma generator',#1079,'particle emitting object being ionized',$);
+#1081=IFCCLASSIFICATIONREFERENCE($,'[CO]EUB','Particle generator',#1079,'particle emitting object being non-ionized',$);
+#1082=IFCCLASSIFICATIONREFERENCE($,'[CO]EV?','Acoustic wave emitting object',#1032,'emitting object of acoustic waves',$);
+#1083=IFCCLASSIFICATIONREFERENCE($,'[CO]EVA','Sonar',#1082,'acoustic wave emitting object utilizing ultrasonic frequencies',$);
+#1084=IFCCLASSIFICATIONREFERENCE($,'[CO]EVB','Anti-noise loudspeaker',#1082,'acoustic wave emitting object utilizing audible frequencies for noise cancellation',$);
+#1085=IFCCLASSIFICATIONREFERENCE($,'[CO]F??','Protecting object',#900,'object for protecting against the effects of dangerous or undesirable conditions',$);
+#1086=IFCCLASSIFICATIONREFERENCE($,'[CO]FA?','Overvoltage protecting object',#1085,'protecting object related to overvoltages',$);
+#1087=IFCCLASSIFICATIONREFERENCE($,'[CO]FAA','Spark gap surge arrester',#1086,'overvoltage protecting object by a spark gap',$);
+#1088=IFCCLASSIFICATIONREFERENCE($,'[CO]FAB','Varistor surge arrester',#1086,'overvoltage protecting object by a varistor',$);
+#1089=IFCCLASSIFICATIONREFERENCE($,'[CO]FAC','Zener diode',#1086,'overvoltage protecting object by a zener diode',$);
+#1090=IFCCLASSIFICATIONREFERENCE($,'[CO]FAD','Surge absorber',#1086,'overvoltage protecting object by surge absorption',$);
+#1091=IFCCLASSIFICATIONREFERENCE($,'[CO]FB?','Earth fault current protecting object',#1085,'protecting object related to earth fault currents',$);
+#1092=IFCCLASSIFICATIONREFERENCE($,'[CO]FBA','Residual current device',#1091,'earth fault current protecting object that monitors an electric network and switches off in the event of earth fault currents',$);
+#1093=IFCCLASSIFICATIONREFERENCE($,'[CO]FBB','Neutral earthing limiter',#1091,'earth fault current protecting object that limits the value of an earth fault current',$);
+#1094=IFCCLASSIFICATIONREFERENCE($,'[CO]FC?','Overcurrent protecting object',#1085,'protecting object related to overcurrents',$);
+#1095=IFCCLASSIFICATIONREFERENCE($,'[CO]FCA','Fuse',#1094,'overcurrent protecting object that by fusing opens the circuit in which it is inserted by breaking the current when this exceeds a given value for a sufficient time',$);
+#1096=IFCCLASSIFICATIONREFERENCE($,'[CO]FCB','Miniature circuit-breaker',#1094,'overcurrent protecting object capable of making, carrying and breaking currents under normal circuit conditions and also making, carrying for a specified time, and automatically breaking currents under specified abnormal circuit conditions (this class is intended for circuit-breakers such as those defined in accordance with IEC 60898 series)',$);
+#1097=IFCCLASSIFICATIONREFERENCE($,'[CO]FCC','Bimetal',#1094,'overcurrent protecting object that opens the circuit in which it is inserted by interrupting the current when this exceeds a given value for a sufficient time',$);
+#1098=IFCCLASSIFICATIONREFERENCE($,'[CO]FE?','Field protecting object',#1085,'protecting object related to electric and/or magnetic fields',$);
+#1099=IFCCLASSIFICATIONREFERENCE($,'[CO]FEA','Electric field shield',#1098,'field protecting object of electric fields',$);
+#1100=IFCCLASSIFICATIONREFERENCE($,'[CO]FEB','Magnetic field shield',#1098,'field protecting object of magnetic fields',$);
+#1101=IFCCLASSIFICATIONREFERENCE($,'[CO]FEC','Electromagnetic field shield',#1098,'field protecting object of electromagnetic fields',$);
+#1102=IFCCLASSIFICATIONREFERENCE($,'[CO]FL?','Pressure protecting object',#1085,'protecting object related to pressure',$);
+#1103=IFCCLASSIFICATIONREFERENCE($,'[CO]FLA','Safety valve',#1102,'pressure protecting object releasing steam or liquid when the pressure exceeds a set limit value',$);
+#1104=IFCCLASSIFICATIONREFERENCE($,'[CO]FLB','Safety damper',#1102,'pressure protecting object opening or closing an air flow when the pressure is above or below a set limit value',$);
+#1105=IFCCLASSIFICATIONREFERENCE($,'[CO]FLC','Vacuum breaker',#1102,'pressure protecting object preventing vacuum',$);
+#1106=IFCCLASSIFICATIONREFERENCE($,'[CO]FLD','Rupture disc',#1102,'pressure protecting object that breaks open irreversibly in the event of excessive pressure difference',$);
+#1107=IFCCLASSIFICATIONREFERENCE($,'[CO]FLE','Expansion tank',#1102,'pressure protecting object provided by a volume expansion',$);
+#1108=IFCCLASSIFICATIONREFERENCE($,'[CO]FM?','Fire protecting object',#1085,'protecting object related to fire',$);
+#1109=IFCCLASSIFICATIONREFERENCE($,'[CO]FMA','Fire damper',#1108,'fire protecting object closing an air flow on detection of fire',$);
+#1110=IFCCLASSIFICATIONREFERENCE($,'[CO]FMB','Flue damper',#1108,'fire protecting object opening or closing an air flow on detection of smoke',$);
+#1111=IFCCLASSIFICATIONREFERENCE($,'[CO]FMC','Fire-and-flue damper',#1108,'fire protecting object closing an air flow on detection of smoke and/or fire',$);
+#1112=IFCCLASSIFICATIONREFERENCE($,'[CO]FMD','Fire extinguisher',#1108,'fire protecting object for manual operation',$);
+#1113=IFCCLASSIFICATIONREFERENCE($,'[CO]FME','Fire blind',#1108,'fire protecting object closing a passage for preventing spread of smoke and/or fire',$);
+#1114=IFCCLASSIFICATIONREFERENCE($,'[CO]FMF','Smoke curtain',#1108,'fire protecting object dividing an area into smoke zones',$);
+#1115=IFCCLASSIFICATIONREFERENCE($,'[CO]FMG','Smoke closure',#1108,'fire protecting object closing a hole for preventing spread of fire',$);
+#1116=IFCCLASSIFICATIONREFERENCE($,'[CO]FMH','Fire insulation',#1108,'fire protecting object by a surface cover',$);
+#1117=IFCCLASSIFICATIONREFERENCE($,'[CO]FMJ','Fire retardant coating',#1108,'fire protecting object by a surface coating',$);
+#1118=IFCCLASSIFICATIONREFERENCE($,'[CO]FMK','Fire impregnation',#1108,'fire protecting object by impregnation',$);
+#1119=IFCCLASSIFICATIONREFERENCE($,'[CO]FN?','Mechanical force protecting object',#1085,'protecting object related to mechanical force',$);
+#1120=IFCCLASSIFICATIONREFERENCE($,'[CO]FNA','Safety clutch',#1119,'mechanical force protecting object providing a disconnection when a torque becomes too high',$);
+#1121=IFCCLASSIFICATIONREFERENCE($,'[CO]FNB','Impact protection',#1119,'mechanical force protecting object absorbing impact',$);
+#1122=IFCCLASSIFICATIONREFERENCE($,'[CO]FQ?','Preventive protecting object',#1085,'protecting object by barrier or obstacle',$);
+#1123=IFCCLASSIFICATIONREFERENCE($,'[CO]FQA','Protective mesh',#1122,'preventive protecting object against objects passing',$);
+#1124=IFCCLASSIFICATIONREFERENCE($,'[CO]FQB','Glide protection',#1122,'preventive protecting object against objects or matter sliding or drifting',$);
+#1125=IFCCLASSIFICATIONREFERENCE($,'[CO]FQC','Protective rod',#1122,'preventive protecting object against falling',$);
+#1126=IFCCLASSIFICATIONREFERENCE($,'[CO]FQD','Protective rail',#1122,'preventive protecting object against falling out or colliding',$);
+#1127=IFCCLASSIFICATIONREFERENCE($,'[CO]FQE','Protective fabric',#1122,'preventive protecting object against physical penetration',$);
+#1128=IFCCLASSIFICATIONREFERENCE($,'[CO]FQF','Glare protection',#1122,'preventive protecting object against glare',$);
+#1129=IFCCLASSIFICATIONREFERENCE($,'[CO]FQG','Splatter protection',#1122,'preventive protecting object against splatter',$);
+#1130=IFCCLASSIFICATIONREFERENCE($,'[CO]FQH','Break protection',#1122,'preventive protecting object against manual breakage',$);
+#1131=IFCCLASSIFICATIONREFERENCE($,'[CO]FQJ','Lock hatch cover',#1122,'preventive protecting object against manual unlocking',$);
+#1132=IFCCLASSIFICATIONREFERENCE($,'[CO]FR?','Wear protection object',#1085,'protecting object related to material erosion',$);
+#1133=IFCCLASSIFICATIONREFERENCE($,'[CO]FRA','Lubrication object',#1132,'wear protection object providing liquid material between objects',$);
+#1134=IFCCLASSIFICATIONREFERENCE($,'[CO]FRB','Wear pad',#1132,'wear protection object providing a solid separating material between objects',$);
+#1135=IFCCLASSIFICATIONREFERENCE($,'[CO]FS?','Environment protecting object',#1085,'protecting object related to the local environment',$);
+#1136=IFCCLASSIFICATIONREFERENCE($,'[CO]FSA','Plaster',#1135,'environment protecting object in the form of a solidified paste layer covering another object',$);
+#1137=IFCCLASSIFICATIONREFERENCE($,'[CO]FSB','Paint',#1135,'environment protecting object in the form of a hardened liquid film on the surface of another object',$);
+#1138=IFCCLASSIFICATIONREFERENCE($,'[CO]FSC','Impregnation',#1135,'environment protecting object in the form of an applied or induced liquid making the object resistant to externally induced decay or destruction',$);
+#1139=IFCCLASSIFICATIONREFERENCE($,'[CO]FSD','Shelter',#1135,'environment protecting object providing shelter against climatic effects',$);
+#1140=IFCCLASSIFICATIONREFERENCE($,'[CO]FSE','Seismic vibration control device',#1135,'environment protecting object in the form of vibration control',$);
+#1141=IFCCLASSIFICATIONREFERENCE($,'[CO]FSF','Corrosion protection',#1135,'environment protection object in the form of oxidation of metal',$);
+#1142=IFCCLASSIFICATIONREFERENCE($,'[CO]FSG','Protective seal',#1135,'environment protecting object in the form of a seal on the surface of another object',$);
+#1143=IFCCLASSIFICATIONREFERENCE($,'[CO]FSH','Material separating layer',#1135,'environment protecting object in the form of a capillary action breaking layer',$);
+#1144=IFCCLASSIFICATIONREFERENCE($,'[CO]G??','Generating object',#900,'object for providing a controllable flow',$);
+#1145=IFCCLASSIFICATIONREFERENCE($,'[CO]GA?','Mechanical to electrical energy generating object',#1144,'generating object of electric energy powered by mechanical energy',$);
+#1146=IFCCLASSIFICATIONREFERENCE($,'[CO]GAA','AC generator',#1145,'mechanical to electrical energy generating object of alternating current',$);
+#1147=IFCCLASSIFICATIONREFERENCE($,'[CO]GAB','DC generator',#1145,'mechanical to electrical energy generating object of direct current',$);
+#1148=IFCCLASSIFICATIONREFERENCE($,'[CO]GB?','Chemical to electrical energy generating object',#1144,'generating object of electric energy powered by chemical reaction',$);
+#1149=IFCCLASSIFICATIONREFERENCE($,'[CO]GBA','Electric battery',#1148,'Chemical to electrical energy generating object from a non-renewable source',$);
+#1150=IFCCLASSIFICATIONREFERENCE($,'[CO]GBB','Fuel cell',#1148,'Chemical to electrical energy generating object continuously fed by fuel and oxidant',$);
+#1151=IFCCLASSIFICATIONREFERENCE($,'[CO]GC?','Solar to electrical energy generating object',#1144,'generating object of electric energy from solar energy',$);
+#1152=IFCCLASSIFICATIONREFERENCE($,'[CO]GCA','Crystalline photovoltaic module',#1151,'Solar to electrical energy generating object utilizing wafer based semiconducting material',$);
+#1153=IFCCLASSIFICATIONREFERENCE($,'[CO]GCB','Thin film photovoltaic module',#1151,'Solar to electrical energy generating object utilizing thin film material',$);
+#1154=IFCCLASSIFICATIONREFERENCE($,'[CO]GF?','Signal generating object',#1144,'generating object of signals for carrier of information',$);
+#1155=IFCCLASSIFICATIONREFERENCE($,'[CO]GFA','Electromagnetic wave transmitter',#1154,'signal generating object by electromagnetic waves',$);
+#1156=IFCCLASSIFICATIONREFERENCE($,'[CO]GFB','Signal generator',#1154,'signal generating object by wires',$);
+#1157=IFCCLASSIFICATIONREFERENCE($,'[CO]GL?','Continuous transfer object',#1144,'generating object continuously of items',$);
+#1158=IFCCLASSIFICATIONREFERENCE($,'[CO]GLA','Belt conveyer',#1157,'continuous transfer object on a belt',$);
+#1159=IFCCLASSIFICATIONREFERENCE($,'[CO]GLB','Chain conveyer',#1157,'continuous transfer object on a chain',$);
+#1160=IFCCLASSIFICATIONREFERENCE($,'[CO]GLC','Roller conveyer',#1157,'continuous transfer object on rollers',$);
+#1161=IFCCLASSIFICATIONREFERENCE($,'[CO]GLD','Screw conveyer',#1157,'continuous transfer object propelled by a screw',$);
+#1162=IFCCLASSIFICATIONREFERENCE($,'[CO]GLE','Escalator',#1157,'continuous transfer object consisting of a chain of steps',$);
+#1163=IFCCLASSIFICATIONREFERENCE($,'[CO]GLF','Paternoster',#1157,'continuous transfer object consisting of chain of containers',$);
+#1164=IFCCLASSIFICATIONREFERENCE($,'[CO]GLG','Vibrating conveyor',#1157,'continuous transfer object on a vibrating surface',$);
+#1165=IFCCLASSIFICATIONREFERENCE($,'[CO]GM?','Discontinuous transfer object',#1144,'generating object discontinuously of items',$);
+#1166=IFCCLASSIFICATIONREFERENCE($,'[CO]GMA','Wagon',#1165,'discontinuous transfer object horizontally',$);
+#1167=IFCCLASSIFICATIONREFERENCE($,'[CO]GMB','Elevator',#1165,'discontinuous transfer object vertically',$);
+#1168=IFCCLASSIFICATIONREFERENCE($,'[CO]GMC','Crane',#1165,'discontinuous transfer object lifting and moving objects vertically and/or horizontally',$);
+#1169=IFCCLASSIFICATIONREFERENCE($,'[CO]GMD','Palletizer',#1165,'discontinuous transfer object placing objects together',$);
+#1170=IFCCLASSIFICATIONREFERENCE($,'[CO]GME','Turntable',#1165,'discontinuous transfer object turning a conveyed flow',$);
+#1171=IFCCLASSIFICATIONREFERENCE($,'[CO]GMF','Vibrating feeder',#1165,'discontinuous transfer object moving objects in a linear flow by vibrating',$);
+#1172=IFCCLASSIFICATIONREFERENCE($,'[CO]GP?','Liquid flow generating object',#1144,'generating object of liquid',$);
+#1173=IFCCLASSIFICATIONREFERENCE($,'[CO]GPA','Positive displacement pump',#1172,'liquid flow generating object by forcing trapped liquid to the discharge side',$);
+#1174=IFCCLASSIFICATIONREFERENCE($,'[CO]GPB','Liquid velocity pump',#1172,'liquid flow generating object by increasing the flow velocity through mechanical motion',$);
+#1175=IFCCLASSIFICATIONREFERENCE($,'[CO]GPC','Hydraulic ram pump',#1172,'liquid flow generating object by transferring the kinetic energy of an input flow to an output flow',$);
+#1176=IFCCLASSIFICATIONREFERENCE($,'[CO]GPD','Lubricator',#1172,'liquid flow generating object by converting potential energy to kinetic energy by gravity',$);
+#1177=IFCCLASSIFICATIONREFERENCE($,'[CO]GQ?','Gaseous flow generating object',#1144,'generating object of gas',$);
+#1178=IFCCLASSIFICATIONREFERENCE($,'[CO]GQA','Gas compressor',#1177,'gaseous flow generating object by forcing trapped gas to the discharge side',$);
+#1179=IFCCLASSIFICATIONREFERENCE($,'[CO]GQB','Mechanical fan',#1177,'gaseous flow generating object by increasing the flow velocity through mechanical motion',$);
+#1180=IFCCLASSIFICATIONREFERENCE($,'[CO]GQC','Gas ejector',#1177,'gaseous flow generating object by transferring the kinetic energy of an input flow to an output flow',$);
+#1181=IFCCLASSIFICATIONREFERENCE($,'[CO]GR?','Solar to thermal energy generating object',#1144,'generating object of thermal energy from solar energy',$);
+#1182=IFCCLASSIFICATIONREFERENCE($,'[CO]GRA','Solar panel',#1181,'solar to thermal energy generating object into a fluid thermal flow',$);
+#1183=IFCCLASSIFICATIONREFERENCE($,'[CO]GRB','Solar tower',#1181,'solar to thermal energy generating object generating a fluid flow',$);
+#1184=IFCCLASSIFICATIONREFERENCE($,'[CO]H??','Matter processing object',#900,'object for treating matter',$);
+#1185=IFCCLASSIFICATIONREFERENCE($,'[CO]HJ?','Primary forming object',#1184,'matter processing object by primary forming',$);
+#1186=IFCCLASSIFICATIONREFERENCE($,'[CO]HJA','Casting tool',#1185,'primary forming object from liquid initial material state',$);
+#1187=IFCCLASSIFICATIONREFERENCE($,'[CO]HJB','Press',#1185,'primary forming object from solid matter initial material state',$);
+#1188=IFCCLASSIFICATIONREFERENCE($,'[CO]HK?','Surface treatment object',#1184,'matter processing object for surface treatment',$);
+#1189=IFCCLASSIFICATIONREFERENCE($,'[CO]HKA','Surface preparation machine',#1188,'surface treatment object by preparation',$);
+#1190=IFCCLASSIFICATIONREFERENCE($,'[CO]HKB','Surface modification machine',#1188,'surface treatment object by modification',$);
+#1191=IFCCLASSIFICATIONREFERENCE($,'[CO]HKC','Surface coating machine',#1188,'surface treatment object by coating',$);
+#1192=IFCCLASSIFICATIONREFERENCE($,'[CO]HL?','Assembling object',#1184,'matter processing object by joining or assembling',$);
+#1193=IFCCLASSIFICATIONREFERENCE($,'[CO]HLA','Assembly robot',#1192,'assembling object by physical assembly',$);
+#1194=IFCCLASSIFICATIONREFERENCE($,'[CO]HLB','Mechanical joining machine',#1192,'assembling object by mechanical joining',$);
+#1195=IFCCLASSIFICATIONREFERENCE($,'[CO]HLC','Thermal welding machine',#1192,'assembling object by thermal joining',$);
+#1196=IFCCLASSIFICATIONREFERENCE($,'[CO]HLD','Chemical joining machine',#1192,'assembling object by chemical joining',$);
+#1197=IFCCLASSIFICATIONREFERENCE($,'[CO]HM?','Force separating object',#1184,'matter processing object by separating mixed substances by force',$);
+#1198=IFCCLASSIFICATIONREFERENCE($,'[CO]HMA','Settling tank',#1197,'force separating object of particles by gravity',$);
+#1199=IFCCLASSIFICATIONREFERENCE($,'[CO]HMB','Centrifuge',#1197,'force separating object of substances by rotation in a rotating chamber',$);
+#1200=IFCCLASSIFICATIONREFERENCE($,'[CO]HMC','Cyclone',#1197,'force separating object of substances by rotation in a fixed chamber',$);
+#1201=IFCCLASSIFICATIONREFERENCE($,'[CO]HP?','Thermal separating object',#1184,'matter processing object by thermally separating mixed substances',$);
+#1202=IFCCLASSIFICATIONREFERENCE($,'[CO]HPA','Dryer',#1201,'thermal separating object removing moisture',$);
+#1203=IFCCLASSIFICATIONREFERENCE($,'[CO]HPB','Distillation column',#1201,'thermal separating object generating the vaporized aggregation of a liquid',$);
+#1204=IFCCLASSIFICATIONREFERENCE($,'[CO]HQ?','Mechanical separating object',#1184,'matter processing object by mechanically separating mixed substances',$);
+#1205=IFCCLASSIFICATIONREFERENCE($,'[CO]HQA','Skimmer',#1204,'mechanical separating object by removing substance from the surface of a liquid',$);
+#1206=IFCCLASSIFICATIONREFERENCE($,'[CO]HQB','Separation grate',#1204,'mechanical separating object of particles based on their size and/or shape',$);
+#1207=IFCCLASSIFICATIONREFERENCE($,'[CO]HR?','Electric or magnetic separating object',#1184,'matter processing object by electromagnetically separating mixed substances',$);
+#1208=IFCCLASSIFICATIONREFERENCE($,'[CO]HRA','Electrostatic separator',#1207,'electric or magnetic separating object utilising an electric field',$);
+#1209=IFCCLASSIFICATIONREFERENCE($,'[CO]HRB','Magnetic separator',#1207,'electric or magnetic separating object separating object utilising a magnetic field',$);
+#1210=IFCCLASSIFICATIONREFERENCE($,'[CO]HS?','Chemical separating object',#1184,'matter processing object by chemically separating mixed substances',$);
+#1211=IFCCLASSIFICATIONREFERENCE($,'[CO]HSA','Ion exchanger',#1210,'chemical separating object utilising exchange of ions',$);
+#1212=IFCCLASSIFICATIONREFERENCE($,'[CO]HSB','Absorber',#1210,'chemical separating object utilising absorption',$);
+#1213=IFCCLASSIFICATIONREFERENCE($,'[CO]HSC','Adsorber',#1210,'chemical separating object utilising adsorption',$);
+#1214=IFCCLASSIFICATIONREFERENCE($,'[CO]HU?','Grinding and crushing object',#1184,'matter processing object providing smaller forms of solid material',$);
+#1215=IFCCLASSIFICATIONREFERENCE($,'[CO]HUA','Cutter',#1214,'grinding and crushing object for soft, medium-hard, fibrous and tough matter',$);
+#1216=IFCCLASSIFICATIONREFERENCE($,'[CO]HUB','Mill',#1214,'grinding and crushing object for machining parts into precise sizes and shapes',$);
+#1217=IFCCLASSIFICATIONREFERENCE($,'[CO]HUC','Crusher',#1214,'grinding and crushing object of rock kind',$);
+#1218=IFCCLASSIFICATIONREFERENCE($,'[CO]HV?','Agglomerating object',#1184,'matter processing object providing larger forms of substances',$);
+#1219=IFCCLASSIFICATIONREFERENCE($,'[CO]HVA','Pelletizer',#1218,'agglomerating object compressing or moulding a material into the shape of a pellet',$);
+#1220=IFCCLASSIFICATIONREFERENCE($,'[CO]HVB','Flocculator',#1218,'agglomerating object enabling (a process wherein) colloids to come out of suspension in the form of floc or flake',$);
+#1221=IFCCLASSIFICATIONREFERENCE($,'[CO]HW?','Mixing object',#1184,'matter processing object mixing different substances',$);
+#1222=IFCCLASSIFICATIONREFERENCE($,'[CO]HWA','Mixer',#1221,'mixing object that provides a new property substance from different substances',$);
+#1223=IFCCLASSIFICATIONREFERENCE($,'[CO]HWB','Humidifier',#1221,'mixing object providing humid gaseous substance',$);
+#1224=IFCCLASSIFICATIONREFERENCE($,'[CO]HX?','Reacting object',#1184,'matter processing object providing new material by chemical or biological reaction',$);
+#1225=IFCCLASSIFICATIONREFERENCE($,'[CO]HXA','Chemical reactor',#1224,'reacting object enabling and controlling a chemical reaction',$);
+#1226=IFCCLASSIFICATIONREFERENCE($,'[CO]HXB','Fermenter',#1224,'reacting object enabling and controlling a biological reaction',$);
+#1227=IFCCLASSIFICATIONREFERENCE($,'[CO]K??','Information processing object',#900,'object for treating input signals and providing an appropriate output',$);
+#1228=IFCCLASSIFICATIONREFERENCE($,'[CO]KE?','Electric signal processing object',#1227,'information processing object for processing electric signals',$);
+#1229=IFCCLASSIFICATIONREFERENCE($,'[CO]KEA','Computer',#1228,'electric signal processing object with human intervention',$);
+#1230=IFCCLASSIFICATIONREFERENCE($,'[CO]KEB','Control unit',#1228,'electric signal processing object without human intervention for control of devices',$);
+#1231=IFCCLASSIFICATIONREFERENCE($,'[CO]KEC','Communication device',#1228,'electric signal processing object without human intervention providing input/output to devices',$);
+#1232=IFCCLASSIFICATIONREFERENCE($,'[CO]KED','Router',#1228,'electric signal processing object without human intervention forwarding signals between data networks',$);
+#1233=IFCCLASSIFICATIONREFERENCE($,'[CO]KEE','Card writer',#1228,'electric signal processing object without human intervention saving the information provided by the input on a storage device',$);
+#1234=IFCCLASSIFICATIONREFERENCE($,'[CO]KF?','Electric signal relaying object',#1227,'information processing object for relaying electric signals',$);
+#1235=IFCCLASSIFICATIONREFERENCE($,'[CO]KFA','Relay',#1234,'electric signal relaying object by forwarding without delay',$);
+#1236=IFCCLASSIFICATIONREFERENCE($,'[CO]KFB','Time relay',#1234,'electric signal relaying object by forwarding with delay',$);
+#1237=IFCCLASSIFICATIONREFERENCE($,'[CO]KFC','Repeater',#1234,'electric signal relaying object by repeating the input signal as an improved output signal',$);
+#1238=IFCCLASSIFICATIONREFERENCE($,'[CO]KFD','Electric network bridge',#1234,'electric signal relaying object by bridging multiple data network segments',$);
+#1239=IFCCLASSIFICATIONREFERENCE($,'[CO]KFE','Electric network switch',#1234,'electric signal relaying object by forwarding the input to specific devices within a data network',$);
+#1240=IFCCLASSIFICATIONREFERENCE($,'[CO]KG?','Optical signalling object',#1227,'information processing object of optical signals',$);
+#1241=IFCCLASSIFICATIONREFERENCE($,'[CO]KGA','Optical network switch',#1240,'optical signalling object actively by forwarding the input to specific devices within an optical network',$);
+#1242=IFCCLASSIFICATIONREFERENCE($,'[CO]KGB','Optical router',#1240,'optical signalling object forwarding data packages between computer networks',$);
+#1243=IFCCLASSIFICATIONREFERENCE($,'[CO]KGC','Optical repeater',#1240,'optical signalling object by repeating the input signal as an improved output signal',$);
+#1244=IFCCLASSIFICATIONREFERENCE($,'[CO]KH?','Fluid signalling object',#1227,'information processing object of fluid signals',$);
+#1245=IFCCLASSIFICATIONREFERENCE($,'[CO]KHA','Fluid controller',#1244,'fluid signalling object for feedback control',$);
+#1246=IFCCLASSIFICATIONREFERENCE($,'[CO]KHB','Pilot valve',#1244,'fluid signalling object relaying signals',$);
+#1247=IFCCLASSIFICATIONREFERENCE($,'[CO]KJ?','Mechanical signalling object',#1227,'information processing object of mechanical signals',$);
+#1248=IFCCLASSIFICATIONREFERENCE($,'[CO]KJA','Mechanical regulator',#1247,'mechanical signalling object adjusting an air or fluid flow',$);
+#1249=IFCCLASSIFICATIONREFERENCE($,'[CO]KJB','Locking cylinder',#1247,'mechanical signalling object for locking/unlocking of a locking mechanism',$);
+#1250=IFCCLASSIFICATIONREFERENCE($,'[CO]KJC','Key coded device',#1247,'mechanical signalling object providing a binary state of a mechanical device',$);
+#1251=IFCCLASSIFICATIONREFERENCE($,'[CO]KZ?','Multiple kind signalling object',#1227,'information processing object of signals of multiple kinds',$);
+#1252=IFCCLASSIFICATIONREFERENCE($,'[CO]KZA','Multiple kind switch',#1251,'multiple kind signalling object forwarding the input to specific devices',$);
+#1253=IFCCLASSIFICATIONREFERENCE($,'[CO]M??','Driving object',#900,'object for providing mechanical movement or force',$);
+#1254=IFCCLASSIFICATIONREFERENCE($,'[CO]MA?','Electromagnetic rotational driving object',#1253,'driving object in the form of rotational torque, powered by electromagnetic force',$);
+#1255=IFCCLASSIFICATIONREFERENCE($,'[CO]MAA','Electric motor',#1254,'electromagnetic rotational driving object providing a continuous movement',$);
+#1256=IFCCLASSIFICATIONREFERENCE($,'[CO]MAB','Stepper motor',#1254,'electromagnetic rotational driving object providing discrete steps',$);
+#1257=IFCCLASSIFICATIONREFERENCE($,'[CO]MB?','Electromagnetic linear driving object',#1253,'driving object in the form of linear force, powered by electromagnetic force',$);
+#1258=IFCCLASSIFICATIONREFERENCE($,'[CO]MBA','Linear motor',#1257,'electromagnetic linear driving object providing a continuous movement',$);
+#1259=IFCCLASSIFICATIONREFERENCE($,'[CO]MBB','Electromagnet',#1257,'electromagnetic linear driving object providing discrete steps',$);
+#1260=IFCCLASSIFICATIONREFERENCE($,'[CO]MC?','Magnetic force driving object',#1253,'driving object powered by magnetic force',$);
+#1261=IFCCLASSIFICATIONREFERENCE($,'[CO]MCA','Permanent magnet',#1260,'magnetic force driving object by permanent magnet',$);
+#1262=IFCCLASSIFICATIONREFERENCE($,'[CO]MD?','Piezoelectric driving object',#1253,'driving object powered by piezoelectric force',$);
+#1263=IFCCLASSIFICATIONREFERENCE($,'[CO]MDA','Piezoelectric motor',#1262,'piezoelectric driving object by piezoelectric crystal',$);
+#1264=IFCCLASSIFICATIONREFERENCE($,'[CO]ML?','Mechanical energy driving object',#1253,'driving object powered by mechanical energy',$);
+#1265=IFCCLASSIFICATIONREFERENCE($,'[CO]MLA','Gravity energy drive',#1264,'mechanical energy driving object releasing gravitational energy',$);
+#1266=IFCCLASSIFICATIONREFERENCE($,'[CO]MLB','Elastic energy drive',#1264,'mechanical energy driving object releasing elastic energy',$);
+#1267=IFCCLASSIFICATIONREFERENCE($,'[CO]MLC','Wind turbine',#1264,'mechanical energy driving object in the form of kinetic energy in blowing air',$);
+#1268=IFCCLASSIFICATIONREFERENCE($,'[CO]MLD','Water turbine',#1264,'mechanical energy driving object in the form of kinetic energy in streaming water',$);
+#1269=IFCCLASSIFICATIONREFERENCE($,'[CO]MLE','Gas turbine',#1264,'mechanical energy driving object in the form of kinetic energy and reaction force in streaming and/or expanding gases',$);
+#1270=IFCCLASSIFICATIONREFERENCE($,'[CO]MM?','Fluid powered driving object',#1253,'driving object powered by fluid displacement or pressure',$);
+#1271=IFCCLASSIFICATIONREFERENCE($,'[CO]MMA','Hydraulic cylinder',#1270,'fluid powered driving object providing movement corresponding to a liquid volume',$);
+#1272=IFCCLASSIFICATIONREFERENCE($,'[CO]MMB','Pneumatic cylinder',#1270,'fluid powered driving object providing movement to end positions depending on the gaseous pressure',$);
+#1273=IFCCLASSIFICATIONREFERENCE($,'[CO]MMC','Hydraulic motor',#1270,'fluid powered driving object providing rotation by a flowing liquid',$);
+#1274=IFCCLASSIFICATIONREFERENCE($,'[CO]MMD','Pneumatic motor',#1270,'fluid powered driving object providing rotation by a pressurized gas',$);
+#1275=IFCCLASSIFICATIONREFERENCE($,'[CO]MS?','Combustion engine',#1253,'driving object powered by combustion of fuels',$);
+#1276=IFCCLASSIFICATIONREFERENCE($,'[CO]MSA','Otto cycle engine',#1275,'combustion engine in cylinders with pistons powered by spark ignited combustion',$);
+#1277=IFCCLASSIFICATIONREFERENCE($,'[CO]MSB','Diesel cycle engine',#1275,'combustion engine in cylinders with pistons powered by compressed ignited combustion',$);
+#1278=IFCCLASSIFICATIONREFERENCE($,'[CO]MSC','Wankel engine',#1275,'combustion engine in circular compartment with eccentric rotary piston powered by spark ignited combustion',$);
+#1279=IFCCLASSIFICATIONREFERENCE($,'[CO]MT?','Heat engine',#1253,'driving object powered by an external heat source',$);
+#1280=IFCCLASSIFICATIONREFERENCE($,'[CO]MTA','Steam engine',#1279,'heat engine utilizing expanding steam acting on one piston',$);
+#1281=IFCCLASSIFICATIONREFERENCE($,'[CO]MTB','Stirling Engine',#1279,'heat engine utilizing two pistons within one heated and one cooled cylinder or cylinder part',$);
+#1282=IFCCLASSIFICATIONREFERENCE($,'[CO]N??','Covering object',#900,'object for enclosing partly or fully another object',$);
+#1283=IFCCLASSIFICATIONREFERENCE($,'[CO]NA?','Infilling object',#1282,'covering object by filling an opening',$);
+#1284=IFCCLASSIFICATIONREFERENCE($,'[CO]NAA','Pane',#1283,'infilling object permitting light entry',$);
+#1285=IFCCLASSIFICATIONREFERENCE($,'[CO]NAB','Panel',#1283,'infilling object preventing entry of light and solid objects',$);
+#1286=IFCCLASSIFICATIONREFERENCE($,'[CO]NAC','Grating',#1283,'infilling object permitting fluid entry',$);
+#1287=IFCCLASSIFICATIONREFERENCE($,'[CO]NAD','Sealing joint',#1283,'infilling object preventing fluid entry',$);
+#1288=IFCCLASSIFICATIONREFERENCE($,'[CO]NB?','Closure object',#1282,'covering object by closing an opening',$);
+#1289=IFCCLASSIFICATIONREFERENCE($,'[CO]NBA','Door leaf',#1288,'closure object in a door',$);
+#1290=IFCCLASSIFICATIONREFERENCE($,'[CO]NBB','Leaf of large door',#1288,'closure object in a large door',$);
+#1291=IFCCLASSIFICATIONREFERENCE($,'[CO]NBC','Cabinet door',#1288,'closure object in an enclosure',$);
+#1292=IFCCLASSIFICATIONREFERENCE($,'[CO]NBD','Gate leaf',#1288,'closure object in a gate',$);
+#1293=IFCCLASSIFICATIONREFERENCE($,'[CO]NBE','Hatch cover',#1288,'closure object in a hatch',$);
+#1294=IFCCLASSIFICATIONREFERENCE($,'[CO]NC?','Finishing object',#1282,'covering object by finishing structures',$);
+#1295=IFCCLASSIFICATIONREFERENCE($,'[CO]NCA','Paving',#1294,'finishing object of a pavement',$);
+#1296=IFCCLASSIFICATIONREFERENCE($,'[CO]NCB','Wall covering',#1294,'finishing object of a wall',$);
+#1297=IFCCLASSIFICATIONREFERENCE($,'[CO]NCC','Flooring',#1294,'finishing object of a floor',$);
+#1298=IFCCLASSIFICATIONREFERENCE($,'[CO]NCD','Ceiling finish',#1294,'finishing object of a ceiling',$);
+#1299=IFCCLASSIFICATIONREFERENCE($,'[CO]NCE','Roofing',#1294,'finishing object of a roof',$);
+#1300=IFCCLASSIFICATIONREFERENCE($,'[CO]NCF','Window sill',#1294,'finishing object of an opening',$);
+#1301=IFCCLASSIFICATIONREFERENCE($,'[CO]NCG','Plant mat',#1294,'finishing object of a vegetation area',$);
+#1302=IFCCLASSIFICATIONREFERENCE($,'[CO]NCH','Surface forming object',#1294,'finishing object by forming the outer shape of an object',$);
+#1303=IFCCLASSIFICATIONREFERENCE($,'[CO]ND?','Terminating object',#1282,'covering object by terminating another object',$);
+#1304=IFCCLASSIFICATIONREFERENCE($,'[CO]NDA','Curb',#1303,'terminating object delimiting an edge of pavement or vegetation area',$);
+#1305=IFCCLASSIFICATIONREFERENCE($,'[CO]NDB','Crown',#1303,'terminating object delimiting an edge of a wall',$);
+#1306=IFCCLASSIFICATIONREFERENCE($,'[CO]NDC','Raised floor edge',#1303,'terminating object delimiting an edge of a floor',$);
+#1307=IFCCLASSIFICATIONREFERENCE($,'[CO]NDD','Ceiling edge',#1303,'terminating object delimiting an edge of a ceiling',$);
+#1308=IFCCLASSIFICATIONREFERENCE($,'[CO]NDE','Fascia',#1303,'terminating object delimiting an edge of a roof',$);
+#1309=IFCCLASSIFICATIONREFERENCE($,'[CO]NE?','Hiding object',#1282,'covering object by hiding joints and transitions',$);
+#1310=IFCCLASSIFICATIONREFERENCE($,'[CO]NEA','Ceiling lining',#1309,'hiding object between a wall and a ceiling',$);
+#1311=IFCCLASSIFICATIONREFERENCE($,'[CO]NEB','Skirting board',#1309,'hiding object between a wall and a floor',$);
+#1312=IFCCLASSIFICATIONREFERENCE($,'[CO]NEC','Wall corner lining',#1309,'hiding object between two walls',$);
+#1313=IFCCLASSIFICATIONREFERENCE($,'[CO]NED','Flashing',#1309,'hiding object on a roof',$);
+#1314=IFCCLASSIFICATIONREFERENCE($,'[CO]NEE','Reveal liner',#1309,'hiding object around an opening',$);
+#1315=IFCCLASSIFICATIONREFERENCE($,'[CO]P??','Presenting object',#900,'object for providing perceptible information',$);
+#1316=IFCCLASSIFICATIONREFERENCE($,'[CO]PF?','Visible state indicator',#1315,'presenting object in visible form of discrete states',$);
+#1317=IFCCLASSIFICATIONREFERENCE($,'[CO]PFA','Signal lamp',#1316,'visible state indicator by light',$);
+#1318=IFCCLASSIFICATIONREFERENCE($,'[CO]PFB','Semaphore',#1316,'visible state indicator by mechanical positions',$);
+#1319=IFCCLASSIFICATIONREFERENCE($,'[CO]PG?','Scalar display',#1315,'presenting object in a visible form of a single scalar variable',$);
+#1320=IFCCLASSIFICATIONREFERENCE($,'[CO]PGA','Voltmeter',#1319,'scalar display representing electric voltage',$);
+#1321=IFCCLASSIFICATIONREFERENCE($,'[CO]PGB','Resistivity indicator',#1319,'scalar display representing resistivity or conductivity',$);
+#1322=IFCCLASSIFICATIONREFERENCE($,'[CO]PGC','Ampere meter',#1319,'scalar display representing electric current',$);
+#1323=IFCCLASSIFICATIONREFERENCE($,'[CO]PGD','Density indicator',#1319,'scalar display representing density',$);
+#1324=IFCCLASSIFICATIONREFERENCE($,'[CO]PGE','Field indicator',#1319,'scalar display representing field',$);
+#1325=IFCCLASSIFICATIONREFERENCE($,'[CO]PGF','Flow meter',#1319,'scalar display representing flow',$);
+#1326=IFCCLASSIFICATIONREFERENCE($,'[CO]PGG','Dimension indicator',#1319,'scalar display representing spatial dimension and/or position',$);
+#1327=IFCCLASSIFICATIONREFERENCE($,'[CO]PGH','Energy meter',#1319,'scalar display representing energy',$);
+#1328=IFCCLASSIFICATIONREFERENCE($,'[CO]PGJ','Wattmeter',#1319,'scalar display representing electric power',$);
+#1329=IFCCLASSIFICATIONREFERENCE($,'[CO]PGK','Clock',#1319,'scalar display representing time',$);
+#1330=IFCCLASSIFICATIONREFERENCE($,'[CO]PGL','Level indicator',#1319,'scalar display representing level',$);
+#1331=IFCCLASSIFICATIONREFERENCE($,'[CO]PGM','Humidity indicator',#1319,'scalar display representing humidity',$);
+#1332=IFCCLASSIFICATIONREFERENCE($,'[CO]PGP','Pressure indicator',#1319,'scalar display representing pressure',$);
+#1333=IFCCLASSIFICATIONREFERENCE($,'[CO]PGQ','Concentration indicator',#1319,'scalar display representing substance concentration',$);
+#1334=IFCCLASSIFICATIONREFERENCE($,'[CO]PGR','Electromagnetic wave indicator',#1319,'scalar display representing radiation',$);
+#1335=IFCCLASSIFICATIONREFERENCE($,'[CO]PGS','Frequency meter',#1319,'scalar display representing time-rate',$);
+#1336=IFCCLASSIFICATIONREFERENCE($,'[CO]PGT','Temperature indicator',#1319,'scalar display representing temperature',$);
+#1337=IFCCLASSIFICATIONREFERENCE($,'[CO]PGV','Cos-phi meter',#1319,'scalar display representing the offset between active power and reactive power',$);
+#1338=IFCCLASSIFICATIONREFERENCE($,'[CO]PGW','Force indicator',#1319,'scalar display representing weight, force to torque',$);
+#1339=IFCCLASSIFICATIONREFERENCE($,'[CO]PGX','Audio spectrometer',#1319,'scalar display representing sound',$);
+#1340=IFCCLASSIFICATIONREFERENCE($,'[CO]PGZ','Quantity meter',#1319,'scalar display representing number of incidents',$);
+#1341=IFCCLASSIFICATIONREFERENCE($,'[CO]PH?','Graphical display',#1315,'presenting object in visible form by pictures, symbols, text or physical characteristics',$);
+#1342=IFCCLASSIFICATIONREFERENCE($,'[CO]PHA','Display',#1341,'graphical display dynamically on its surface',$);
+#1343=IFCCLASSIFICATIONREFERENCE($,'[CO]PHB','Projector',#1341,'graphical display dynamically on another surface',$);
+#1344=IFCCLASSIFICATIONREFERENCE($,'[CO]PHC','Printer',#1341,'graphical display on a printed media',$);
+#1345=IFCCLASSIFICATIONREFERENCE($,'[CO]PHD','Sign',#1341,'graphical display statically on its surface',$);
+#1346=IFCCLASSIFICATIONREFERENCE($,'[CO]PHE','Marking',#1341,'graphical display statically on the surface of another object',$);
+#1347=IFCCLASSIFICATIONREFERENCE($,'[CO]PHF','Marking post',#1341,'graphical display statically by its physical characteristics',$);
+#1348=IFCCLASSIFICATIONREFERENCE($,'[CO]PJ?','Acoustic device',#1315,'presenting object in audible form',$);
+#1349=IFCCLASSIFICATIONREFERENCE($,'[CO]PJA','Loudspeaker',#1348,'acoustic device provided by the movement of a membrane',$);
+#1350=IFCCLASSIFICATIONREFERENCE($,'[CO]PJB','Horn',#1348,'acoustic device provided by the stream of air',$);
+#1351=IFCCLASSIFICATIONREFERENCE($,'[CO]PJC','Bell',#1348,'acoustic device provided by mechanical excitation of the object',$);
+#1352=IFCCLASSIFICATIONREFERENCE($,'[CO]PK?','Tactile device',#1315,'presenting object in tactile form',$);
+#1353=IFCCLASSIFICATIONREFERENCE($,'[CO]PKA','Vibrator',#1352,'tactile device of discrete states by vibration',$);
+#1354=IFCCLASSIFICATIONREFERENCE($,'[CO]PKB','Braille text sign',#1352,'tactile device of static information',$);
+#1355=IFCCLASSIFICATIONREFERENCE($,'[CO]PKC','Free height warning',#1352,'tactile device for suprajacent obstacles',$);
+#1356=IFCCLASSIFICATIONREFERENCE($,'[CO]PL?','Ornamental object',#1315,'presenting object in an ornamental form',$);
+#1357=IFCCLASSIFICATIONREFERENCE($,'[CO]PLA','Cornice',#1356,'ornamental object as a separation between two areas',$);
+#1358=IFCCLASSIFICATIONREFERENCE($,'[CO]PLB','Figure',#1356,'ornamental object as a spatial representation of something',$);
+#1359=IFCCLASSIFICATIONREFERENCE($,'[CO]PLC','Frieze',#1356,'ornamental object as protruding figures and/or decorative elements on a wall',$);
+#1360=IFCCLASSIFICATIONREFERENCE($,'[CO]PLD','Picture',#1356,'ornamental object representing, illustrating or reproducing something visibly on a surface',$);
+#1361=IFCCLASSIFICATIONREFERENCE($,'[CO]PLE','Carpet',#1356,'ornamental object by a fabric covering part of another object',$);
+#1362=IFCCLASSIFICATIONREFERENCE($,'[CO]PZ?','Multiple form presenting object',#1315,'presenting object in multiple forms',$);
+#1363=IFCCLASSIFICATIONREFERENCE($,'[CO]PZA','Audio-visual alarm',#1362,'multiple form presenting object for discrete states',$);
+#1364=IFCCLASSIFICATIONREFERENCE($,'[CO]PZB','Television set',#1362,'multiple form presenting object for multiple information',$);
+#1365=IFCCLASSIFICATIONREFERENCE($,'[CO]Q??','Controlling object',#900,'object for controlling access or flow',$);
+#1366=IFCCLASSIFICATIONREFERENCE($,'[CO]QA?','Electric controlling object',#1365,'controlling object of electric current in an electric circuit',$);
+#1367=IFCCLASSIFICATIONREFERENCE($,'[CO]QAA','Contactor',#1366,'electric controlling object mechanically under normal service conditions only',$);
+#1368=IFCCLASSIFICATIONREFERENCE($,'[CO]QAB','Circuit breaker',#1366,'electric controlling object mechanically under normal and abnormal service conditions',$);
+#1369=IFCCLASSIFICATIONREFERENCE($,'[CO]QAC','Electronic power switch',#1366,'electric controlling object electronically',$);
+#1370=IFCCLASSIFICATIONREFERENCE($,'[CO]QB?','Electric separating object',#1365,'controlling object by separating electric circuits',$);
+#1371=IFCCLASSIFICATIONREFERENCE($,'[CO]QBA','Fuseless disconnector',#1370,'electric separating object by mechanically isolating the downstream circuit from the upstream circuit only',$);
+#1372=IFCCLASSIFICATIONREFERENCE($,'[CO]QBB','Fused disconnector',#1370,'electric separating object by mechanically isolating the downstream circuit from the upstream circuit and with a built-in fuse',$);
+#1373=IFCCLASSIFICATIONREFERENCE($,'[CO]QC?','Electric earthing object',#1365,'controlling object by earthing electric circuits',$);
+#1374=IFCCLASSIFICATIONREFERENCE($,'[CO]QCA','Earthing switch',#1373,'electric earthing object connecting electric circuits to earth',$);
+#1375=IFCCLASSIFICATIONREFERENCE($,'[CO]QM?','Sealed fluid switching object',#1365,'controlling object of fluids on/off in sealed enclosures',$);
+#1376=IFCCLASSIFICATIONREFERENCE($,'[CO]QMA','Liquid shutoff valve',#1375,'sealed fluid switching object of a liquid flow',$);
+#1377=IFCCLASSIFICATIONREFERENCE($,'[CO]QMB','Gas shutoff valve',#1375,'sealed fluid switching object of a gas flow',$);
+#1378=IFCCLASSIFICATIONREFERENCE($,'[CO]QN?','Sealed fluid varying object',#1365,'controlling object for varying the flow of fluids in sealed enclosures',$);
+#1379=IFCCLASSIFICATIONREFERENCE($,'[CO]QNA','Liquid control valve',#1378,'sealed fluid varying object of a liquid flow',$);
+#1380=IFCCLASSIFICATIONREFERENCE($,'[CO]QNB','Gas control valve',#1378,'sealed fluid varying object of a gas flow',$);
+#1381=IFCCLASSIFICATIONREFERENCE($,'[CO]QP?','Open flow controlling object',#1365,'controlling object of fluids in an open enclosures',$);
+#1382=IFCCLASSIFICATIONREFERENCE($,'[CO]QPA','Lock gate',#1381,'open fluid controlling object for varying or switching on/off',$);
+#1383=IFCCLASSIFICATIONREFERENCE($,'[CO]QPB','Vortex generator',#1381,'open fluid controlling object for directing a flow by a fixed means',$);
+#1384=IFCCLASSIFICATIONREFERENCE($,'[CO]QPC','Rudder',#1381,'open fluid controlling object for directing a flow by a variable means',$);
+#1385=IFCCLASSIFICATIONREFERENCE($,'[CO]QQ?','Space access object',#1365,'controlling object to a space',$);
+#1386=IFCCLASSIFICATIONREFERENCE($,'[CO]QQA','Window',#1385,'space access object for light entry only',$);
+#1387=IFCCLASSIFICATIONREFERENCE($,'[CO]QQB','Window unit',#1385,'space access object for entry of light and persons',$);
+#1388=IFCCLASSIFICATIONREFERENCE($,'[CO]QQC','Door',#1385,'space access object for upright passage by persons',$);
+#1389=IFCCLASSIFICATIONREFERENCE($,'[CO]QQD','Hatch',#1385,'space access object of restricted size for passage of persons and goods',$);
+#1390=IFCCLASSIFICATIONREFERENCE($,'[CO]QQE','Large door',#1385,'space access object of extended size for transfer of objects',$);
+#1391=IFCCLASSIFICATIONREFERENCE($,'[CO]QQF','Gate',#1385,'space access object partially',$);
+#1392=IFCCLASSIFICATIONREFERENCE($,'[CO]QQG','Rail scotch block',#1385,'space access object by blocking entrance to a track',$);
+#1393=IFCCLASSIFICATIONREFERENCE($,'[CO]QR?','Solid substance flow varying object',#1365,'controlling object of solid substance',$);
+#1394=IFCCLASSIFICATIONREFERENCE($,'[CO]QRA','Rotary air lock valve',#1393,'solid substance flow varying object with rotating chambers',$);
+#1395=IFCCLASSIFICATIONREFERENCE($,'[CO]QRB','Iris valve',#1393,'solid substance flow varying object with an iris opening',$);
+#1396=IFCCLASSIFICATIONREFERENCE($,'[CO]QRC','Flap gate valve',#1393,'solid substance flow varying object with a hinged flap or disc',$);
+#1397=IFCCLASSIFICATIONREFERENCE($,'[CO]QS?','Mechanical movement controlling object',#1365,'controlling object for mechanical movement',$);
+#1398=IFCCLASSIFICATIONREFERENCE($,'[CO]QSA','Lock',#1397,'mechanical movement controlling object by blocking',$);
+#1399=IFCCLASSIFICATIONREFERENCE($,'[CO]QSB','Track switch',#1397,'mechanical movement controlling object by switching between different tracks',$);
+#1400=IFCCLASSIFICATIONREFERENCE($,'[CO]QZ?','Multiple measure controlling object',#1365,'controlling object in electrical circuits utilising different measures',$);
+#1401=IFCCLASSIFICATIONREFERENCE($,'[CO]QZA','Earthing and disconnecting switch',#1400,'multiple measure controlling object for earthing or separating electrical circuit',$);
+#1402=IFCCLASSIFICATIONREFERENCE($,'[CO]R??','Restricting object',#900,'object for restricting or stabilising',$);
+#1403=IFCCLASSIFICATIONREFERENCE($,'[CO]RA?','Electricity restricting object',#1402,'restricting object by restricting a flow of electric energy',$);
+#1404=IFCCLASSIFICATIONREFERENCE($,'[CO]RAA','Diode',#1403,'electricity restricting object by blocking current flow in one direction',$);
+#1405=IFCCLASSIFICATIONREFERENCE($,'[CO]RAB','Reactor',#1403,'electricity restricting object by providing an inductance in a circuit',$);
+#1406=IFCCLASSIFICATIONREFERENCE($,'[CO]RAC','Resistor',#1403,'electricity restricting object by providing a resistance in a circuit',$);
+#1407=IFCCLASSIFICATIONREFERENCE($,'[CO]RB?','Electricity stabilising object',#1402,'restricting object by stabilising a flow of electric energy',$);
+#1408=IFCCLASSIFICATIONREFERENCE($,'[CO]RBA','Uninterrupted power supply',#1407,'electricity stabilising object ensuring a continuous power supply',$);
+#1409=IFCCLASSIFICATIONREFERENCE($,'[CO]RBB','Power filter',#1407,'electricity stabilising object suppressing a defined frequency range within the flow',$);
+#1410=IFCCLASSIFICATIONREFERENCE($,'[CO]RBC','Phase compensator',#1407,'electricity stabilising object providing reactive power',$);
+#1411=IFCCLASSIFICATIONREFERENCE($,'[CO]RBD','Power equaliser',#1407,'electricity stabilising object suppressing or amplifying multiple frequency ranges within the flow',$);
+#1412=IFCCLASSIFICATIONREFERENCE($,'[CO]RF?','Signal stabilising object',#1402,'restricting object by stabilising a signal',$);
+#1413=IFCCLASSIFICATIONREFERENCE($,'[CO]RFA','Signal equaliser',#1412,'signal stabilising object suppressing or amplifying multiple frequency ranges of the signal',$);
+#1414=IFCCLASSIFICATIONREFERENCE($,'[CO]RFB','Signal filter',#1412,'signal stabilising object suppressing a defined frequency range of the signal',$);
+#1415=IFCCLASSIFICATIONREFERENCE($,'[CO]RL?','Movement restricting object',#1402,'restricting object by restricting movements',$);
+#1416=IFCCLASSIFICATIONREFERENCE($,'[CO]RLA','Security chain',#1415,'movement restricting object by limiting movement of an object',$);
+#1417=IFCCLASSIFICATIONREFERENCE($,'[CO]RLB','Shock absorber',#1415,'movement restricting object by settling and damping movements of an object',$);
+#1418=IFCCLASSIFICATIONREFERENCE($,'[CO]RLC','Brake',#1415,'movement restricting object by reducing the speed of or stopping another object',$);
+#1419=IFCCLASSIFICATIONREFERENCE($,'[CO]RLD','Speed bump',#1415,'movement restricting object by preventing high speed of another object',$);
+#1420=IFCCLASSIFICATIONREFERENCE($,'[CO]RM?','Return flow restricting object',#1402,'restricting object by restricting a return flow of matter',$);
+#1421=IFCCLASSIFICATIONREFERENCE($,'[CO]RMA','Non-return valve',#1420,'return flow restricting object of liquids',$);
+#1422=IFCCLASSIFICATIONREFERENCE($,'[CO]RMB','Check damper',#1420,'return flow restricting object of gas',$);
+#1423=IFCCLASSIFICATIONREFERENCE($,'[CO]RMC','Flap',#1420,'return flow restricting object of solid substances',$);
+#1424=IFCCLASSIFICATIONREFERENCE($,'[CO]RMD','Stench trap',#1420,'return flow restricting object of odour',$);
+#1425=IFCCLASSIFICATIONREFERENCE($,'[CO]RN?','Flow restrictor',#1402,'restricting object by restricting a guided flow of matter',$);
+#1426=IFCCLASSIFICATIONREFERENCE($,'[CO]RNA','Regulating valve',#1425,'flow restrictor of liquids by an adjustable means',$);
+#1427=IFCCLASSIFICATIONREFERENCE($,'[CO]RNB','Regulating damper',#1425,'flow restrictor of gas by an adjustable means',$);
+#1428=IFCCLASSIFICATIONREFERENCE($,'[CO]RNC','Solids regulating valve',#1425,'flow restrictor of solid substances by an adjustable means',$);
+#1429=IFCCLASSIFICATIONREFERENCE($,'[CO]RND','Diffuser',#1425,'flow restrictor by a fixed means',$);
+#1430=IFCCLASSIFICATIONREFERENCE($,'[CO]RQ?','Local climate stabilising object',#1402,'restricting object of the local environment',$);
+#1431=IFCCLASSIFICATIONREFERENCE($,'[CO]RQA','Insulation',#1430,'local climate stabilising object by restricting a thermal or sound transmission',$);
+#1432=IFCCLASSIFICATIONREFERENCE($,'[CO]RQB','Membrane',#1430,'local climate stabilising object by stabilising a flow of liquids, moisture, wind or material',$);
+#1433=IFCCLASSIFICATIONREFERENCE($,'[CO]RQC','Noise barrier',#1430,'local climate stabilising object by stabilising the propagation of sound',$);
+#1434=IFCCLASSIFICATIONREFERENCE($,'[CO]RQD','Screen',#1430,'local climate stabilising object by restricting wind, precipitation or radiation',$);
+#1435=IFCCLASSIFICATIONREFERENCE($,'[CO]RQE','Curtain',#1430,'local climate stabilising object by restricting transmission of light using textiles',$);
+#1436=IFCCLASSIFICATIONREFERENCE($,'[CO]RQF','Louvered blind',#1430,'local climate stabilising object by restricting transmission of light using narrow adjustable louvers',$);
+#1437=IFCCLASSIFICATIONREFERENCE($,'[CO]RQG','Shutter',#1430,'local climate stabilising object by restricting transmission of light using an external panel',$);
+#1438=IFCCLASSIFICATIONREFERENCE($,'[CO]RQH','Weed control fabric',#1430,'local climate stabilising object preventing weed growth',$);
+#1439=IFCCLASSIFICATIONREFERENCE($,'[CO]RQJ','Screen wall',#1430,'local climate stabilising object by restricting vision and sound',$);
+#1440=IFCCLASSIFICATIONREFERENCE($,'[CO]RU?','Access restricting object',#1402,'restricting object by restricting access to an area',$);
+#1441=IFCCLASSIFICATIONREFERENCE($,'[CO]RUA','Fence',#1440,'access restricting object by a horizontal elongated barrier with a vertical extent',$);
+#1442=IFCCLASSIFICATIONREFERENCE($,'[CO]RUB','Movable barrier',#1440,'access restriction object by a movable horizontal barrier',$);
+#1443=IFCCLASSIFICATIONREFERENCE($,'[CO]RUC','Turnstile',#1440,'access restricting object by a rotational barrier',$);
+#1444=IFCCLASSIFICATIONREFERENCE($,'[CO]RUD','Bollard',#1440,'access restricting object by a barrier with limited horizontal and vertical extent',$);
+#1445=IFCCLASSIFICATIONREFERENCE($,'[CO]RUE','Rodent barrier',#1440,'access restricting object by shaping a path',$);
+#1446=IFCCLASSIFICATIONREFERENCE($,'[CO]RUF','Bird mesh',#1440,'access restricting object by a mesh',$);
+#1447=IFCCLASSIFICATIONREFERENCE($,'[CO]S??','Human interaction object',#900,'object for detecting a human action and providing an appropriate response',$);
+#1448=IFCCLASSIFICATIONREFERENCE($,'[CO]SF?','Face interaction device',#1447,'human interaction object by facial interaction',$);
+#1449=IFCCLASSIFICATIONREFERENCE($,'[CO]SFA','Eye focus reader',#1448,'face interaction device by sensing of facial activity',$);
+#1450=IFCCLASSIFICATIONREFERENCE($,'[CO]SFB','Face recognition device',#1448,'face interaction device by face recognition',$);
+#1451=IFCCLASSIFICATIONREFERENCE($,'[CO]SG?','Hand interaction device',#1447,'human interaction object to a hand action',$);
+#1452=IFCCLASSIFICATIONREFERENCE($,'[CO]SGA','Turn handle',#1451,'hand interaction device by turning',$);
+#1453=IFCCLASSIFICATIONREFERENCE($,'[CO]SGB','Grip handle',#1451,'hand interaction device by gripping',$);
+#1454=IFCCLASSIFICATIONREFERENCE($,'[CO]SGC','Push handle',#1451,'hand interaction device by pushing',$);
+#1455=IFCCLASSIFICATIONREFERENCE($,'[CO]SGD','Lever handle',#1451,'hand interaction device by lever',$);
+#1456=IFCCLASSIFICATIONREFERENCE($,'[CO]SGE','Pull handle',#1451,'hand interaction device by pulling',$);
+#1457=IFCCLASSIFICATIONREFERENCE($,'[CO]SGF','Key',#1451,'hand interaction device to turn a matching locking cylinder or locking mechanism',$);
+#1458=IFCCLASSIFICATIONREFERENCE($,'[CO]SH?','Foot interaction device',#1447,'human interaction object to a foot position',$);
+#1459=IFCCLASSIFICATIONREFERENCE($,'[CO]SHA','Pedal switch',#1458,'foot interaction device by pressing by the foot',$);
+#1460=IFCCLASSIFICATIONREFERENCE($,'[CO]SJ?','Finger interaction device',#1447,'human interaction object by finger operation',$);
+#1461=IFCCLASSIFICATIONREFERENCE($,'[CO]SJA','Flip switch',#1460,'finger interaction device by lever operation',$);
+#1462=IFCCLASSIFICATIONREFERENCE($,'[CO]SJB','Pushbutton',#1460,'finger interaction device by pressing',$);
+#1463=IFCCLASSIFICATIONREFERENCE($,'[CO]SJC','Touch actuator',#1460,'finger interaction device by a touching',$);
+#1464=IFCCLASSIFICATIONREFERENCE($,'[CO]SJD','Turning wheel',#1460,'finger interaction device by advancing/reversing a wheel',$);
+#1465=IFCCLASSIFICATIONREFERENCE($,'[CO]SK?','Movement interaction device',#1447,'manual interaction object by its movement or positioning',$);
+#1466=IFCCLASSIFICATIONREFERENCE($,'[CO]SKA','Joystick',#1465,'movement interaction device by restricted positioning and selecting operations',$);
+#1467=IFCCLASSIFICATIONREFERENCE($,'[CO]SKB','Mouse',#1465,'movement interaction device by unrestricted positioning and selecting operations',$);
+#1468=IFCCLASSIFICATIONREFERENCE($,'[CO]SZ?','Multi-interaction device',#1447,'human interaction object with multiple kinds of interacting means',$);
+#1469=IFCCLASSIFICATIONREFERENCE($,'[CO]SZA','Operating panel',#1468,'multi-interaction device involving manual means',$);
+#1470=IFCCLASSIFICATIONREFERENCE($,'[CO]T??','Transforming object',#900,'object for transforming',$);
+#1471=IFCCLASSIFICATIONREFERENCE($,'[CO]TA?','Electric energy transforming object',#1470,'transforming object of electric energy while retaining AC or DC',$);
+#1472=IFCCLASSIFICATIONREFERENCE($,'[CO]TAA','Transformer',#1471,'electric energy transforming object from AC to AC without change of the frequency',$);
+#1473=IFCCLASSIFICATIONREFERENCE($,'[CO]TAB','DC/DC converter',#1471,'electric energy transforming object from DC to DC',$);
+#1474=IFCCLASSIFICATIONREFERENCE($,'[CO]TAC','Frequency converter',#1471,'electric energy transforming object from AC to AC while changing the frequency',$);
+#1475=IFCCLASSIFICATIONREFERENCE($,'[CO]TAD','Phase shifter',#1471,'electric energy transforming object from AC to AC while shifting the angle between voltages and currents',$);
+#1476=IFCCLASSIFICATIONREFERENCE($,'[CO]TB?','Electric energy converting object',#1470,'transforming object of electric energy changing between AC and DC',$);
+#1477=IFCCLASSIFICATIONREFERENCE($,'[CO]TBA','Rectifier',#1476,'electric energy converting object converting from AC to DC',$);
+#1478=IFCCLASSIFICATIONREFERENCE($,'[CO]TBB','Inverter',#1476,'electric energy converting object converting from DC to AC',$);
+#1479=IFCCLASSIFICATIONREFERENCE($,'[CO]TBC','Bidirectional converter',#1476,'electric energy converting object converting from AC to DC or DC to AC',$);
+#1480=IFCCLASSIFICATIONREFERENCE($,'[CO]TC?','Universal power supply',#1470,'transforming object of electric energy from AC and DC to AC or DC',$);
+#1481=IFCCLASSIFICATIONREFERENCE($,'[CO]TCA','Universal AC power supply',#1480,'universal power supply providing AC',$);
+#1482=IFCCLASSIFICATIONREFERENCE($,'[CO]TCB','Universal DC power supply',#1480,'universal power supply providing DC',$);
+#1483=IFCCLASSIFICATIONREFERENCE($,'[CO]TF?','Signal converting object',#1470,'transforming object of signal',$);
+#1484=IFCCLASSIFICATIONREFERENCE($,'[CO]TFA','Amplifier',#1483,'signal converting object maintaining the signal form',$);
+#1485=IFCCLASSIFICATIONREFERENCE($,'[CO]TFB','Signal antenna',#1483,'signal converting object between a wired and wireless form',$);
+#1486=IFCCLASSIFICATIONREFERENCE($,'[CO]TFC','Signal converter',#1483,'signal converting object between wired forms',$);
+#1487=IFCCLASSIFICATIONREFERENCE($,'[CO]TFD','Optical receiver/transmitter',#1483,'signal converting object between electrical and optical form',$);
+#1488=IFCCLASSIFICATIONREFERENCE($,'[CO]TFE','Telephone',#1483,'signal converting object between acoustic waves and electrical signals',$);
+#1489=IFCCLASSIFICATIONREFERENCE($,'[CO]TL?','Mechanical energy transforming object',#1470,'transforming object of mechanical energy',$);
+#1490=IFCCLASSIFICATIONREFERENCE($,'[CO]TLA','Gear',#1489,'mechanical energy transforming object modifying velocity',$);
+#1491=IFCCLASSIFICATIONREFERENCE($,'[CO]TLB','Torque converter',#1489,'mechanical energy transforming object modifying torque',$);
+#1492=IFCCLASSIFICATIONREFERENCE($,'[CO]TLC','Lever',#1489,'mechanical energy transforming object modifying force',$);
+#1493=IFCCLASSIFICATIONREFERENCE($,'[CO]TM?','Mass reduction object',#1470,'transforming object of matter by mass reduction',$);
+#1494=IFCCLASSIFICATIONREFERENCE($,'[CO]TMA','Drilling machine',#1493,'mass reduction object by a mechanical process',$);
+#1495=IFCCLASSIFICATIONREFERENCE($,'[CO]TMB','Thermal cutter',#1493,'mass reduction object by a thermal process',$);
+#1496=IFCCLASSIFICATIONREFERENCE($,'[CO]TMC','Etching machine',#1493,'mass reduction object by a chemical process',$);
+#1497=IFCCLASSIFICATIONREFERENCE($,'[CO]TP?','Matter reshaping object',#1470,'transforming object of matter by shape reforming',$);
+#1498=IFCCLASSIFICATIONREFERENCE($,'[CO]TPA','Forging machine',#1497,'matter reshaping object by forging',$);
+#1499=IFCCLASSIFICATIONREFERENCE($,'[CO]TPB','Extruder',#1497,'matter reshaping object by extruding',$);
+#1500=IFCCLASSIFICATIONREFERENCE($,'[CO]TPC','Wire drawing machine',#1497,'matter reshaping object by drawing',$);
+#1501=IFCCLASSIFICATIONREFERENCE($,'[CO]TPD','Rolling machine',#1497,'matter reshaping object by rolling or spinning',$);
+#1502=IFCCLASSIFICATIONREFERENCE($,'[CO]TPE','Bending machine',#1497,'matter reshaping object by bending',$);
+#1503=IFCCLASSIFICATIONREFERENCE($,'[CO]TPF','Electromagnetic forming machine',#1497,'matter reshaping object by high energy rate forming',$);
+#1504=IFCCLASSIFICATIONREFERENCE($,'[CO]TR?','Organic plant',#1470,'transforming object of radiation by photosynthesis',$);
+#1505=IFCCLASSIFICATIONREFERENCE($,'[CO]TRA','Tree',#1504,'organic plant with a single woody stem',$);
+#1506=IFCCLASSIFICATIONREFERENCE($,'[CO]TRB','Shrub',#1504,'organic plant with several woody stems',$);
+#1507=IFCCLASSIFICATIONREFERENCE($,'[CO]TRC','Climber',#1504,'organic plant with long shoots with climbing organs',$);
+#1508=IFCCLASSIFICATIONREFERENCE($,'[CO]TRD','Herb',#1504,'organic plant that is not woody or is insignificantly woody',$);
+#1509=IFCCLASSIFICATIONREFERENCE($,'[CO]U??','Holding object',#900,'object for localising other objects',$);
+#1510=IFCCLASSIFICATIONREFERENCE($,'[CO]UA?','Positioning object',#1509,'holding object by fixing at a defined position',$);
+#1511=IFCCLASSIFICATIONREFERENCE($,'[CO]UAA','Insulator',#1510,'positioning object for conductor',$);
+#1512=IFCCLASSIFICATIONREFERENCE($,'[CO]UAB','Cable gland',#1510,'positioning object for cable',$);
+#1513=IFCCLASSIFICATIONREFERENCE($,'[CO]UAC','Light fixture',#1510,'positioning object for light source',$);
+#1514=IFCCLASSIFICATIONREFERENCE($,'[CO]UAD','Stair stringer',#1510,'positioning object for tread',$);
+#1515=IFCCLASSIFICATIONREFERENCE($,'[CO]UAE','Batten',#1510,'positioning object for cover',$);
+#1516=IFCCLASSIFICATIONREFERENCE($,'[CO]UAF','Stake',#1510,'positioning object for plant',$);
+#1517=IFCCLASSIFICATIONREFERENCE($,'[CO]UAG','Rail gauge',#1510,'positioning object for rail spacing',$);
+#1518=IFCCLASSIFICATIONREFERENCE($,'[CO]UAH ','Clamp',#1517,'positioning object for tool or work piece',$);
+#1519=IFCCLASSIFICATIONREFERENCE($,'[CO]UAJ','Vehicle stand',#1510,'positioning object for vehicle',$);
+#1520=IFCCLASSIFICATIONREFERENCE($,'[CO]UB?','Carrying object',#1509,'holding object by carrying',$);
+#1521=IFCCLASSIFICATIONREFERENCE($,'[CO]UBA','Cable tray',#1520,'carrying object between discrete positions',$);
+#1522=IFCCLASSIFICATIONREFERENCE($,'[CO]UBB','Hanger',#1520,'carrying object at a discrete position by vertical pulling',$);
+#1523=IFCCLASSIFICATIONREFERENCE($,'[CO]UBC','Tread',#1520,'carrying object at a discrete position by steps',$);
+#1524=IFCCLASSIFICATIONREFERENCE($,'[CO]UBD','Mast',#1520,'carrying object at a discrete position by vertical pushing',$);
+#1525=IFCCLASSIFICATIONREFERENCE($,'[CO]UBE','Table',#1520,'carrying object at a discrete plane by pushing',$);
+#1526=IFCCLASSIFICATIONREFERENCE($,'[CO]UBF','Portal',#1520,'carrying object between discrete positions at a defined level',$);
+#1527=IFCCLASSIFICATIONREFERENCE($,'[CO]UC?','Enclosing object',#1509,'holding object by enclosing',$);
+#1528=IFCCLASSIFICATIONREFERENCE($,'[CO]UCA','Cubicle',#1527,'enclosing object for devices',$);
+#1529=IFCCLASSIFICATIONREFERENCE($,'[CO]UCB','Plant soil',#1527,'enclosing object for plant roots',$);
+#1530=IFCCLASSIFICATIONREFERENCE($,'[CO]UCC','Cooker hood',#1527,'enclosing object for spot ventilation',$);
+#1531=IFCCLASSIFICATIONREFERENCE($,'[CO]UL?','Structural supporting object',#1509,'holding object by forming a structural support',$);
+#1532=IFCCLASSIFICATIONREFERENCE($,'[CO]ULA','Base course',#1531,'structural supporting object in form of an aggregate material layer',$);
+#1533=IFCCLASSIFICATIONREFERENCE($,'[CO]ULB','Console',#1531,'structural supporting object in form of a projection supporting an overlying structure',$);
+#1534=IFCCLASSIFICATIONREFERENCE($,'[CO]ULC','Pile',#1531,'structural supporting object in a curved or straight linear form transferring compressing forces to surrounding media',$);
+#1535=IFCCLASSIFICATIONREFERENCE($,'[CO]ULD','Column',#1531,'structural supporting object in a curved or straight linear form transferring compressing forces to other structural objects',$);
+#1536=IFCCLASSIFICATIONREFERENCE($,'[CO]ULE','Beam',#1531,'structural supporting object in a straight linear form withstanding bending moments',$);
+#1537=IFCCLASSIFICATIONREFERENCE($,'[CO]ULF','Tension brace',#1531,'structural supporting object in a straight linear form withstanding tensile forces',$);
+#1538=IFCCLASSIFICATIONREFERENCE($,'[CO]ULG','Block',#1531,'structural supporting object in form of a block',$);
+#1539=IFCCLASSIFICATIONREFERENCE($,'[CO]ULH','Arch',#1531,'structural supporting object in a curved linear form withstanding bending moments',$);
+#1540=IFCCLASSIFICATIONREFERENCE($,'[CO]ULJ','Abutment',#1531,'structural supporting object in a plane or curved surface form withstanding compression forces and bending moment',$);
+#1541=IFCCLASSIFICATIONREFERENCE($,'[CO]ULK','Slab plate',#1531,'structural supporting object in laying a plane surface form withstanding bending moments',$);
+#1542=IFCCLASSIFICATIONREFERENCE($,'[CO]ULL','Retaining wall plate',#1531,'structural supporting object in a standing plane or curved surface form withstanding bending moments',$);
+#1543=IFCCLASSIFICATIONREFERENCE($,'[CO]ULM','Wall plate',#1531,'structural supporting object in a plane or curved surface form withstanding compression forces',$);
+#1544=IFCCLASSIFICATIONREFERENCE($,'[CO]ULN','Shell',#1531,'structural supporting object a plane or curved surface form withstanding tensile forces',$);
+#1545=IFCCLASSIFICATIONREFERENCE($,'[CO]ULP','Continuous arch',#1531,'structural supporting object in a curved surface form withstanding bending moments',$);
+#1546=IFCCLASSIFICATIONREFERENCE($,'[CO]ULQ','Bearing plate',#1531,'structural supporting object a flat surface form distributing loads in joints',$);
+#1547=IFCCLASSIFICATIONREFERENCE($,'[CO]ULR','Catenary',#1531,'structural supporting object in a curved linear form withstanding tensile forces',$);
+#1548=IFCCLASSIFICATIONREFERENCE($,'[CO]UM?','Reinforcing object',#1509,'holding object by reinforcement',$);
+#1549=IFCCLASSIFICATIONREFERENCE($,'[CO]UMA','Reinforcing rod',#1548,'reinforcing object as an embedded rod',$);
+#1550=IFCCLASSIFICATIONREFERENCE($,'[CO]UMB','Reinforcing mesh',#1548,'reinforcing object as an embedded mesh or fabric',$);
+#1551=IFCCLASSIFICATIONREFERENCE($,'[CO]UMC','Reinforcing mass layer',#1548,'reinforcing object as a mass layer',$);
+#1552=IFCCLASSIFICATIONREFERENCE($,'[CO]UMD','Reinforcing matter',#1548,'reinforcing object as an embedded matter',$);
+#1553=IFCCLASSIFICATIONREFERENCE($,'[CO]UME','Security strike plate',#1548,'reinforcing object as a lock latch',$);
+#1554=IFCCLASSIFICATIONREFERENCE($,'[CO]UMF','Splice',#1548,'reinforcing object as an intermediate filling',$);
+#1555=IFCCLASSIFICATIONREFERENCE($,'[CO]UMG','Soil vertical drain',#1548,'reinforcing object by vertical draining',$);
+#1556=IFCCLASSIFICATIONREFERENCE($,'[CO]UMH','Concrete spurting',#1548,'reinforcing object by concrete spurting',$);
+#1557=IFCCLASSIFICATIONREFERENCE($,'[CO]UMJ','Concrete lining',#1548,'reinforcing object by concrete lining',$);
+#1558=IFCCLASSIFICATIONREFERENCE($,'[CO]UMK','Stiffener',#1548,'reinforcing object by stiffener',$);
+#1559=IFCCLASSIFICATIONREFERENCE($,'[CO]UML','Diagonal bonding',#1548,'reinforcing object by diagonal bonding',$);
+#1560=IFCCLASSIFICATIONREFERENCE($,'[CO]UMM','Stabilizing cable',#1548,'reinforcing object as cables',$);
+#1561=IFCCLASSIFICATIONREFERENCE($,'[CO]UMN','Horizontal rail',#1548,'reinforcing object as a horizontal rail',$);
+#1562=IFCCLASSIFICATIONREFERENCE($,'[CO]UMP','Earth pressure embankment',#1548,'reinforcing object which by its counterweight stabilizes soil',$);
+#1563=IFCCLASSIFICATIONREFERENCE($,'[CO]UN?','Framing object',#1509,'holding object by framing other objects',$);
+#1564=IFCCLASSIFICATIONREFERENCE($,'[CO]UNA','Fixed frame',#1563,'framing object with a fixed frame',$);
+#1565=IFCCLASSIFICATIONREFERENCE($,'[CO]UNB','Threshold',#1563,'framing object with the lowest part of gate abutting',$);
+#1566=IFCCLASSIFICATIONREFERENCE($,'[CO]UNC','Movable frame',#1563,'framing object with a moveable frame',$);
+#1567=IFCCLASSIFICATIONREFERENCE($,'[CO]UND','Glazing bar frame',#1563,'framing object dividing a sash or door',$);
+#1568=IFCCLASSIFICATIONREFERENCE($,'[CO]UP?','Jointing object',#1509,'holding object by non-static jointing',$);
+#1569=IFCCLASSIFICATIONREFERENCE($,'[CO]UPA','Bearing',#1568,'jointing object by bearing',$);
+#1570=IFCCLASSIFICATIONREFERENCE($,'[CO]UPB','Hinge',#1568,'jointing object by mechanical joint',$);
+#1571=IFCCLASSIFICATIONREFERENCE($,'[CO]UPC','Chemical joint',#1568,'jointing object by chemical bonding',$);
+#1572=IFCCLASSIFICATIONREFERENCE($,'[CO]UQ?','Fastening object',#1509,'holding object by static jointing',$);
+#1573=IFCCLASSIFICATIONREFERENCE($,'[CO]UQA','Anchor plate',#1572,'fastening object by rigid and permanent mechanical means',$);
+#1574=IFCCLASSIFICATIONREFERENCE($,'[CO]UQB','Bolt',#1572,'fastening object by rigid and reversible mechanically means',$);
+#1575=IFCCLASSIFICATIONREFERENCE($,'[CO]UQC','Chemical bond',#1572,'fastening object by chemical bonding',$);
+#1576=IFCCLASSIFICATIONREFERENCE($,'[CO]UT?','Levelling object',#1509,'holding object for levelling',$);
+#1577=IFCCLASSIFICATIONREFERENCE($,'[CO]UTA','Filling',#1576,'levelling object providing a defined profiled surface of plane by adding material',$);
+#1578=IFCCLASSIFICATIONREFERENCE($,'[CO]UTB','Excavation',#1576,'levelling object providing a defined profiled surface of plane by removing material',$);
+#1579=IFCCLASSIFICATIONREFERENCE($,'[CO]UU?','Existing ground',#1509,'holding object being existing ground',$);
+#1580=IFCCLASSIFICATIONREFERENCE($,'[CO]UUA','Rock',#1579,'existing ground of rock',$);
+#1581=IFCCLASSIFICATIONREFERENCE($,'[CO]UUB','Friction soil',#1579,'existing ground of friction soil',$);
+#1582=IFCCLASSIFICATIONREFERENCE($,'[CO]UUC','Cohesive soil',#1579,'existing ground of cohesive soil',$);
+#1583=IFCCLASSIFICATIONREFERENCE($,'[CO]UUD','Organic soil',#1579,'existing ground of organic soil',$);
+#1584=IFCCLASSIFICATIONREFERENCE($,'[CO]UUE','Filled material',#1579,'existing ground of filled material',$);
+#1585=IFCCLASSIFICATIONREFERENCE($,'[CO]W??','Guiding object',#900,'object for leading from one place to another',$);
+#1586=IFCCLASSIFICATIONREFERENCE($,'[CO]WB?','High voltage electric energy guiding object',#1585,'guiding object of high voltage electric energy',$);
+#1587=IFCCLASSIFICATIONREFERENCE($,'[CO]WBA','High voltage busbar',#1586,'high voltage electric energy guiding object by a busbar',$);
+#1588=IFCCLASSIFICATIONREFERENCE($,'[CO]WBB','High voltage cable',#1586,'high voltage electric energy guiding object by a cable',$);
+#1589=IFCCLASSIFICATIONREFERENCE($,'[CO]WBC','High voltage wire',#1586,'high voltage electric energy guiding object by a wire',$);
+#1590=IFCCLASSIFICATIONREFERENCE($,'[CO]WBD','High voltage bushing',#1586,'high voltage electric energy guiding object through an enclosure, wall or barrier',$);
+#1591=IFCCLASSIFICATIONREFERENCE($,'[CO]WD?','Low voltage electric energy guiding object',#1585,'guiding object of low voltage electric energy',$);
+#1592=IFCCLASSIFICATIONREFERENCE($,'[CO]WDA','Low voltage busbar',#1591,'low voltage electric energy guiding object by a busbar',$);
+#1593=IFCCLASSIFICATIONREFERENCE($,'[CO]WDB','Low voltage cable',#1591,'low voltage electric energy guiding object by a cable',$);
+#1594=IFCCLASSIFICATIONREFERENCE($,'[CO]WDC','Low voltage wire',#1591,'low voltage electric energy guiding object by a wire',$);
+#1595=IFCCLASSIFICATIONREFERENCE($,'[CO]WDD','Low voltage bushing',#1591,'low voltage electric energy guiding object through an enclosure, wall or barrier',$);
+#1596=IFCCLASSIFICATIONREFERENCE($,'[CO]WE?','Reference potential guiding object',#1585,'guiding object of a reference potential',$);
+#1597=IFCCLASSIFICATIONREFERENCE($,'[CO]WEA','Earthing rail',#1596,'reference potential guiding object of earth by a busbar',$);
+#1598=IFCCLASSIFICATIONREFERENCE($,'[CO]WEB','Earthing cable',#1596,'reference potential guiding object of earth by a cable',$);
+#1599=IFCCLASSIFICATIONREFERENCE($,'[CO]WEC','Equipotential bonding rail',#1596,'reference potential guiding object by a busbar',$);
+#1600=IFCCLASSIFICATIONREFERENCE($,'[CO]WED','Equipotential bonding cable',#1596,'reference potential guiding object by a cable',$);
+#1601=IFCCLASSIFICATIONREFERENCE($,'[CO]WG?','Electric signal guiding object',#1585,'guiding object for electric signals',$);
+#1602=IFCCLASSIFICATIONREFERENCE($,'[CO]WGA','Control cable',#1601,'electric signal guiding object for controlling and measuring',$);
+#1603=IFCCLASSIFICATIONREFERENCE($,'[CO]WGB','Data cable',#1601,'electric signal guiding object for data transmission',$);
+#1604=IFCCLASSIFICATIONREFERENCE($,'[CO]WGC','Electric signal bushing',#1601,'electric signal guiding object through an enclosure or wall',$);
+#1605=IFCCLASSIFICATIONREFERENCE($,'[CO]WH?','Light guiding object',#1585,'guiding object for light',$);
+#1606=IFCCLASSIFICATIONREFERENCE($,'[CO]WHA','Fibre optical cable',#1605,'light guiding object for signal transmission',$);
+#1607=IFCCLASSIFICATIONREFERENCE($,'[CO]WHB','Optical light fibre',#1605,'light guiding object for light transmission',$);
+#1608=IFCCLASSIFICATIONREFERENCE($,'[CO]WHC','Lens',#1605,'light guiding object for defined light refraction',$);
+#1609=IFCCLASSIFICATIONREFERENCE($,'[CO]WHD','Mirror',#1605,'light guiding object for defined light reflection',$);
+#1610=IFCCLASSIFICATIONREFERENCE($,'[CO]WJ?','Sound guiding object',#1585,'guiding object for sound',$);
+#1611=IFCCLASSIFICATIONREFERENCE($,'[CO]WJA','Sound reflector',#1610,'sound guiding object by reflection',$);
+#1612=IFCCLASSIFICATIONREFERENCE($,'[CO]WL?','Solid matter guiding object',#1585,'guiding object of solid matter in open enclosure',$);
+#1613=IFCCLASSIFICATIONREFERENCE($,'[CO]WLA','Roller table',#1612,'solid matter guiding object by rolling',$);
+#1614=IFCCLASSIFICATIONREFERENCE($,'[CO]WLB','Chute',#1612,'solid matter guiding object by sliding',$);
+#1615=IFCCLASSIFICATIONREFERENCE($,'[CO]WM?','Open enclosure guiding object',#1585,'guiding object of fluid flow in open enclosure',$);
+#1616=IFCCLASSIFICATIONREFERENCE($,'[CO]WMA','Drainage layer',#1615,'open enclosure guiding object in a layer',$);
+#1617=IFCCLASSIFICATIONREFERENCE($,'[CO]WMB','Gutter',#1615,'open enclosure guiding object for liquid in open conduit',$);
+#1618=IFCCLASSIFICATIONREFERENCE($,'[CO]WMC','Drain liquid flashing',#1615,'open enclosure guiding object for liquid with an open surface',$);
+#1619=IFCCLASSIFICATIONREFERENCE($,'[CO]WMD','Drip nose',#1615,'open enclosure guiding object for liquid by projection',$);
+#1620=IFCCLASSIFICATIONREFERENCE($,'[CO]WME','Gutter drip',#1615,'open enclosure guiding object for liquid by liquid-draining profile',$);
+#1621=IFCCLASSIFICATIONREFERENCE($,'[CO]WMF','Drainpipe',#1615,'open enclosure guiding object by perforated pipe',$);
+#1622=IFCCLASSIFICATIONREFERENCE($,'[CO]WMG','Infiltration chamber',#1615,'open enclosure guiding object in a chamber',$);
+#1623=IFCCLASSIFICATIONREFERENCE($,'[CO]WP?','Closed enclosure guiding object',#1585,'guiding object of matter flow in closed enclosure',$);
+#1624=IFCCLASSIFICATIONREFERENCE($,'[CO]WPA','Pipe',#1623,'closed enclosure guiding object in a circular rigid form',$);
+#1625=IFCCLASSIFICATIONREFERENCE($,'[CO]WPB','Duct',#1623,'closed enclosure guiding object in a non-circular rigid form',$);
+#1626=IFCCLASSIFICATIONREFERENCE($,'[CO]WPC','Hose',#1623,'closed enclosure guiding object in a flexible form',$);
+#1627=IFCCLASSIFICATIONREFERENCE($,'[CO]WQ?','Mechanical energy guiding object',#1585,'guiding object of mechanical energy',$);
+#1628=IFCCLASSIFICATIONREFERENCE($,'[CO]WQA','Drive axle',#1627,'mechanical energy guiding object by a shaft',$);
+#1629=IFCCLASSIFICATIONREFERENCE($,'[CO]WQB','Drive belt',#1627,'mechanical energy guiding object by a belt',$);
+#1630=IFCCLASSIFICATIONREFERENCE($,'[CO]WQC','Drive chain',#1627,'mechanical energy guiding object by a chain',$);
+#1631=IFCCLASSIFICATIONREFERENCE($,'[CO]WQD','Drive link',#1627,'mechanical energy guiding object by a linkage',$);
+#1632=IFCCLASSIFICATIONREFERENCE($,'[CO]WQE','Wheel',#1627,'mechanical energy guiding object by a wheel',$);
+#1633=IFCCLASSIFICATIONREFERENCE($,'[CO]WQF','Toothed bar',#1627,'mechanical energy guiding object by a toothed bar',$);
+#1634=IFCCLASSIFICATIONREFERENCE($,'[CO]WQG','Hydraulic hose',#1627,'mechanical energy guiding object by a fluid link',$);
+#1635=IFCCLASSIFICATIONREFERENCE($,'[CO]WR?','Rail object',#1585,'guiding object for track bound objects',$);
+#1636=IFCCLASSIFICATIONREFERENCE($,'[CO]WRA','Track',#1635,'rail object providing a directed path',$);
+#1637=IFCCLASSIFICATIONREFERENCE($,'[CO]WRB','Track crossing',#1635,'rail object providing a crossing',$);
+#1638=IFCCLASSIFICATIONREFERENCE($,'[CO]WV?','Thermal energy guiding object',#1585,'guiding object for thermal energy',$);
+#1639=IFCCLASSIFICATIONREFERENCE($,'[CO]WVA','Thermal conductor',#1638,'thermal energy guiding object by a solid mass',$);
+#1640=IFCCLASSIFICATIONREFERENCE($,'[CO]WVB','Thermal paste',#1638,'thermal energy guiding object by a static fluid mass',$);
+#1641=IFCCLASSIFICATIONREFERENCE($,'[CO]WZ?','Multiple flow guiding object',#1585,'guiding object with multiple kinds of flows',$);
+#1642=IFCCLASSIFICATIONREFERENCE($,'[CO]WZA','Umbilical cable',#1641,'multiple flow guiding object of electrical energy, mechanical energy, electrical signal, optical signals and/or fluid',$);
+#1643=IFCCLASSIFICATIONREFERENCE($,'[CO]X??','Interfacing object',#900,'object for interfacing an object',$);
+#1644=IFCCLASSIFICATIONREFERENCE($,'[CO]XB?','High voltage connecting object',#1643,'interfacing object for high voltage electric power',$);
+#1645=IFCCLASSIFICATIONREFERENCE($,'[CO]XBA','High voltage terminal',#1644,'high voltage connecting object for a single connection',$);
+#1646=IFCCLASSIFICATIONREFERENCE($,'[CO]XBB','High voltage socket',#1644,'high voltage connecting object for pluggable connections',$);
+#1647=IFCCLASSIFICATIONREFERENCE($,'[CO]XBC','High voltage terminal box',#1644,'high voltage connecting object for multiple fixed connections',$);
+#1648=IFCCLASSIFICATIONREFERENCE($,'[CO]XD?','Low voltage connecting object',#1643,'interfacing object for low voltage electric power',$);
+#1649=IFCCLASSIFICATIONREFERENCE($,'[CO]XDA','Electric terminal',#1648,'low voltage connecting object for a single connection',$);
+#1650=IFCCLASSIFICATIONREFERENCE($,'[CO]XDB','Low voltage power socket',#1648,'low voltage connecting object for pluggable connections',$);
+#1651=IFCCLASSIFICATIONREFERENCE($,'[CO]XDC','Terminal box',#1648,'low voltage connecting object for multiple fixed connections',$);
+#1652=IFCCLASSIFICATIONREFERENCE($,'[CO]XDD','Power outlet',#1648,'low voltage connecting object for fixed connection of a flexible cable for a current-using equipment',$);
+#1653=IFCCLASSIFICATIONREFERENCE($,'[CO]XDE','Power distributer',#1648,'low voltage connecting object of multiple pluggable current-using equipment',$);
+#1654=IFCCLASSIFICATIONREFERENCE($,'[CO]XE?','Potential connecting object',#1643,'interfacing object for earth or reference potential',$);
+#1655=IFCCLASSIFICATIONREFERENCE($,'[CO]XEA','PE terminal',#1654,'potential connecting object for connecting to PE-conductors',$);
+#1656=IFCCLASSIFICATIONREFERENCE($,'[CO]XEB','PB terminal',#1654,'potential connecting object for connecting to PB conductors',$);
+#1657=IFCCLASSIFICATIONREFERENCE($,'[CO]XEC','FE terminal',#1654,'potential connecting object for connecting to FE-conductors',$);
+#1658=IFCCLASSIFICATIONREFERENCE($,'[CO]XED','FB terminal',#1654,'potential connecting object for connecting to FB conductors',$);
+#1659=IFCCLASSIFICATIONREFERENCE($,'[CO]XEE','Earth electrode',#1654,'potential connecting object providing electrical contact with earth',$);
+#1660=IFCCLASSIFICATIONREFERENCE($,'[CO]XEF','Collector',#1654,'potential connecting object connecting lightning to an electrical circuit',$);
+#1661=IFCCLASSIFICATIONREFERENCE($,'[CO]XG?','Electric signal connecting object',#1643,'interfacing object for electric signals',$);
+#1662=IFCCLASSIFICATIONREFERENCE($,'[CO]XGA','Signal socket',#1661,'electric signal connecting object for single pluggable interconnections',$);
+#1663=IFCCLASSIFICATIONREFERENCE($,'[CO]XGB','Terminal',#1661,'electric signal connecting object for a single interconnection',$);
+#1664=IFCCLASSIFICATIONREFERENCE($,'[CO]XGC','Patch panel',#1661,'electric signal connecting object among several lines',$);
+#1665=IFCCLASSIFICATIONREFERENCE($,'[CO]XGD','Signal distributor',#1661,'electric signal connecting object for multiple pluggable interconnections',$);
+#1666=IFCCLASSIFICATIONREFERENCE($,'[CO]XH?','Light connecting object',#1643,'interfacing object of optical fibres',$);
+#1667=IFCCLASSIFICATIONREFERENCE($,'[CO]XHA','Fixed light fibre socket',#1666,'light connecting object for pluggable connections',$);
+#1668=IFCCLASSIFICATIONREFERENCE($,'[CO]XK?','Collecting interfacing object',#1643,'interfacing object collecting for feeding into a flow',$);
+#1669=IFCCLASSIFICATIONREFERENCE($,'[CO]XKA','Sink',#1668,'collecting interfacing object of wastewater',$);
+#1670=IFCCLASSIFICATIONREFERENCE($,'[CO]XKB','Toilet',#1668,'collecting interfacing object of faeces and urine',$);
+#1671=IFCCLASSIFICATIONREFERENCE($,'[CO]XKC','Urinal',#1668,'collecting interfacing object of urine only',$);
+#1672=IFCCLASSIFICATIONREFERENCE($,'[CO]XKD','Drip cup',#1668,'collecting interfacing object of surplus liquid from technical system',$);
+#1673=IFCCLASSIFICATIONREFERENCE($,'[CO]XKE','Floor drain',#1668,'collecting interfacing object of surplus',$);
+#1674=IFCCLASSIFICATIONREFERENCE($,'[CO]XM?','Sealed flow connecting object',#1643,'Interfacing object for sealed material flow',$);
+#1675=IFCCLASSIFICATIONREFERENCE($,'[CO]XMA','Tube flange',#1674,'sealed flow connecting object by a reversible form',$);
+#1676=IFCCLASSIFICATIONREFERENCE($,'[CO]XMB','Tube fitting',#1674,'sealed flow connecting object by an irreversible form',$);
+#1677=IFCCLASSIFICATIONREFERENCE($,'[CO]XN?','Non-detachable coupling',#1643,'interfacing object for continuous transfer of mechanical forces',$);
+#1678=IFCCLASSIFICATIONREFERENCE($,'[CO]XNA','Flange coupler',#1677,'non-detachable coupling of shafts by a pair of flanges',$);
+#1679=IFCCLASSIFICATIONREFERENCE($,'[CO]XNB','Split-muff coupler',#1677,'non-detachable coupling of shafts by a splitted sleeve',$);
+#1680=IFCCLASSIFICATIONREFERENCE($,'[CO]XNC','Hirth coupler',#1677,'non-detachable coupling between parallel shafts by teeth and grooves meshing together',$);
+#1681=IFCCLASSIFICATIONREFERENCE($,'[CO]XND','Cardan joint',#1677,'non-detachable coupling for transmitting power of misaligned shafts',$);
+#1682=IFCCLASSIFICATIONREFERENCE($,'[CO]XNE','Buffer-and-chain coupler',#1677,'non-detachable coupling to catch and connect two moving objects',$);
+#1683=IFCCLASSIFICATIONREFERENCE($,'[CO]XNF','Link-and pin coupler',#1677,'non-detachable coupling to fix two moving objects',$);
+#1684=IFCCLASSIFICATIONREFERENCE($,'[CO]XP?','Detachable coupling',#1643,'interfacing object for discontinuous transfer of mechanical forces',$);
+#1685=IFCCLASSIFICATIONREFERENCE($,'[CO]XPA','Friction clutch',#1684,'detachable coupling for power transmission by means of frictional forces',$);
+#1686=IFCCLASSIFICATIONREFERENCE($,'[CO]XPB','Fluid coupling',#1684,'detachable coupling for power transmission by means of hydrodynamic effect',$);
+#1687=IFCCLASSIFICATIONREFERENCE($,'[CO]XPC','Overrunning clutch',#1684,'detachable coupling for power transmission by interlocking means',$);
+#1688=IFCCLASSIFICATIONREFERENCE($,'[CO]XPD','Magnetic hoist',#1684,'detachable coupling for load by means of magnetic force',$);
+#1689=IFCCLASSIFICATIONREFERENCE($,'[CO]XS?','Level connecting component',#1643,'interfacing object connecting levels',$);
+#1690=IFCCLASSIFICATIONREFERENCE($,'[CO]XSA','Landing',#1689,'level connecting object in the form of a horizontal plane connected to a flight of stairs',$);
+#1691=IFCCLASSIFICATIONREFERENCE($,'[CO]XSB','Flight of stairs',#1689,'level connecting object in the form of sequential steps',$);
+#1692=IFCCLASSIFICATIONREFERENCE($,'[CO]XSC','Ladder',#1689,'level connecting object in the form of sequential rungs or bars',$);
+#1693=IFCCLASSIFICATIONREFERENCE($,'[CO]XSD','Ramp',#1689,'level connecting object in the form of an inclined plane',$);
+#1694=IFCCLASSIFICATIONREFERENCE($,'[CO]XSE','Fireman’s pole',#1689,'level connecting object in the form of a vertical pole',$);
+#1695=IFCCLASSIFICATIONREFERENCE($,'[CO]XT?','Space linking object',#1643,'interfacing object linking space',$);
+#1696=IFCCLASSIFICATIONREFERENCE($,'[CO]XTA','Hole',#1695,'space linking object in the form of penetration',$);
+#1697=IFCCLASSIFICATIONREFERENCE($,'[CO]XTB','Box-out',#1695,'space linking object in the form of cavity',$);
+#1698=IFCCLASSIFICATIONREFERENCE($,'[CO]XZ?','Multiple flow connector object',#1643,'interfacing object with multiple kinds of flows',$);
+#1699=IFCCLASSIFICATIONREFERENCE($,'[CO]XZA','Multi connector',#1698,'multiple flow connector object of electric energy, electrical signal, optical signals and/or fluid',$);
+ENDSEC;
+END-ISO-10303-21;
diff --git a/src/blenderbim/blenderbim/bim/ui.py b/src/blenderbim/blenderbim/bim/ui.py
index bdc74c516a..7654062959 100644
--- a/src/blenderbim/blenderbim/bim/ui.py
+++ b/src/blenderbim/blenderbim/bim/ui.py
@@ -127,6 +127,42 @@ class BIM_PT_section_with_cappings(Panel):
row.operator("bim.clipping_plane_cut_with_cappings", icon="XRAY", text="Cut")
row.operator("bim.revert_clipping_plane_cut", icon="FILE_REFRESH", text="Revert Cut")
+ props = context.scene.BIMProjectProperties
+ box = layout.box()
+ header = box.row(align=True)
+ header.label(text="Clipping Planes")
+ header.operator("bim.save_clipping_planes", text="", icon="EXPORT")
+ header.operator("bim.load_clipping_planes", text="", icon="IMPORT")
+ header.operator("bim.create_clipping_plane", text="", icon="ADD")
+
+ box.template_list(
+ "BIM_UL_clipping_plane",
+ "",
+ props,
+ "clipping_planes",
+ props,
+ "clipping_planes_active",
+ )
+
+ if props.clipping_planes_active < len(props.clipping_planes):
+ active_clipping_plane_obj = props.clipping_planes[props.clipping_planes_active].obj
+ box.prop(active_clipping_plane_obj, "location")
+ box.prop(active_clipping_plane_obj, "rotation_euler")
+
+
+class BIM_UL_clipping_plane(bpy.types.UIList):
+ def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
+ if item and item.obj:
+ obj = item.obj
+ row = layout.row(align=True)
+ row.prop(obj, "name", text="", emboss=False)
+ row.operator("bim.select_object", text="", icon="RESTRICT_SELECT_OFF").obj_name = obj.name
+ row.context_pointer_set("active_object", obj)
+ row.operator("bim.flip_clipping_plane", text="", icon="PASTEFLIPDOWN")
+ row.operator("bim.delete_object", text="", icon="TRASH").obj_name = obj.name
+ else:
+ layout.label(text="", translate=False)
+
class BIM_UL_generic(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
@@ -168,6 +204,7 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
name="Should Make A Cha-Ching Sound When Project Costs Updates", default=False
)
lock_grids_on_import: BoolProperty(name="Should Lock Grids By Default", default=True)
+ spatial_elements_unselectable: BoolProperty(name="Should Make Spatial Elements Unselectable By Default", default=True)
decorations_colour: bpy.props.FloatVectorProperty(
name="Decorations Colour", subtype="COLOR", default=(1, 1, 1, 1), min=0.0, max=1.0, size=4
)
@@ -249,6 +286,10 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
row.prop(self, "should_play_chaching_sound")
row = layout.row()
row.prop(self, "lock_grids_on_import")
+ row = layout.row()
+ row.prop(self, "spatial_elements_unselectable")
+
+
row = layout.row()
row.prop(context.scene.BIMProjectProperties, "should_disable_undo_on_save")
@@ -432,7 +473,7 @@ class BIM_PT_tab_grouping_and_filtering(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
- bl_options = {"DEFAULT_CLOSED"}
+ bl_options = {"DEFAULT_CLOSED","HEADER_LAYOUT_EXPAND"}
@classmethod
def poll(cls, context):
@@ -441,6 +482,13 @@ class BIM_PT_tab_grouping_and_filtering(Panel):
def draw(self, context):
pass
+ def draw_header(self, context):
+ # Draws help button on the right
+ row = self.layout.row(align=True)
+ row.label(text="") # empty text occupies the left of the row
+ row.operator("bim.open_uri", text="", icon="HELP").uri = \
+ "https://docs.ifcopenshell.org/ifcopenshell-python/selector_syntax.html"
+
class BIM_PT_tab_geometry(Panel):
bl_label = "Geometry"
diff --git a/src/blenderbim/blenderbim/core/aggregate.py b/src/blenderbim/blenderbim/core/aggregate.py
index 5445a5647c..58d10814a7 100644
--- a/src/blenderbim/blenderbim/core/aggregate.py
+++ b/src/blenderbim/blenderbim/core/aggregate.py
@@ -29,7 +29,7 @@ def assign_object(ifc, aggregator, collector, relating_obj=None, related_obj=Non
if not aggregator.can_aggregate(relating_obj, related_obj):
return
rel = ifc.run(
- "aggregate.assign_object", product=ifc.get_entity(related_obj), relating_object=ifc.get_entity(relating_obj)
+ "aggregate.assign_object", products=[ifc.get_entity(related_obj)], relating_object=ifc.get_entity(relating_obj)
)
collector.assign(relating_obj)
collector.assign(related_obj)
@@ -45,9 +45,9 @@ def unassign_object(ifc, aggregate, collector, relating_obj=None, related_obj=No
if related_element:
relating_obj = ifc.get_object(relating_element)
if relating_obj:
- ifc.run("aggregate.unassign_object", product=related_element)
+ ifc.run("aggregate.unassign_object", products=[related_element])
if container:
- ifc.run("spatial.assign_container", product=related_element, relating_structure=container)
+ ifc.run("spatial.assign_container", products=[related_element], relating_structure=container)
collector.assign(relating_obj)
collector.assign(related_obj)
diff --git a/src/blenderbim/blenderbim/core/brick.py b/src/blenderbim/blenderbim/core/brick.py
index c16b341b1c..58e7afed18 100644
--- a/src/blenderbim/blenderbim/core/brick.py
+++ b/src/blenderbim/blenderbim/core/brick.py
@@ -74,7 +74,7 @@ def assign_brick_reference(ifc, brick, element=None, library=None, brick_uri=Non
if not reference:
reference = ifc.run("library.add_reference", library=library)
ifc.run("library.edit_reference", reference=reference, attributes=brick.export_brick_attributes(brick_uri))
- ifc.run("library.assign_reference", product=element, reference=reference)
+ ifc.run("library.assign_reference", products=[element], reference=reference)
project = brick.get_brickifc_project()
if not project:
project = brick.add_brickifc_project(brick.get_namespace(brick_uri))
diff --git a/src/blenderbim/blenderbim/core/covering.py b/src/blenderbim/blenderbim/core/covering.py
index 49cf4722b4..f98cbe267a 100644
--- a/src/blenderbim/blenderbim/core/covering.py
+++ b/src/blenderbim/blenderbim/core/covering.py
@@ -47,7 +47,6 @@ def add_instance_flooring_covering_from_cursor(ifc, spatial, model, Type, geomet
obj = spatial.get_named_obj_from_mesh(name, mesh)
spatial.set_obj_origin_to_cursor_position(obj)
-# spatial.traslate_obj_to_z_location(obj, z)
spatial.link_obj_to_active_collection(obj)
points = spatial.get_2d_vertices_from_obj(obj)
points = spatial.get_scaled_2d_vertices(points)
@@ -153,7 +152,7 @@ def add_instance_flooring_coverings_from_walls(ifc, spatial, collector, geometry
spatial.regen_obj_representation(ifc, geometry, obj, body)
def add_instance_ceiling_coverings_from_walls(ifc, spatial, collector, geometry, covering):
- z = covering.get_z_from_ceiling_height()
+ z = covering.get_z_from_ceiling_height() + spatial.get_active_obj_z()
union = spatial.get_union_shape_from_selected_objects()
for i, linear_ring in enumerate(union.interiors):
poly = spatial.get_buffered_poly_from_linear_ring(linear_ring)
diff --git a/src/blenderbim/blenderbim/core/document.py b/src/blenderbim/blenderbim/core/document.py
index ce6dc374c6..ce58e1edf4 100644
--- a/src/blenderbim/blenderbim/core/document.py
+++ b/src/blenderbim/blenderbim/core/document.py
@@ -109,8 +109,8 @@ def remove_document(ifc, document_tool, document=None):
def assign_document(ifc, product=None, document=None):
- ifc.run("document.assign_document", product=product, document=document)
+ ifc.run("document.assign_document", products=[product], document=document)
def unassign_document(ifc, product=None, document=None):
- ifc.run("document.unassign_document", product=product, document=document)
+ ifc.run("document.unassign_document", products=[product], document=document)
diff --git a/src/blenderbim/blenderbim/core/drawing.py b/src/blenderbim/blenderbim/core/drawing.py
index 32333dde0c..cccdc16ad1 100644
--- a/src/blenderbim/blenderbim/core/drawing.py
+++ b/src/blenderbim/blenderbim/core/drawing.py
@@ -241,7 +241,7 @@ def add_drawing(ifc, collector, drawing, target_view=None, location_hint=None):
attributes = {"Identification": "X", "Name": drawing_name, "Scope": "DRAWING"}
ifc.run("document.edit_information", information=information, attributes=attributes)
ifc.run("document.edit_reference", reference=reference, attributes={"Location": uri})
- ifc.run("document.assign_document", product=element, document=reference)
+ ifc.run("document.assign_document", products=[element], document=reference)
drawing.import_drawings()
@@ -251,7 +251,7 @@ def duplicate_drawing(ifc, drawing_tool, drawing=None, should_duplicate_annotati
drawing_tool.copy_representation(drawing, new_drawing)
drawing_tool.set_name(new_drawing, drawing_name)
group = drawing_tool.get_drawing_group(new_drawing)
- ifc.run("group.unassign_group", group=group, product=new_drawing)
+ ifc.run("group.unassign_group", group=group, products=[new_drawing])
new_group = ifc.run("group.add_group")
ifc.run("group.edit_group", group=new_group, attributes={"Name": drawing_name, "ObjectType": "DRAWING"})
ifc.run("group.assign_group", group=new_group, products=[new_drawing])
@@ -261,11 +261,11 @@ def duplicate_drawing(ifc, drawing_tool, drawing=None, should_duplicate_annotati
continue
new_annotation = ifc.run("root.copy_class", product=annotation)
drawing_tool.copy_representation(annotation, new_annotation)
- ifc.run("group.unassign_group", group=group, product=new_annotation)
+ ifc.run("group.unassign_group", group=group, products=[new_annotation])
ifc.run("group.assign_group", group=new_group, products=[new_annotation])
old_reference = drawing_tool.get_drawing_document(new_drawing)
- ifc.run("document.unassign_document", product=new_drawing, document=old_reference)
+ ifc.run("document.unassign_document", products=[new_drawing], document=old_reference)
information = ifc.run("document.add_information")
uri = drawing_tool.get_default_drawing_path(drawing_name)
@@ -276,7 +276,7 @@ def duplicate_drawing(ifc, drawing_tool, drawing=None, should_duplicate_annotati
attributes = {"Identification": "X", "Name": drawing_name, "Scope": "DRAWING"}
ifc.run("document.edit_information", information=information, attributes=attributes)
ifc.run("document.edit_reference", reference=reference, attributes={"Location": uri})
- ifc.run("document.assign_document", product=new_drawing, document=reference)
+ ifc.run("document.assign_document", products=[new_drawing], document=reference)
drawing_tool.import_drawings()
return new_drawing
@@ -388,13 +388,13 @@ def sync_references(ifc, collector, drawing_tool, drawing=None):
should_delete_existing_annotation = False
should_create_annotation = False
+ # remove annotation only if the reference object was changed
+ # otherwise we rely on the existing annotation
if annotation:
if reference_obj and (ifc.is_moved(reference_obj) or ifc.is_edited(reference_obj)):
should_delete_existing_annotation = True
- elif not reference_obj:
- should_delete_existing_annotation = True
- if reference_obj and (should_delete_existing_annotation or not annotation):
+ if should_delete_existing_annotation or not annotation:
should_create_annotation = True
if should_delete_existing_annotation:
diff --git a/src/blenderbim/blenderbim/core/geometry.py b/src/blenderbim/blenderbim/core/geometry.py
index 8677152284..6226059ebb 100644
--- a/src/blenderbim/blenderbim/core/geometry.py
+++ b/src/blenderbim/blenderbim/core/geometry.py
@@ -16,10 +16,17 @@
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see .
-import blenderbim.core.style
+from __future__ import annotations
+from typing import TYPE_CHECKING, Optional
+import blenderbim.core.tool as tool
+
+if TYPE_CHECKING:
+ import bpy
-def edit_object_placement(ifc, geometry, surveyor, obj=None):
+def edit_object_placement(
+ ifc: tool.Ifc, geometry: tool.Geometry, surveyor: tool.Surveyor, obj: Optional[bpy.types.Object] = None
+) -> None:
element = ifc.get_entity(obj)
if not element:
return
@@ -192,23 +199,25 @@ def select_connection(geometry, connection=None):
def remove_connection(geometry, connection=None):
geometry.remove_connection(connection)
+
def get_similar_openings(ifc, opening):
model = ifc.get()
all_openings = model.by_type("IfcOpeningElement")
similar_openings = [o for o in all_openings if o.ObjectPlacement == opening.ObjectPlacement and o != opening]
return similar_openings
+
def get_similar_openings_building_objs(ifc, similar_openings):
building_objs = []
for similar_opening in similar_openings:
building_objs.append(ifc.get_object(similar_opening.VoidsElements[0].RelatingBuildingElement))
return building_objs
-def edit_similar_opening_placement(geometry, opening = None, similar_openings = None):
+
+def edit_similar_opening_placement(geometry, opening=None, similar_openings=None):
if not opening or not similar_openings:
return
for similar_opening in similar_openings:
old_placement = similar_opening.ObjectPlacement
similar_opening.ObjectPlacement = opening.ObjectPlacement
geometry.delete_opening_object_placement(old_placement)
-
diff --git a/src/blenderbim/blenderbim/core/library.py b/src/blenderbim/blenderbim/core/library.py
index eba4fc770a..6b5ce7e767 100644
--- a/src/blenderbim/blenderbim/core/library.py
+++ b/src/blenderbim/blenderbim/core/library.py
@@ -83,8 +83,8 @@ def edit_library_reference(ifc, library):
def assign_library_reference(ifc, obj=None, reference=None):
- ifc.run("library.assign_reference", product=ifc.get_entity(obj), reference=reference)
+ ifc.run("library.assign_reference", products=[ifc.get_entity(obj)], reference=reference)
def unassign_library_reference(ifc, obj=None, reference=None):
- ifc.run("library.unassign_reference", product=ifc.get_entity(obj), reference=reference)
+ ifc.run("library.unassign_reference", products=[ifc.get_entity(obj)], reference=reference)
diff --git a/src/blenderbim/blenderbim/core/material.py b/src/blenderbim/blenderbim/core/material.py
index ba1a19a1b0..da265b2a7c 100644
--- a/src/blenderbim/blenderbim/core/material.py
+++ b/src/blenderbim/blenderbim/core/material.py
@@ -102,7 +102,7 @@ def assign_material(ifc, material_tool, material_type, objects):
element = ifc.get_entity(obj)
if not element:
continue
- ifc.run("material.assign_material", product=element, type=material_type, material=material)
+ ifc.run("material.assign_material", products=[element], type=material_type, material=material)
assigned_material = material_tool.get_material(element)
if material_tool.is_a_material_set(assigned_material):
material_tool.add_material_to_set(material_set=assigned_material, material=material)
@@ -116,12 +116,12 @@ def unassign_material(ifc, material_tool, objects):
inherited_material = material_tool.get_material(element, should_inherit=True)
if material and "Usage" in material.is_a():
element_type = material_tool.get_type(element)
- ifc.run("material.unassign_material", product=element_type)
+ ifc.run("material.unassign_material", products=[element_type])
elif not material and inherited_material:
element_type = material_tool.get_type(element)
- ifc.run("material.unassign_material", product=element_type)
+ ifc.run("material.unassign_material", products=[element_type])
elif material:
- ifc.run("material.unassign_material", product=element)
+ ifc.run("material.unassign_material", products=[element])
def patch_non_parametric_mep_segment(ifc, material_tool, profile_tool, obj):
diff --git a/src/blenderbim/blenderbim/core/nest.py b/src/blenderbim/blenderbim/core/nest.py
index 087a1aa5f6..2dec6b2280 100644
--- a/src/blenderbim/blenderbim/core/nest.py
+++ b/src/blenderbim/blenderbim/core/nest.py
@@ -29,7 +29,9 @@ def assign_object(ifc, nest, collector, relating_obj=None, related_obj=None):
if not nest.can_nest(relating_obj, related_obj):
return
rel = ifc.run(
- "nest.assign_object", related_object=ifc.get_entity(related_obj), relating_object=ifc.get_entity(relating_obj)
+ "nest.assign_object",
+ related_objects=[ifc.get_entity(related_obj)],
+ relating_object=ifc.get_entity(relating_obj),
)
collector.assign(relating_obj)
collector.assign(related_obj)
@@ -45,9 +47,9 @@ def unassign_object(ifc, nest, collector, relating_obj=None, related_obj=None):
if related_element:
relating_obj = ifc.get_object(relating_element)
if relating_obj:
- ifc.run("nest.unassign_object", related_object=related_element)
+ ifc.run("nest.unassign_object", related_objects=[related_element])
if container:
- ifc.run("spatial.assign_container", product=related_element, relating_structure=container)
+ ifc.run("spatial.assign_container", products=[related_element], relating_structure=container)
collector.assign(relating_obj)
collector.assign(related_obj)
diff --git a/src/blenderbim/blenderbim/core/resource.py b/src/blenderbim/blenderbim/core/resource.py
index dd3875a04f..63524cd03c 100644
--- a/src/blenderbim/blenderbim/core/resource.py
+++ b/src/blenderbim/blenderbim/core/resource.py
@@ -195,7 +195,7 @@ def add_usage_constraint(ifc, resource_tool, resource=None, reference_path=None)
},
)
ifc.run("constraint.add_metric_reference", metric=metric, reference_path=reference_path)
- ifc.run("constraint.assign_constraint", product=resource, constraint=objective)
+ ifc.run("constraint.assign_constraint", products=[resource], constraint=objective)
def remove_usage_constraint(ifc, resource_tool, resource, reference_path):
@@ -206,7 +206,7 @@ def remove_usage_constraint(ifc, resource_tool, resource, reference_path):
reference = resource_tool.get_metric_reference(metric, is_deep=True)
if reference == reference_path:
ifc.run("constraint.remove_metric", metric=metric)
- ifc.run("constraint.unassign_constraint", product=resource, constraint=constraint)
+ ifc.run("constraint.unassign_constraint", products=[resource], constraint=constraint)
ifc.run("constraint.remove_constraint", constraint=constraint)
diff --git a/src/blenderbim/blenderbim/core/sequence.py b/src/blenderbim/blenderbim/core/sequence.py
index 2fa2debf5d..9f0b5ab903 100644
--- a/src/blenderbim/blenderbim/core/sequence.py
+++ b/src/blenderbim/blenderbim/core/sequence.py
@@ -61,11 +61,11 @@ def remove_work_schedule(ifc, work_schedule=None):
def assign_work_schedule(ifc, work_plan=None, work_schedule=None):
if work_schedule:
- return ifc.run("aggregate.assign_object", relating_object=work_plan, product=work_schedule)
+ return ifc.run("aggregate.assign_object", relating_object=work_plan, products=[work_schedule])
def unassign_work_schedule(ifc, work_schedule=None):
- ifc.run("aggregate.unassign_object", product=work_schedule)
+ ifc.run("aggregate.unassign_object", products=[work_schedule])
def enable_editing_work_schedule(sequence, work_schedule=None):
diff --git a/src/blenderbim/blenderbim/core/spatial.py b/src/blenderbim/blenderbim/core/spatial.py
index 6a7f0806d0..171642900a 100644
--- a/src/blenderbim/blenderbim/core/spatial.py
+++ b/src/blenderbim/blenderbim/core/spatial.py
@@ -16,23 +16,47 @@
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see .
+from __future__ import annotations
+from typing import TYPE_CHECKING, Optional, Union
+import blenderbim.core.tool as tool
-def reference_structure(ifc, spatial, structure=None, element=None):
+if TYPE_CHECKING:
+ import bpy
+ import ifcopenshell
+
+
+def reference_structure(
+ ifc: tool.Ifc,
+ spatial: tool.Spatial,
+ structure: Optional[ifcopenshell.entity_instance] = None,
+ element: Optional[ifcopenshell.entity_instance] = None,
+) -> Union[ifcopenshell.entity_instance, None]:
if spatial.can_reference(structure, element):
- return ifc.run("spatial.reference_structure", product=element, relating_structure=structure)
+ return ifc.run("spatial.reference_structure", products=[element], relating_structure=structure)
-def dereference_structure(ifc, spatial, structure=None, element=None):
+def dereference_structure(
+ ifc: tool.Ifc,
+ spatial: tool.Spatial,
+ structure: Optional[ifcopenshell.entity_instance] = None,
+ element: Optional[ifcopenshell.entity_instance] = None,
+) -> None:
if spatial.can_reference(structure, element):
- return ifc.run("spatial.dereference_structure", product=element, relating_structure=structure)
+ return ifc.run("spatial.dereference_structure", products=[element], relating_structure=structure)
-def assign_container(ifc, collector, spatial, structure_obj=None, element_obj=None):
+def assign_container(
+ ifc: tool.Ifc,
+ collector: tool.Collector,
+ spatial: tool.Spatial,
+ structure_obj: Optional[bpy.types.Object] = None,
+ element_obj: Optional[bpy.types.Object] = None,
+) -> Union[ifcopenshell.entity_instance, None]:
if not spatial.can_contain(structure_obj, element_obj):
return
rel = ifc.run(
"spatial.assign_container",
- product=ifc.get_entity(element_obj),
+ products=[ifc.get_entity(element_obj)],
relating_structure=ifc.get_entity(structure_obj),
)
spatial.disable_editing(element_obj)
@@ -54,7 +78,7 @@ def change_spatial_level(spatial, parent=None):
def remove_container(ifc, collector, obj=None):
- ifc.run("spatial.remove_container", product=ifc.get_entity(obj))
+ ifc.run("spatial.unassign_container", products=[ifc.get_entity(obj)])
collector.assign(obj)
@@ -126,7 +150,7 @@ def select_decomposed_elements(spatial):
spatial.select_products(spatial.get_decomposed_elements(container))
-#HERE STARTS SPATIAL TOOL
+# HERE STARTS SPATIAL TOOL
def generate_space(ifc, spatial, model, Type):
active_obj = spatial.get_active_obj()
selected_objects = spatial.get_selected_objects()
@@ -140,21 +164,20 @@ def generate_space(ifc, spatial, model, Type):
relating_type = None
if selected_objects and active_obj:
- x, y, z, h, mat = spatial.get_x_y_z_h_mat_from_active_obj(active_obj) ##mat
+ x, y, z, h, mat = spatial.get_x_y_z_h_mat_from_active_obj(active_obj) ##mat
element = ifc.get_entity(active_obj)
else:
- x, y, z, h, mat = spatial.get_x_y_z_h_mat_from_cursor() ##mat
-
+ x, y, z, h, mat = spatial.get_x_y_z_h_mat_from_cursor() ##mat
space_polygon = spatial.get_space_polygon_from_context_visible_objects(x, y)
if not space_polygon:
return
- bm = spatial.get_bmesh_from_polygon(space_polygon, h=h) ##mat
+ bm = spatial.get_bmesh_from_polygon(space_polygon, h=h) ##mat
- mesh = spatial.get_named_mesh_from_bmesh(name = "Space", bmesh = bm)
+ mesh = spatial.get_named_mesh_from_bmesh(name="Space", bmesh=bm)
if element and element.is_a("IfcSpace"):
mesh = spatial.get_transformed_mesh_from_local_to_global(mesh)
@@ -175,6 +198,7 @@ def generate_space(ifc, spatial, model, Type):
if relating_type:
spatial.assign_relating_type_to_element(ifc, Type, element, relating_type)
+
def generate_spaces_from_walls(ifc, spatial, collector):
z = spatial.get_active_obj_z()
h = spatial.get_active_obj_height()
@@ -188,7 +212,7 @@ def generate_spaces_from_walls(ifc, spatial, collector):
name = "Space" + str(i)
- obj = spatial.get_named_obj_from_bmesh(name, bmesh = bm)
+ obj = spatial.get_named_obj_from_bmesh(name, bmesh=bm)
spatial.set_obj_origin_to_bboxcenter(obj)
spatial.traslate_obj_to_z_location(obj, z)
@@ -206,6 +230,7 @@ def toggle_space_visibility(ifc, spatial):
return
spatial.toggle_spaces_visibility_wired_and_textured(spaces)
+
def toggle_hide_spaces(ifc, spatial):
model = ifc.get()
spaces = model.by_type("IfcSpace")
diff --git a/src/blenderbim/blenderbim/core/system.py b/src/blenderbim/blenderbim/core/system.py
index 3c6ce42e5a..8015825f1b 100644
--- a/src/blenderbim/blenderbim/core/system.py
+++ b/src/blenderbim/blenderbim/core/system.py
@@ -55,11 +55,11 @@ def disable_editing_system(system):
def assign_system(ifc, system=None, product=None):
- ifc.run("system.assign_system", product=product, system=system)
+ ifc.run("system.assign_system", products=[product], system=system)
def unassign_system(ifc, system=None, product=None):
- ifc.run("system.unassign_system", product=product, system=system)
+ ifc.run("system.unassign_system", products=[product], system=system)
def select_system_products(system_tool, system=None):
diff --git a/src/blenderbim/blenderbim/core/tool.py b/src/blenderbim/blenderbim/core/tool.py
index b82a9159ce..c3688ab283 100644
--- a/src/blenderbim/blenderbim/core/tool.py
+++ b/src/blenderbim/blenderbim/core/tool.py
@@ -76,7 +76,7 @@ class Blender:
def get_name(cls, ifc_class, name): pass
def get_selected_objects(cls): pass
def create_ifc_object(cls, ifc_class: str, name: str = None, data=None): pass
- def get_obj_ifc_definition_id(cls, obj=None, obj_type=None): pass
+ def get_obj_ifc_definition_id(cls, obj=None, obj_type=None, context=None): pass
def is_ifc_object(cls, obj): pass
def is_ifc_class_active(cls, ifc_class): pass
def get_viewport_context(cls): pass
@@ -153,6 +153,17 @@ class Bsdd:
def should_load_preview_domains(cls): pass
+@interface
+class Clash:
+ def export_clash_sets(cls): pass
+ def get_clash(cls, clash_set, a_global_id, b_global_id): pass
+ def get_clash_set(cls, name): pass
+ def get_clash_sets(cls): pass
+ def import_active_clashes(cls): pass
+ def load_clash_sets(cls, fn): pass
+ def look_at(cls, target, location): pass
+
+
@interface
class Collector:
def assign(cls, obj): pass
@@ -508,7 +519,7 @@ class Misc:
class Model:
def convert_si_to_unit(cls, value): pass
def convert_unit_to_si(cls, value): pass
- def export_curve(cls, position, edge_indices, points=None): pass
+ def export_curve(cls, position, edge_indices): pass
def export_points(cls, position, indices): pass
def export_profile(cls, obj, position=None): pass
def generate_occurrence_name(cls, element_type, ifc_class): pass
diff --git a/src/blenderbim/blenderbim/core/type.py b/src/blenderbim/blenderbim/core/type.py
index ec36ad5ffe..969dc3c125 100644
--- a/src/blenderbim/blenderbim/core/type.py
+++ b/src/blenderbim/blenderbim/core/type.py
@@ -20,7 +20,7 @@ import blenderbim.core.geometry
def assign_type(ifc, type_tool, element=None, type=None):
- ifc.run("type.assign_type", related_object=element, relating_type=type)
+ ifc.run("type.assign_type", related_objects=[element], relating_type=type)
obj = ifc.get_object(element)
if type_tool.has_material_usage(element):
pass # for now, representation regeneration handled by API listeners
diff --git a/src/blenderbim/blenderbim/tool/__init__.py b/src/blenderbim/blenderbim/tool/__init__.py
index 4f98a7b5e3..80ea55419f 100644
--- a/src/blenderbim/blenderbim/tool/__init__.py
+++ b/src/blenderbim/blenderbim/tool/__init__.py
@@ -22,6 +22,7 @@ from blenderbim.tool.boundary import Boundary
from blenderbim.tool.brick import Brick
from blenderbim.tool.bsdd import Bsdd
from blenderbim.tool.cad import Cad
+from blenderbim.tool.clash import Clash
from blenderbim.tool.collector import Collector
from blenderbim.tool.context import Context
from blenderbim.tool.debug import Debug
diff --git a/src/blenderbim/blenderbim/tool/aggregate.py b/src/blenderbim/blenderbim/tool/aggregate.py
index 0731c7ffe5..66e549f67d 100644
--- a/src/blenderbim/blenderbim/tool/aggregate.py
+++ b/src/blenderbim/blenderbim/tool/aggregate.py
@@ -16,6 +16,7 @@
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see .
+import bpy
import blenderbim.core.tool
import blenderbim.tool as tool
import ifcopenshell.util.element
@@ -23,7 +24,7 @@ import ifcopenshell.util.element
class Aggregate(blenderbim.core.tool.Aggregate):
@classmethod
- def can_aggregate(cls, relating_obj, related_obj):
+ def can_aggregate(cls, relating_obj: bpy.types.Object, related_obj: bpy.types.Object) -> bool:
relating_object = tool.Ifc.get_entity(relating_obj)
related_object = tool.Ifc.get_entity(related_obj)
if not relating_object or not related_object:
@@ -60,4 +61,4 @@ class Aggregate(blenderbim.core.tool.Aggregate):
def get_relating_object(cls, related_element):
for rel in related_element.Decomposes:
if rel.is_a("IfcRelAggregates"):
- return rel.RelatingObject
\ No newline at end of file
+ return rel.RelatingObject
diff --git a/src/blenderbim/blenderbim/tool/blender.py b/src/blenderbim/blenderbim/tool/blender.py
index 8fe8d30f30..63bec8569f 100644
--- a/src/blenderbim/blenderbim/tool/blender.py
+++ b/src/blenderbim/blenderbim/tool/blender.py
@@ -17,13 +17,17 @@
# along with BlenderBIM Add-on. If not, see .
import bpy
+import bmesh
import json
import ifcopenshell.api
+import ifcopenshell.util.element
import blenderbim.tool as tool
import blenderbim.bim
+import addon_utils
from mathutils import Vector
from pathlib import Path
-import addon_utils
+from blenderbim.bim.ifc import IFC_CONNECTED_TYPE
+from typing import Any, Optional, Union, Literal, Iterable, Callable
VIEWPORT_ATTRIBUTES = [
@@ -37,13 +41,16 @@ VIEWPORT_ATTRIBUTES = [
"clip_planes",
]
+OBJECT_DATA_TYPE = Union[bpy.types.Mesh, bpy.types.Curve, bpy.types.Camera]
+
class Blender(blenderbim.core.tool.Blender):
OBJECT_TYPES_THAT_SUPPORT_EDIT_MODE = ("MESH", "CURVE", "SURFACE", "META", "FONT", "LATTICE", "ARMATURE")
OBJECT_TYPES_THAT_SUPPORT_EDIT_GPENCIL_MODE = ("GPENCIL",)
+ TYPE_MANAGER_ICON = "LIGHTPROBE_VOLUME" if bpy.app.version >= (4, 1, 0) else "LIGHTPROBE_GRID"
@classmethod
- def get_area_props(cls, context):
+ def get_area_props(cls, context: bpy.types.Context) -> Any:
try:
if context.screen.name.endswith("-nonnormal"): # Ctrl-space temporary fullscreen
screen = bpy.data.screens[context.screen.name[0 : -len("-nonnormal")]]
@@ -55,12 +62,12 @@ class Blender(blenderbim.core.tool.Blender):
return
@classmethod
- def set_active_object(cls, obj):
+ def set_active_object(cls, obj: bpy.types.Object) -> None:
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
@classmethod
- def setup_tabs(cls):
+ def setup_tabs(cls) -> None:
# https://blender.stackexchange.com/questions/140644/how-can-make-the-state-of-a-boolean-property-relative-to-the-3d-view-area
for screen in bpy.data.screens:
if len(screen.BIMAreaProperties) == 20:
@@ -70,7 +77,7 @@ class Blender(blenderbim.core.tool.Blender):
screen.BIMAreaProperties.add()
@classmethod
- def is_tab(cls, context, tab):
+ def is_tab(cls, context: bpy.types.Context, tab: str) -> bool:
aprops = cls.get_area_props(context)
if not aprops:
return context.screen.BIMTabProperties.tab == tab
@@ -79,7 +86,7 @@ class Blender(blenderbim.core.tool.Blender):
return aprops.tab == tab
@classmethod
- def is_default_scene(cls):
+ def is_default_scene(cls) -> bool:
if len(bpy.context.scene.objects) != 3:
return False
if {obj.type for obj in bpy.context.scene.objects} == {"MESH", "LIGHT", "CAMERA"}:
@@ -87,7 +94,7 @@ class Blender(blenderbim.core.tool.Blender):
return False
@classmethod
- def get_name(cls, ifc_class, name):
+ def get_name(cls, ifc_class: str, name: str) -> str:
if not bpy.data.objects.get(f"{ifc_class}/{name}"):
return name
i = 2
@@ -96,13 +103,15 @@ class Blender(blenderbim.core.tool.Blender):
return f"{name} {i}"
@classmethod
- def get_selected_objects(cls):
+ def get_selected_objects(cls) -> set[bpy.types.Object]:
if bpy.context.selected_objects:
return set(bpy.context.selected_objects + [bpy.context.active_object])
return set([bpy.context.active_object])
@classmethod
- def create_ifc_object(cls, ifc_class: str, name: str = None, data=None) -> bpy.types.Object:
+ def create_ifc_object(
+ cls, ifc_class: str, name: Optional[str] = None, data: Optional[OBJECT_DATA_TYPE] = None
+ ) -> bpy.types.Object:
name = name or "My " + ifc_class
name = cls.get_name(ifc_class, name)
obj = bpy.data.objects.new(name, data)
@@ -110,7 +119,11 @@ class Blender(blenderbim.core.tool.Blender):
return obj
@classmethod
- def get_obj_ifc_definition_id(cls, obj=None, obj_type=None):
+ def get_obj_ifc_definition_id(
+ cls, obj: Optional[str] = None, obj_type: Optional[str] = None, context: Optional[bpy.types.Context] = None
+ ) -> Union[int, None]:
+ if context is None:
+ context = bpy.context
if obj_type == "Object":
return bpy.data.objects.get(obj).BIMObjectProperties.ifc_definition_id
elif obj_type == "Material":
@@ -122,41 +135,42 @@ class Blender(blenderbim.core.tool.Blender):
elif obj_type == "MaterialSetItem":
return bpy.data.objects.get(obj).BIMObjectMaterialProperties.active_material_set_item_id
elif obj_type == "Task":
- return bpy.context.scene.BIMTaskTreeProperties.tasks[
- bpy.context.scene.BIMWorkScheduleProperties.active_task_index
+ return context.scene.BIMTaskTreeProperties.tasks[
+ context.scene.BIMWorkScheduleProperties.active_task_index
].ifc_definition_id
elif obj_type == "Cost":
- return bpy.context.scene.BIMCostProperties.cost_items[
- bpy.context.scene.BIMCostProperties.active_cost_item_index
+ return context.scene.BIMCostProperties.cost_items[
+ context.scene.BIMCostProperties.active_cost_item_index
].ifc_definition_id
elif obj_type == "Resource":
- return bpy.context.scene.BIMResourceTreeProperties.resources[
- bpy.context.scene.BIMResourceProperties.active_resource_index
+ return context.scene.BIMResourceTreeProperties.resources[
+ context.scene.BIMResourceProperties.active_resource_index
].ifc_definition_id
elif obj_type == "Profile":
- return bpy.context.scene.BIMProfileProperties.profiles[
- bpy.context.scene.BIMProfileProperties.active_profile_index
+ return context.scene.BIMProfileProperties.profiles[
+ context.scene.BIMProfileProperties.active_profile_index
].ifc_definition_id
elif obj_type == "WorkSchedule":
- return bpy.context.scene.BIMWorkScheduleProperties.active_work_schedule_id
+ return context.scene.BIMWorkScheduleProperties.active_work_schedule_id
elif obj_type == "Group":
- prop = bpy.context.scene.BIMGroupProperties
+ prop = context.scene.BIMGroupProperties
return prop.groups[prop.active_group_index].ifc_definition_id
@classmethod
- def is_ifc_object(cls, obj):
+ def is_ifc_object(cls, obj: bpy.types.Object) -> bool:
return bool(obj.BIMObjectProperties.ifc_definition_id)
@classmethod
- def is_ifc_class_active(cls, ifc_class):
- if bpy.context.active_object:
- if cls.is_ifc_object(bpy.context.active_object):
- return tool.Ifc.get_entity(bpy.context.active_object).is_a(ifc_class)
+ def is_ifc_class_active(cls, ifc_class: str) -> bool:
+ obj = bpy.context.active_object
+ if obj:
+ if cls.is_ifc_object(obj):
+ return tool.Ifc.get_entity(obj).is_a(ifc_class)
return False
return False
@classmethod
- def show_info_message(cls, text, message_type="INFO"):
+ def show_info_message(cls, text: str, message_type: Literal["INFO", "ERROR"] = "INFO") -> None:
"""useful for showing error messages outside blender operators
Possible `message_type`: `INFO` / `ERROR`"""
@@ -167,14 +181,14 @@ class Blender(blenderbim.core.tool.Blender):
bpy.context.window_manager.popup_menu(message_ui, title=message_type.capitalize(), icon=message_type)
@classmethod
- def get_view3d_area(cls):
+ def get_view3d_area(cls) -> Union[bpy.types.Area, None]:
for window in bpy.context.window_manager.windows:
for area in window.screen.areas:
if area.type == "VIEW_3D":
return area
@classmethod
- def get_blender_prop_default_value(cls, props, prop_name):
+ def get_blender_prop_default_value(cls, props, prop_name: str) -> Any:
prop_bl_rna = props.bl_rna.properties[prop_name]
if getattr(prop_bl_rna, "array_length", 0) > 0:
prop_value = prop_bl_rna.default_array
@@ -183,7 +197,7 @@ class Blender(blenderbim.core.tool.Blender):
return prop_value
@classmethod
- def get_viewport_context(cls):
+ def get_viewport_context(cls) -> dict:
"""Get viewport area context for context overriding.
Useful for calling operators outside viewport context.
@@ -198,25 +212,25 @@ class Blender(blenderbim.core.tool.Blender):
return context_override
@classmethod
- def get_viewport_position(cls):
+ def get_viewport_position(cls) -> dict:
region_3d = cls.get_viewport_context()["area"].spaces[0].region_3d
copy_if_possible = lambda x: x.copy() if hasattr(x, "copy") else x
viewport_data = {attr: copy_if_possible(getattr(region_3d, attr)) for attr in VIEWPORT_ATTRIBUTES}
return viewport_data
@classmethod
- def set_viewport_position(cls, data):
+ def set_viewport_position(cls, data: dict) -> None:
region_3d = cls.get_viewport_context()["area"].spaces[0].region_3d
for attr in VIEWPORT_ATTRIBUTES:
setattr(region_3d, attr, data[attr])
@classmethod
- def set_viewport_tool(cls, tool_name):
+ def set_viewport_tool(cls, tool_name: str) -> None:
with bpy.context.temp_override(**tool.Blender.get_viewport_context()):
bpy.ops.wm.tool_set_by_id(name=tool_name)
@classmethod
- def get_shader_editor_context(cls):
+ def get_shader_editor_context(cls) -> Union[dict, None]:
for screen in bpy.data.screens:
for area in screen.areas:
if area.type == "NODE_EDITOR":
@@ -226,7 +240,7 @@ class Blender(blenderbim.core.tool.Blender):
return context_override
@classmethod
- def copy_node_graph(cls, material_to, material_from):
+ def copy_node_graph(cls, material_to: bpy.types.Material, material_from: bpy.types.Material) -> None:
# https://projects.blender.org/blender/blender/issues/108763
if bpy.app.version[:2] == (4, 0):
print(
@@ -270,7 +284,9 @@ class Blender(blenderbim.core.tool.Blender):
shader_editor.pin = previous_pin_setting
@classmethod
- def get_material_node(cls, blender_material, node_type, kwargs={}):
+ def get_material_node(
+ cls, blender_material: bpy.types.Material, node_type: str, kwargs: Optional[dict] = {}
+ ) -> Union[bpy.types.ShaderNode, None]:
"""returns first node from the `blender_material` shader graph with type `node_type`"""
if not blender_material.use_nodes:
return
@@ -280,15 +296,15 @@ class Blender(blenderbim.core.tool.Blender):
return node
@classmethod
- def update_screen(cls):
+ def update_screen(cls) -> None:
bpy.ops.wm.redraw_timer(type="DRAW_WIN_SWAP", iterations=1)
@classmethod
- def update_viewport(cls):
+ def update_viewport(cls) -> None:
tool.Blender.get_viewport_context()["area"].tag_redraw()
@classmethod
- def force_depsgraph_update(cls):
+ def force_depsgraph_update(cls) -> None:
"""useful if you need to trigger callbacks like `depsgraph_update_pre`"""
# blender is requiring some ID to be changed
# to trigger depsgraph update
@@ -297,9 +313,11 @@ class Blender(blenderbim.core.tool.Blender):
bpy.context.view_layer.update()
@classmethod
- def ensure_unique_name(cls, name, objects, iteration=0):
+ def ensure_unique_name(cls, name: str, objects: Iterable[str], iteration=0) -> str:
"""returns a unique name for the given name and dictionary of objects
- blender style name with .001, .002, etc. suffix
+ blender style name with .001, .002, etc. suffix.
+
+ objects can be `bpy.data.objects`.
"""
current_iteration = name if not iteration else f"{name}.{iteration:03d}"
if current_iteration not in objects:
@@ -307,7 +325,7 @@ class Blender(blenderbim.core.tool.Blender):
return cls.ensure_unique_name(name, objects, iteration + 1)
@classmethod
- def blender_path_to_posix(cls, blender_path):
+ def blender_path_to_posix(cls, blender_path: str) -> str:
"""Process blender path to be saved as posix.
If path is relative the method will keep it relative to .ifc file
@@ -322,7 +340,7 @@ class Blender(blenderbim.core.tool.Blender):
return path.as_posix()
@classmethod
- def get_default_selection_keypmap(cls):
+ def get_default_selection_keypmap(cls) -> tuple:
"""keymap to replicate default blender selection behaviour with click and box selection"""
# code below comes from blender_default.py which is part of default blender scripts licensed under GPL v2
# https://github.com/blender/blender/blob/master/release/scripts/presets/keyconfig/keymap_data/blender_default.py
@@ -369,7 +387,9 @@ class Blender(blenderbim.core.tool.Blender):
return keymap
@classmethod
- def add_layout_hotkey_operator(cls, tool_name, layout, text, hotkey, description):
+ def add_layout_hotkey_operator(
+ cls, tool_name: str, layout: bpy.types.UILayout, text: str, hotkey: str, description: str
+ ) -> tuple[bpy.types.OperatorProperties, bpy.types.UILayout]:
modifiers = {
"A": "EVENT_ALT",
"S": "EVENT_SHIFT",
@@ -386,7 +406,7 @@ class Blender(blenderbim.core.tool.Blender):
return op, row
@classmethod
- def get_object_bounding_box(cls, obj):
+ def get_object_bounding_box(cls, obj: bpy.types.Object) -> dict:
"""Returns dict with local min and max x, y, z values for the object.
Careful with using this method for objects in EDIT mode because
@@ -407,14 +427,20 @@ class Blender(blenderbim.core.tool.Blender):
return bbox_dict
@classmethod
- def select_and_activate_single_object(cls, context, active_object):
+ def select_and_activate_single_object(cls, context: bpy.types.Context, active_object: bpy.types.Object) -> None:
for obj in context.selected_objects:
obj.select_set(False)
context.view_layer.objects.active = active_object
active_object.select_set(True)
@classmethod
- def set_objects_selection(cls, context, active_object, selected_objects, clear_previous_selection=True):
+ def set_objects_selection(
+ cls,
+ context: bpy.types.Context,
+ active_object: Optional[bpy.types.Object] = None,
+ selected_objects: list[bpy.types.Object] = list(),
+ clear_previous_selection=True,
+ ) -> None:
if clear_previous_selection:
for obj in context.selected_objects:
obj.select_set(False)
@@ -425,7 +451,7 @@ class Blender(blenderbim.core.tool.Blender):
active_object.select_set(True)
@classmethod
- def enum_property_has_valid_index(cls, props, prop_name, enum_items):
+ def enum_property_has_valid_index(cls, props, prop_name: str, enum_items: tuple) -> bool:
"""method created for readibility and to avoid console warnings like
`pyrna_enum_to_py: current value '17' matches no enum in 'BIMModelProperties', '', 'relating_type_id'`
"""
@@ -436,7 +462,7 @@ class Blender(blenderbim.core.tool.Blender):
return current_value_index < len(enum_items)
@classmethod
- def append_data_block(cls, filepath, data_block_type, name, link=False, relative=False):
+ def append_data_block(cls, filepath: str, data_block_type: str, name: str, link=False, relative=False) -> dict:
if Path(filepath) == Path(bpy.data.filepath):
data_block = getattr(bpy.data, data_block_type).get(name, None)
if not data_block:
@@ -450,7 +476,7 @@ class Blender(blenderbim.core.tool.Blender):
return {"data_block": getattr(data_to, data_block_type)[0], "msg": ""}
@classmethod
- def remove_data_block(cls, data_block, do_unlink=True):
+ def remove_data_block(cls, data_block: bpy.types.ID, do_unlink=True) -> None:
"""Removes a datablock (such as a mesh)
See https://projects.blender.org/blender/blender/issues/118787 for more
@@ -472,7 +498,7 @@ class Blender(blenderbim.core.tool.Blender):
## BMESH UTILS ##
@classmethod
- def apply_bmesh(cls, mesh, bm, obj=None):
+ def apply_bmesh(cls, mesh: bpy.types.Mesh, bm: bmesh.types.BMesh, obj: Optional[bpy.types.Object] = None) -> None:
"""`obj` argument is not optional if you plan to update mesh in EDIT mode
and it's possible that that mesh object won't be currenly active."""
import bmesh
@@ -506,7 +532,7 @@ class Blender(blenderbim.core.tool.Blender):
mesh.update()
@classmethod
- def get_bmesh_for_mesh(cls, mesh, clean=False):
+ def get_bmesh_for_mesh(cls, mesh: bpy.types.Mesh, clean=False) -> bmesh.types.BMesh:
import bmesh
if mesh.is_editmode:
@@ -520,7 +546,16 @@ class Blender(blenderbim.core.tool.Blender):
return bm
@classmethod
- def bmesh_join(cls, bm_a, bm_b, callback=None):
+ def bmesh_join(
+ cls,
+ bm_a: bmesh.types.BMesh,
+ bm_b: bmesh.types.BMesh,
+ callback: Optional[
+ Callable[
+ [bmesh.types.BMesh, list[bmesh.types.BMVert], list[bmesh.types.BMEdge], list[bmesh.types.BMFace]], None
+ ]
+ ] = None,
+ ):
"""Join two meshes into single one, store it in `bm_a`"""
import bmesh
@@ -535,7 +570,19 @@ class Blender(blenderbim.core.tool.Blender):
return bm_a
@classmethod
- def toggle_edit_mode(cls, context):
+ def bmesh_check_vertex_in_groups(
+ cls, vertex: bmesh.types.BMVert, deform_layer: bmesh.types.BMLayerItem, groups: list[int]
+ ) -> Union[tuple[Literal[True], int], tuple[Literal[False], None]]:
+ """returns tuple boolean (whether vertex is in any of the groups) and related group index"""
+ for group_index in vertex[deform_layer].keys():
+ # ignore vertex groups assignments produced by edge subdivision near arcs
+ # they usually have weight = 0.5
+ if group_index in groups and vertex[deform_layer][group_index] == 1.0:
+ return True, group_index
+ return False, None
+
+ @classmethod
+ def toggle_edit_mode(cls, context: bpy.types.Context) -> set:
ao = context.active_object
if not ao:
return {"CANCELLED"}
@@ -547,21 +594,21 @@ class Blender(blenderbim.core.tool.Blender):
return {"CANCELLED"}
@classmethod
- def is_object_an_ifc_class(cls, obj, classes):
+ def is_object_an_ifc_class(cls, obj: bpy.types.Object, classes: Iterable[str]) -> bool:
if not tool.Ifc.get():
return False
element = tool.Ifc.get_entity(obj)
return element and element.is_a() in classes
@classmethod
- def get_object_from_guid(cls, guid):
+ def get_object_from_guid(cls, guid: str) -> Union[IFC_CONNECTED_TYPE, None]:
element = tool.Ifc.get().by_guid(guid)
obj = tool.Ifc.get_object(element)
if obj:
return obj
@classmethod
- def lock_transform(cls, obj, lock_state=True):
+ def lock_transform(cls, obj: bpy.types.ObjectBase, lock_state=True) -> None:
for prop in ("lock_location", "lock_rotation", "lock_scale"):
attr = getattr(obj, prop)
for axis_idx in range(3):
@@ -570,7 +617,9 @@ class Blender(blenderbim.core.tool.Blender):
operator_invoke_filepath_hotkeys_description = "Hold Shift to open the file, Alt to browse containing directory"
@classmethod
- def operator_invoke_filepath_hotkeys(cls, operator, context, event, filepath: Path):
+ def operator_invoke_filepath_hotkeys(
+ cls, operator: bpy.types.Operator, context: bpy.types.Context, event: bpy.types.Event, filepath: Path
+ ) -> Union[set, None]:
if not event.alt and not event.shift:
return
@@ -611,6 +660,43 @@ class Blender(blenderbim.core.tool.Blender):
open_file_or_folder(filepath.as_posix())
return {"PASS_THROUGH"}
+ @classmethod
+ def get_layer_collection(
+ cls, collection: bpy.types.Collection, view_layer: Optional[bpy.types.ViewLayer] = None
+ ) -> Union[bpy.types.LayerCollection, None]:
+ return cls.get_layer_collections_mapping([collection], view_layer).get(collection)
+
+ @classmethod
+ def get_layer_collections_mapping(
+ cls, collections: list[bpy.types.Collection], view_layer: Optional[bpy.types.ViewLayer] = None
+ ) -> dict[bpy.types.Collection, bpy.types.LayerCollection]:
+ if view_layer is None:
+ view_layer = bpy.context.view_layer
+
+ collections = list(collections) # copy to prevent mutation
+ collections_mapping = dict()
+ queue = [view_layer.layer_collection]
+
+ while queue:
+ layer = queue.pop()
+ collection = layer.collection
+ if collection in collections:
+ collections_mapping[collection] = layer
+ collections.remove(collection)
+ if not collections:
+ break
+ queue.extend(list(layer.children))
+
+ return collections_mapping
+
+ @classmethod
+ def object_supports_edit_mode(cls, obj):
+ if not obj.data:
+ return False, "Can't Edit Empty Object"
+ if obj in bpy.context.scene.BIMProjectProperties.clipping_planes_objs:
+ return False, "Can't Edit Clipping Plane Geometry"
+ return True, ""
+
class Modifier:
@classmethod
def is_eligible_for_railing_modifier(cls, obj):
@@ -712,8 +798,7 @@ class Blender(blenderbim.core.tool.Blender):
@classmethod
def get_modifiers_data(cls, parent_element):
array_pset = ifcopenshell.util.element.get_pset(parent_element, "BBIM_Array")
- for modifier_data in json.loads(array_pset["Data"]):
- yield modifier_data
+ yield from json.loads(array_pset["Data"])
@classmethod
def get_children_objects(cls, modifier_data):
@@ -760,7 +845,9 @@ class Blender(blenderbim.core.tool.Blender):
bpy.utils.register_tool(ws_model.PipeTool, after={"bim.duct_tool"}, separator=False, group=False)
bpy.utils.register_tool(ws_model.BimTool, after={"bim.pipe_tool"}, separator=False, group=False)
bpy.utils.register_tool(ws_drawing.AnnotationTool, after={"bim.bim_tool"}, separator=True, group=False)
- bpy.utils.register_tool(ws_spatial.SpatialTool, after={"bim.annotation_tool"}, separator=False, group=False)
+ bpy.utils.register_tool(
+ ws_spatial.SpatialTool, after={"bim.annotation_tool"}, separator=False, group=False
+ )
bpy.utils.register_tool(
ws_structural.StructuralTool, after={"bim.spatial_tool"}, separator=False, group=False
)
@@ -797,3 +884,105 @@ class Blender(blenderbim.core.tool.Blender):
bpy.utils.unregister_tool(ws_covering.CoveringTool)
except:
pass
+
+ @classmethod
+ def get_scene_panels_list(cls) -> tuple[bpy.types.Panel, ...]:
+ # example default blender scene panels can be found in
+ # https://projects.blender.org/blender/blender/src/branch/main/scripts/startup/bl_ui/properties_scene.py#L421
+ scene_panels: list[str] = []
+ panels_to_parents: dict[str, str] = dict()
+ for item_name in dir(bpy.types):
+ item = getattr(bpy.types, item_name)
+ # filter only panels
+ if not hasattr(item, "bl_rna") or not isinstance(item.bl_rna, bpy.types.Panel):
+ continue
+ # ignore bbim panels
+ if item.__module__.startswith("blenderbim"):
+ continue
+ # filter scene panels
+ if getattr(item, "bl_context", None) != "scene":
+ continue
+ scene_panels.append(item_name)
+ parent_panel = getattr(item, "bl_parent_id", None)
+ if parent_panel is not None:
+ panels_to_parents[item_name] = parent_panel
+
+ scene_panels = cls.sort_panels_for_register(scene_panels, panels_to_parents)
+ final_panels = [getattr(bpy.types, p) for p in scene_panels]
+ return tuple(final_panels)
+
+ @classmethod
+ def sort_panels_for_register(cls, items: list[str], items_to_parents: dict[str, str]) -> list[str]:
+ """sort panels ensuring parents panels will be registered first
+ as otherwise we'll get errors unregistering them all and registering child panel"""
+ final_items = []
+ unsorted = items.copy()
+
+ # first, add items without parents
+ for item in unsorted[:]:
+ if item not in items_to_parents:
+ final_items.append(item)
+ unsorted.remove(item)
+
+ # store children for each parent
+ children: dict[str, list[str]] = dict()
+ for item in items_to_parents:
+ children.setdefault(items_to_parents[item], []).append(item)
+
+ # add children recursively, ensuring parents are added first
+ keep_looking = True
+ while keep_looking:
+ keep_looking = False
+ for item in list(children.keys()):
+ # check if parent panel was already added
+ if item not in final_items:
+ continue
+ final_items.extend(children[item])
+ del children[item]
+ keep_looking = True
+
+ assert set(items) == set(final_items), "Sorted list doesn't match original"
+ return final_items
+
+ @classmethod
+ def override_scene_panel(cls, original_panel: bpy.types.Panel) -> None:
+ @classmethod
+ def poll_check_blender_tab(cls, context):
+ return tool.Blender.is_tab(context, "BLENDER")
+
+ polls = blenderbim.bim.original_scene_panels_polls
+
+ # override poll method
+ if not hasattr(original_panel, "poll"):
+ polls[original_panel] = None
+ original_panel.poll = poll_check_blender_tab
+ else:
+ polls[original_panel] = original_panel.poll
+
+ @classmethod
+ def wrapped_poll(cls, context):
+ return polls[cls](context) and poll_check_blender_tab.__func__(cls, context)
+
+ original_panel.poll = wrapped_poll
+
+ # reregister to activate new poll
+ bpy.utils.unregister_class(original_panel)
+ bpy.utils.register_class(original_panel)
+
+ @classmethod
+ def remove_scene_panel_override(cls, panel: bpy.types.Panel) -> None:
+ polls = blenderbim.bim.original_scene_panels_polls
+
+ poll = polls[panel]
+ if poll is None:
+ del panel.poll
+ else:
+ panel.poll = poll
+
+ # panel might be already unregistered during blender exit
+ # or if it's addon was disabled
+ if panel.is_registered:
+ # reregister to activate new poll
+ bpy.utils.unregister_class(panel)
+ bpy.utils.register_class(panel)
+ del polls[panel]
diff --git a/src/blenderbim/blenderbim/tool/boundary.py b/src/blenderbim/blenderbim/tool/boundary.py
index c922a23462..1f47e3eb82 100644
--- a/src/blenderbim/blenderbim/tool/boundary.py
+++ b/src/blenderbim/blenderbim/tool/boundary.py
@@ -72,7 +72,11 @@ class Boundary(blenderbim.core.tool.Boundary):
obj.matrix_world = space.matrix_world
@classmethod
- def decorate_boundary(cls, obj):
+ def decorate_boundary(cls, obj: bpy.types.Object) -> None:
new = bpy.context.scene.BIMBoundaryProperties.boundaries.add()
new.obj = obj
obj.show_in_front = True
+
+ @classmethod
+ def undecorate_boundary(cls, obj: bpy.types.Object) -> None:
+ obj.show_in_front = False
diff --git a/src/blenderbim/blenderbim/tool/clash.py b/src/blenderbim/blenderbim/tool/clash.py
new file mode 100644
index 0000000000..c83c3f9deb
--- /dev/null
+++ b/src/blenderbim/blenderbim/tool/clash.py
@@ -0,0 +1,117 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2024 Dion Moult
+#
+# 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 .
+
+import os
+import bpy
+import json
+import ifcopenshell
+import blenderbim.core.tool
+import blenderbim.tool as tool
+from contextlib import contextmanager
+from mathutils import Vector
+
+
+class Clash(blenderbim.core.tool.Clash):
+
+ @classmethod
+ def export_clash_sets(cls):
+ clash_sets = []
+ for clash_set in bpy.context.scene.BIMClashProperties.clash_sets:
+ a = []
+ b = []
+ for ab in ["a", "b"]:
+ for data in getattr(clash_set, ab):
+ clash_source = {"file": data.name}
+ query = tool.Search.export_filter_query(data.filter_groups)
+ if query and data.mode != "a":
+ clash_source["selector"] = query
+ clash_source["mode"] = data.mode
+ if ab == "a":
+ a.append(clash_source)
+ elif ab == "b":
+ b.append(clash_source)
+ clash_set_data = {"name": clash_set.name, "mode": clash_set.mode, "a": a, "b": b}
+ if clash_set.mode == "intersection":
+ clash_set_data["tolerance"] = clash_set.tolerance
+ clash_set_data["check_all"] = clash_set.check_all
+ elif clash_set.mode == "collision":
+ clash_set_data["allow_touching"] = clash_set.allow_touching
+ elif clash_set.mode == "clearance":
+ clash_set_data["clearance"] = clash_set.clearance
+ clash_set_data["check_all"] = clash_set.check_all
+ clash_sets.append(clash_set_data)
+ return clash_sets
+
+ @classmethod
+ def get_clash(cls, clash_set, a_global_id, b_global_id):
+ clashes = clash_set.get("clashes", None)
+ if not clashes:
+ return
+ return clashes.get(f"{a_global_id}-{b_global_id}", None)
+
+ @classmethod
+ def get_clash_set(cls, name):
+ for clash_set in ClashStore.clash_sets:
+ if clash_set["name"] == name:
+ return clash_set
+
+ @classmethod
+ def get_clash_sets(cls):
+ return ClashStore.clash_sets
+
+ @classmethod
+ def import_active_clashes(cls):
+ clash_set = bpy.context.scene.BIMClashProperties.active_clash_set
+ if not clash_set:
+ return
+ clash_set.clashes.clear()
+ result = tool.Clash.get_clash_set(clash_set.name)
+ for clash in sorted(result.get("clashes", {}).values(), key=lambda x: x["distance"]):
+ blender_clash = clash_set.clashes.add()
+ blender_clash.a_global_id = clash["a_global_id"]
+ blender_clash.b_global_id = clash["b_global_id"]
+ blender_clash.a_name = "{}/{}".format(clash["a_ifc_class"], clash["a_name"])
+ blender_clash.b_name = "{}/{}".format(clash["b_ifc_class"], clash["b_name"])
+ blender_clash.status = False if not "status" in clash.keys() else clash["status"]
+
+ @classmethod
+ def load_clash_sets(cls, fn):
+ with open(fn) as f:
+ ClashStore.clash_sets = json.load(f)
+
+ @classmethod
+ def look_at(cls, target, location):
+ camera_location = location
+ area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D")
+ region = next(region for region in area.regions if region.type == "WINDOW")
+ space = next(space for space in area.spaces if space.type == "VIEW_3D")
+ override = {"area": area, "region": region, "space_data": space}
+ space.region_3d.view_location = target
+ space.region_3d.view_rotation = Vector((camera_location - target)).to_track_quat("Z", "Y")
+ space.region_3d.view_distance = (camera_location - target).length
+ space.shading.show_xray = True
+
+
+class ClashStore:
+ clash_sets = None
+ path = None
+
+ @staticmethod
+ def purge():
+ ClashStore.clash_sets = None
+ ClashStore.path = None
diff --git a/src/blenderbim/blenderbim/tool/collector.py b/src/blenderbim/blenderbim/tool/collector.py
index 841a515cc7..095d56ea1d 100644
--- a/src/blenderbim/blenderbim/tool/collector.py
+++ b/src/blenderbim/blenderbim/tool/collector.py
@@ -20,12 +20,24 @@ import bpy
import ifcopenshell.util.element
import blenderbim.core.tool
+import blenderbim.core.spatial
+import blenderbim.core.aggregate
import blenderbim.tool as tool
+from typing import Union
class Collector(blenderbim.core.tool.Collector):
@classmethod
- def sync(cls, obj):
+ def sync(cls, obj: bpy.types.Object, skip_unlinking=False) -> None:
+ """Sync object IFC state (assigned containter / aggregate) with the collection it's currently in.
+
+ Then subsequently run `Collector.assign` (if state has changed)
+ to link them to collections / unlink from anything unrelated collections.
+
+ If `skip_unlinking` is `True` then method won't try to assign parent object
+ if it's already assigned in IFC saving some time.
+ But it has a downside not unlinking object from unrelated collections.
+ """
# This is the reverse of assign. It reads the Blender collection and figures out its IFC hierarchy
element = tool.Ifc.get_entity(obj)
@@ -40,48 +52,57 @@ class Collector(blenderbim.core.tool.Collector):
if not obj.users_collection:
return
- object_collection = None
- collection_collection = None
-
- object_collection = cls._get_own_collection(element, obj)
- if object_collection:
- collection_collection = cls._get_collection(element, obj)
- else:
- object_collection = cls._get_collection(element, obj)
+ # create related collections
+ cls._get_own_collection(element, obj)
+ cls._get_collection(element, obj)
parent_collection = None
for collection in obj.users_collection:
if parent_collection:
break
+ # skip Types and non-BIM collections
if not collection.BIMCollectionProperties.obj:
continue
+ # for objects that own collections we search for the first parent collection
if collection.BIMCollectionProperties.obj == obj:
+ collection_name = collection.name
for bpy_collection in bpy.data.collections:
- if bpy_collection.children.find(collection.name) != -1:
+ if bpy_collection.children.get(collection_name) and bpy_collection.BIMCollectionProperties.obj:
parent_collection = bpy_collection
+ parent_obj = bpy_collection.BIMCollectionProperties.obj
+ break
else:
parent_collection = collection
+ parent_obj = collection.BIMCollectionProperties.obj
if not parent_collection:
return
- parent_obj = parent_collection.BIMCollectionProperties.obj
- if not parent_obj:
- return
-
parent = tool.Ifc.get_entity(parent_obj)
+ if skip_unlinking:
+ previous_parent = ifcopenshell.util.element.get_container(
+ element, should_get_direct=True
+ ) or ifcopenshell.util.element.get_aggregate(element)
+ if parent == previous_parent:
+ return
+
if parent:
# This is lazy, but works. One of these will succeed, the other will fail silently.
blenderbim.core.spatial.assign_container(
tool.Ifc, tool.Collector, tool.Spatial, structure_obj=parent_obj, element_obj=obj
)
+ # NOTE: won't allow assigning IfcElements to the IfcProject directly
+ # and some elements might get missing in other viewers if they're don't support displaying
+ # elements without hierarchy
blenderbim.core.aggregate.assign_object(
tool.Ifc, tool.Aggregate, tool.Collector, relating_obj=parent_obj, related_obj=obj
)
@classmethod
- def assign(cls, obj):
+ def assign(cls, obj: bpy.types.Object) -> None:
+ """link object and it's owned collection to the proper collection
+ and unlink them from any other"""
element = tool.Ifc.get_entity(obj)
object_collection = None
@@ -93,12 +114,17 @@ class Collector(blenderbim.core.tool.Collector):
else:
object_collection = cls._get_collection(element, obj)
+ # NOTE: calling `obj.users_collection` is expensive in large projects
+ # since it's iterating over all collections under the hood
+
+ # ensure object is linked only to object_collection
if obj.users_collection != (object_collection,):
for collection in obj.users_collection:
collection.objects.unlink(obj)
if object_collection is not None:
object_collection.objects.link(obj)
+ # ensure object_collection is linked only to collection_collection
if collection_collection and collection_collection.children.find(object_collection.name) == -1:
if bpy.context.scene.collection.children.find(object_collection.name) != -1:
bpy.context.scene.collection.children.unlink(object_collection)
@@ -112,7 +138,10 @@ class Collector(blenderbim.core.tool.Collector):
bpy.data.collections.remove(obj.BIMObjectProperties.collection)
@classmethod
- def _get_own_collection(cls, element, obj):
+ def _get_own_collection(
+ cls, element: ifcopenshell.entity_instance, obj: bpy.types.Object
+ ) -> Union[bpy.types.Collection, None]:
+ """get or create own collection for the element if it's neccessary for it's type"""
if element.is_a("IfcProject"):
return cls._create_own_collection(obj)
@@ -139,10 +168,8 @@ class Collector(blenderbim.core.tool.Collector):
grid_obj = tool.Ifc.get_object(grid)
if grid_obj:
grid_col = cls._get_own_collection(grid, grid_obj)
- axes_col = [c for c in grid_col.children if axes in c.name]
- if axes_col:
- return axes_col[0]
- return bpy.data.collections.new(axes)
+ axes_col = next((c for c in grid_col.children if axes in c.name), None)
+ return axes_col or bpy.data.collections.new(axes)
if element.is_a("IfcAnnotation") and element.ObjectType == "DRAWING":
return cls._create_own_collection(obj)
@@ -157,11 +184,12 @@ class Collector(blenderbim.core.tool.Collector):
return cls._create_own_collection(obj)
if getattr(element, "IsNestedBy", None):
- if [e for e in element.IsNestedBy[0].RelatedObjects if not e.is_a("IfcPort")]:
+ if any(e for e in element.IsNestedBy[0].RelatedObjects if not e.is_a("IfcPort")):
return cls._create_own_collection(obj)
@classmethod
- def _get_collection(cls, element, obj):
+ def _get_collection(cls, element: ifcopenshell.entity_instance, obj: bpy.types.Object) -> bpy.types.Collection:
+ """get or create collection for the element based on it's type"""
if element.is_a("IfcTypeObject"):
return cls._create_project_child_collection("Types")
@@ -224,12 +252,11 @@ class Collector(blenderbim.core.tool.Collector):
project_obj = tool.Ifc.get_object(tool.Ifc.get().by_type("IfcProject")[0])
if project_obj:
- collection = bpy.data.collections.get(project_obj.name)
- if collection:
- return collection
+ return project_obj.BIMObjectProperties.collection
@classmethod
- def _create_project_child_collection(cls, name):
+ def _create_project_child_collection(cls, name: str) -> bpy.types.Collection:
+ """get or create new collection inside project"""
collection = bpy.data.collections.get(name)
if not collection:
collection = bpy.data.collections.new(name)
@@ -239,7 +266,8 @@ class Collector(blenderbim.core.tool.Collector):
return collection
@classmethod
- def _create_own_collection(cls, obj):
+ def _create_own_collection(cls, obj: bpy.types.Object) -> bpy.types.Collection:
+ """get or create own collection for the element"""
if obj.BIMObjectProperties.collection:
return obj.BIMObjectProperties.collection
collection = bpy.data.collections.new(obj.name)
diff --git a/src/blenderbim/blenderbim/tool/drawing.py b/src/blenderbim/blenderbim/tool/drawing.py
index e59a40868f..d1d7e9649a 100644
--- a/src/blenderbim/blenderbim/tool/drawing.py
+++ b/src/blenderbim/blenderbim/tool/drawing.py
@@ -18,6 +18,7 @@
import os
import re
+import collections
import bpy
import math
import json
@@ -27,29 +28,36 @@ import shutil
import logging
import shapely
import platform
-from shapely.ops import unary_union
import mathutils
import subprocess
import numpy as np
import blenderbim.core.tool
import blenderbim.core.geometry
import blenderbim.tool as tool
+import ifcopenshell.api
+import ifcopenshell.geom
import ifcopenshell.util.representation
+import ifcopenshell.util.element
+import ifcopenshell.util.selector
+import blenderbim.bim.import_ifc
import blenderbim.bim.module.drawing.sheeter as sheeter
import blenderbim.bim.module.drawing.scheduler as scheduler
import blenderbim.bim.module.drawing.annotation as annotation
import blenderbim.bim.module.drawing.helper as helper
+import blenderbim.core.root
+from shapely.ops import unary_union
from blenderbim.bim.module.drawing.data import FONT_SIZES, DecoratorData
from blenderbim.bim.module.drawing.prop import get_diagram_scales, BOX_ALIGNMENT_POSITIONS, ANNOTATION_TYPES_DATA
from lxml import etree
-from mathutils import Vector
+from mathutils import Vector, Matrix
from fractions import Fraction
-import collections
+from typing import Optional, Union, Iterable, Any
+from pathlib import Path
class Drawing(blenderbim.core.tool.Drawing):
@classmethod
- def canonicalise_class_name(self, name):
+ def canonicalise_class_name(cls, name):
return re.sub("[^0-9a-zA-Z]+", "", name)
@classmethod
@@ -244,7 +252,7 @@ class Drawing(blenderbim.core.tool.Drawing):
bpy.data.collections.remove(collection, do_unlink=True)
@classmethod
- def delete_drawing_elements(cls, elements):
+ def delete_drawing_elements(cls, elements: Iterable[ifcopenshell.entity_instance]) -> None:
for element in elements:
obj = tool.Ifc.get_object(element)
ifcopenshell.api.run("root.remove_product", tool.Ifc.get(), product=element)
@@ -255,7 +263,7 @@ class Drawing(blenderbim.core.tool.Drawing):
tool.Blender.remove_data_block(obj_data)
@classmethod
- def delete_object(cls, obj):
+ def delete_object(cls, obj: bpy.types.Object) -> None:
bpy.data.objects.remove(obj)
@classmethod
@@ -334,7 +342,7 @@ class Drawing(blenderbim.core.tool.Drawing):
return identification
@classmethod
- def export_text_literal_attributes(cls, obj):
+ def export_text_literal_attributes(cls, obj: bpy.types.Object) -> list[dict[str, Any]]:
literals = []
for literal_props in obj.BIMTextProperties.literals:
literal_data = blenderbim.bim.helper.export_attributes(literal_props.attributes)
@@ -342,7 +350,9 @@ class Drawing(blenderbim.core.tool.Drawing):
return literals
@classmethod
- def create_annotation_context(cls, target_view, object_type=None):
+ def create_annotation_context(
+ cls, target_view: str, object_type: Optional[str] = None
+ ) -> ifcopenshell.entity_instance:
# checking PLAN target view and annotation type that doesn't require 3d
if target_view in ("PLAN_VIEW", "REFLECTED_PLAN_VIEW") and object_type not in (
"FALL",
@@ -363,7 +373,9 @@ class Drawing(blenderbim.core.tool.Drawing):
)
@classmethod
- def get_annotation_context(cls, target_view, object_type=None):
+ def get_annotation_context(
+ cls, target_view: str, object_type: Optional[str] = None
+ ) -> Union[ifcopenshell.entity_instance, None]:
# checking PLAN target view and annotation type that doesn't require 3d
if target_view in ("PLAN_VIEW", "REFLECTED_PLAN_VIEW") and object_type not in (
"FALL",
@@ -378,7 +390,9 @@ class Drawing(blenderbim.core.tool.Drawing):
return ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW")
@classmethod
- def get_document_uri(cls, document, description=None):
+ def get_document_uri(
+ cls, document: ifcopenshell.entity_instance, description: Optional[str] = None
+ ) -> Union[str, None]:
if getattr(document, "Location", None):
if os.path.isabs(document.Location):
return document.Location
@@ -417,13 +431,13 @@ class Drawing(blenderbim.core.tool.Drawing):
return obj.BIMObjectProperties.collection
@classmethod
- def get_drawing_group(cls, drawing):
+ def get_drawing_group(cls, drawing: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
for rel in drawing.HasAssignments or []:
if rel.is_a("IfcRelAssignsToGroup") and rel.RelatingGroup.ObjectType == "DRAWING":
return rel.RelatingGroup
@classmethod
- def get_drawing_document(cls, drawing):
+ def get_drawing_document(cls, drawing: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
for rel in drawing.HasAssociations:
if rel.is_a("IfcRelAssociatesDocument"):
return rel.RelatingDocument
@@ -437,11 +451,11 @@ class Drawing(blenderbim.core.tool.Drawing):
return results
@classmethod
- def get_drawing_target_view(cls, drawing):
+ def get_drawing_target_view(cls, drawing: ifcopenshell.entity_instance) -> str:
return ifcopenshell.util.element.get_psets(drawing).get("EPset_Drawing", {}).get("TargetView", "MODEL_VIEW")
@classmethod
- def get_group_elements(cls, group):
+ def get_group_elements(cls, group: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
for rel in group.IsGroupedBy or []:
return rel.RelatedObjects
@@ -584,13 +598,13 @@ class Drawing(blenderbim.core.tool.Drawing):
return ifc_literal
@classmethod
- def get_assigned_product(cls, element):
+ def get_assigned_product(cls, element: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]:
for rel in element.HasAssignments:
if rel.is_a("IfcRelAssignsToProduct"):
return rel.RelatingProduct
@classmethod
- def import_annotations_in_group(cls, group):
+ def import_annotations_in_group(cls, group: ifcopenshell.entity_instance) -> None:
elements = set(
[e for e in cls.get_group_elements(group) if e.is_a("IfcAnnotation") and e.ObjectType != "DRAWING"]
)
@@ -600,6 +614,7 @@ class Drawing(blenderbim.core.tool.Drawing):
ifc_importer.file = tool.Ifc.get()
ifc_importer.calculate_unit_scale()
ifc_importer.process_context_filter()
+ ifc_importer.load_existing_meshes()
ifc_importer.material_creator.load_existing_materials()
ifc_importer.create_generic_elements(elements)
for obj in ifc_importer.added_data.values():
@@ -609,7 +624,7 @@ class Drawing(blenderbim.core.tool.Drawing):
# but BIMCameraProperties are only synced with EPsetDrawing at drawing import
# therefore camera props can differ from pset if the user changed them from pset.
@classmethod
- def import_drawing(cls, drawing):
+ def import_drawing(cls, drawing: ifcopenshell.entity_instance) -> bpy.types.Object:
settings = ifcopenshell.geom.settings()
settings.set(settings.STRICT_TOLERANCE, True)
@@ -656,8 +671,40 @@ class Drawing(blenderbim.core.tool.Drawing):
camera.angle = fov
- psets = ifcopenshell.util.element.get_psets(drawing)
- pset = psets.get("EPset_Drawing")
+ tool.Loader.link_mesh(shape, camera)
+
+ obj = bpy.data.objects.new(tool.Loader.get_name(drawing), camera)
+ cls.import_camera_props(drawing, obj)
+ tool.Ifc.link(drawing, obj)
+
+ m = shape.transformation.matrix.data
+ mat = mathutils.Matrix(
+ ([m[0], m[3], m[6], m[9]], [m[1], m[4], m[7], m[10]], [m[2], m[5], m[8], m[11]], [0, 0, 0, 1])
+ )
+ obj.matrix_world = mat
+
+ if cls.get_drawing_target_view(drawing) == "REFLECTED_PLAN_VIEW":
+ obj.matrix_world[1][1] *= -1
+
+ tool.Geometry.record_object_position(obj)
+ tool.Collector.assign(obj)
+
+ return obj
+
+ @classmethod
+ def import_camera_props(cls, drawing, obj):
+ # Temporarily clear the definition id to prevent prop update callbacks to IFC.
+ ifc_definition_id = obj.BIMObjectProperties.ifc_definition_id
+ obj.BIMObjectProperties.ifc_definition_id = 0
+
+ camera = obj.data
+ camera.BIMCameraProperties.has_underlay = False
+ camera.BIMCameraProperties.has_linework = True
+ camera.BIMCameraProperties.has_annotation = True
+ camera.BIMCameraProperties.target_view = "PLAN_VIEW"
+ camera.BIMCameraProperties.is_nts = False
+
+ pset = ifcopenshell.util.element.get_pset(drawing, "EPset_Drawing")
if pset:
if "TargetView" in pset:
camera.BIMCameraProperties.target_view = pset["TargetView"]
@@ -684,24 +731,7 @@ class Drawing(blenderbim.core.tool.Drawing):
if "IsNTS" in pset:
camera.BIMCameraProperties.is_nts = bool(pset["IsNTS"])
- tool.Loader.link_mesh(shape, camera)
-
- obj = bpy.data.objects.new(tool.Loader.get_name(drawing), camera)
- tool.Ifc.link(drawing, obj)
-
- m = shape.transformation.matrix.data
- mat = mathutils.Matrix(
- ([m[0], m[3], m[6], m[9]], [m[1], m[4], m[7], m[10]], [m[2], m[5], m[8], m[11]], [0, 0, 0, 1])
- )
- obj.matrix_world = mat
-
- if cls.get_drawing_target_view(drawing) == "REFLECTED_PLAN_VIEW":
- obj.matrix_world[1][1] *= -1
-
- tool.Geometry.record_object_position(obj)
- tool.Collector.assign(obj)
-
- return obj
+ obj.BIMObjectProperties.ifc_definition_id = ifc_definition_id
@classmethod
def import_drawings(cls):
@@ -733,10 +763,10 @@ class Drawing(blenderbim.core.tool.Drawing):
for drawing in sorted(drawings, key=lambda x: x.Name or "Unnamed"):
new = props.drawings.add()
- new.ifc_definition_id = drawing.id()
new.name = drawing.Name or "Unnamed"
new.is_selected = current_drawings_selection.get(drawing.id(), True)
new.is_drawing = True
+ new.ifc_definition_id = drawing.id() # Last, to prevent unnecessary prop callbacks
@classmethod
def import_documents(cls, document_type):
@@ -862,7 +892,7 @@ class Drawing(blenderbim.core.tool.Drawing):
should_add_representation=True,
context=None,
ifc_representation_class=None,
- ):
+ ) -> Union[ifcopenshell.entity_instance, None]:
return blenderbim.core.root.assign_class(
tool.Ifc,
tool.Collector,
@@ -1048,7 +1078,9 @@ class Drawing(blenderbim.core.tool.Drawing):
shutil.copy(ootb_resource, resource_path)
@classmethod
- def get_potential_reference_elements(cls, drawing):
+ def get_potential_reference_elements(
+ cls, drawing: ifcopenshell.entity_instance
+ ) -> list[ifcopenshell.entity_instance]:
elements = []
existing_references = cls.get_group_elements(cls.get_drawing_group(drawing))
for element in tool.Ifc.get().by_type("IfcAnnotation"):
@@ -1069,7 +1101,9 @@ class Drawing(blenderbim.core.tool.Drawing):
return elements
@classmethod
- def get_drawing_reference_annotation(cls, drawing, reference_element):
+ def get_drawing_reference_annotation(
+ cls, drawing: ifcopenshell.entity_instance, reference_element: ifcopenshell.entity_instance
+ ) -> Union[ifcopenshell.entity_instance, bool, None]:
if drawing == reference_element:
return True
for element in cls.get_group_elements(cls.get_drawing_group(drawing)):
@@ -1085,26 +1119,49 @@ class Drawing(blenderbim.core.tool.Drawing):
return element
@classmethod
- def generate_reference_annotation(cls, drawing, reference_element, context):
+ def generate_reference_annotation(
+ cls,
+ drawing: ifcopenshell.entity_instance,
+ reference_element: ifcopenshell.entity_instance,
+ context: ifcopenshell.entity_instance,
+ ) -> ifcopenshell.entity_instance:
if reference_element.is_a("IfcGridAxis"):
return cls.generate_grid_axis_reference_annotation(drawing, reference_element, context)
+
elif reference_element.is_a("IfcAnnotation") and reference_element.ObjectType == "DRAWING":
- psets = ifcopenshell.util.element.get_psets(reference_element)
- target_view = psets.get("EPset_Drawing", {}).get("TargetView", None)
+
+ def ensure_referenced_drawing_obj_exists(drawing: ifcopenshell.entity_instance):
+ obj = tool.Ifc.get_object(drawing)
+ if obj is None:
+ obj = cls.import_drawing(drawing)
+ # there is no need for other drawing annotations in that case
+ # but we import them anyway so we won't break that drawing activation
+ cls.import_annotations_in_group(cls.get_drawing_group(drawing))
+ tool.Blender.get_layer_collection(obj.users_collection[0]).hide_viewport = True
+
+ target_view = ifcopenshell.util.element.get_pset(reference_element, "EPset_Drawing", "TargetView")
if target_view == "ELEVATION_VIEW":
+ ensure_referenced_drawing_obj_exists(reference_element)
return cls.generate_elevation_reference_annotation(drawing, reference_element, context)
elif target_view == "SECTION_VIEW":
+ ensure_referenced_drawing_obj_exists(reference_element)
return cls.generate_section_reference_annotation(drawing, reference_element, context)
+
elif reference_element.is_a("IfcBuildingStorey"):
return cls.generate_storey_annotation(drawing, reference_element, context)
@classmethod
- def generate_storey_annotation(cls, drawing, reference_element, context):
+ def generate_storey_annotation(
+ cls,
+ drawing: ifcopenshell.entity_instance,
+ reference_element: ifcopenshell.entity_instance,
+ context: ifcopenshell.entity_instance,
+ ) -> ifcopenshell.entity_instance:
camera = tool.Ifc.get_object(drawing)
bounds = helper.ortho_view_frame(camera.data) if camera.data.type == "ORTHO" else None
reference_obj = tool.Ifc.get_object(reference_element)
- def to_camera_coords(camera, reference_obj):
+ def to_camera_coords(camera: bpy.types.Object, reference_obj: bpy.types.Object) -> Matrix:
mat = reference_obj.matrix_world.copy()
xyz = camera.matrix_world.inverted() @ reference_obj.matrix_world.translation
xyz[2] = 0
@@ -1159,13 +1216,18 @@ class Drawing(blenderbim.core.tool.Drawing):
return element
@classmethod
- def generate_section_reference_annotation(cls, drawing, reference_element, context):
+ def generate_section_reference_annotation(
+ cls,
+ drawing: ifcopenshell.entity_instance,
+ reference_element: ifcopenshell.entity_instance,
+ context: ifcopenshell.entity_instance,
+ ) -> ifcopenshell.entity_instance:
reference_obj = tool.Ifc.get_object(reference_element)
reference_obj.matrix_world
camera = tool.Ifc.get_object(drawing)
bounds = helper.ortho_view_frame(camera.data) if camera.data.type == "ORTHO" else None
- def to_camera_coords(camera, reference_obj):
+ def to_camera_coords(camera: bpy.types.Object, reference_obj: bpy.types.Object) -> Matrix:
mat = reference_obj.matrix_world.copy()
xyz = camera.matrix_world.inverted() @ reference_obj.matrix_world.translation
xyz[2] = 0
@@ -1176,7 +1238,9 @@ class Drawing(blenderbim.core.tool.Drawing):
mat.translation += annotation_offset
return mat
- def clip_to_camera_boundary(mesh, bounds):
+ def clip_to_camera_boundary(
+ mesh: bpy.types.Mesh, bounds: tuple[float, float, float, float, float, float]
+ ) -> Union[bpy.types.Mesh, None]:
mesh.verts.ensure_lookup_table()
points = [v.co for v in mesh.verts[0:2]]
points = helper.clip_segment(bounds, points)
@@ -1237,12 +1301,17 @@ class Drawing(blenderbim.core.tool.Drawing):
return element
@classmethod
- def generate_elevation_reference_annotation(cls, drawing, reference_element, context):
+ def generate_elevation_reference_annotation(
+ cls,
+ drawing: ifcopenshell.entity_instance,
+ reference_element: ifcopenshell.entity_instance,
+ context: ifcopenshell.entity_instance,
+ ) -> ifcopenshell.entity_instance:
reference_obj = tool.Ifc.get_object(reference_element)
reference_obj.matrix_world
camera = tool.Ifc.get_object(drawing)
- def to_camera_coords(camera, reference_obj):
+ def to_camera_coords(camera: bpy.types.Object, reference_obj: bpy.types.Object) -> Matrix:
mat = reference_obj.matrix_world.copy()
xyz = camera.matrix_world.inverted() @ reference_obj.matrix_world.translation
xyz[2] = 0
@@ -1269,7 +1338,12 @@ class Drawing(blenderbim.core.tool.Drawing):
return element
@classmethod
- def generate_grid_axis_reference_annotation(cls, drawing, reference_element, context):
+ def generate_grid_axis_reference_annotation(
+ cls,
+ drawing: ifcopenshell.entity_instance,
+ reference_element: ifcopenshell.entity_instance,
+ context: ifcopenshell.entity_instance,
+ ) -> ifcopenshell.entity_instance:
target_view = tool.Drawing.get_drawing_target_view(drawing)
camera = tool.Ifc.get_object(drawing)
@@ -1297,7 +1371,7 @@ class Drawing(blenderbim.core.tool.Drawing):
mesh.to_mesh(obj.data)
return obj
- def to_camera_coords(obj, mesh):
+ def to_camera_coords(obj: bpy.types.Object, mesh: bpy.types.Mesh) -> tuple[bpy.types.Object, bpy.types.Mesh]:
mesh.transform(camera.matrix_world.inverted() @ obj.matrix_world)
obj.matrix_world = camera.matrix_world
annotation_offset = mathutils.Vector((0, 0, -camera.data.clip_start - 0.05))
@@ -1354,7 +1428,7 @@ class Drawing(blenderbim.core.tool.Drawing):
return element
@classmethod
- def is_perpendicular(cls, a, b):
+ def is_perpendicular(cls, a: bpy.types.Object, b: bpy.types.Object) -> bool:
axes = [mathutils.Vector((1, 0, 0)), mathutils.Vector((0, 1, 0)), mathutils.Vector((0, 0, 1))]
a_quaternion = a.matrix_world.to_quaternion()
b_quaternion = b.matrix_world.to_quaternion()
@@ -1364,7 +1438,7 @@ class Drawing(blenderbim.core.tool.Drawing):
return False
@classmethod
- def get_camera_block(cls, obj):
+ def get_camera_block(cls, obj: bpy.types.Object) -> dict:
raster_x = obj.data.BIMCameraProperties.raster_x
raster_y = obj.data.BIMCameraProperties.raster_y
@@ -1397,7 +1471,7 @@ class Drawing(blenderbim.core.tool.Drawing):
return {"verts": verts, "faces": faces}
@classmethod
- def is_intersecting(cls, a, b):
+ def is_intersecting(cls, a: bpy.types.Object, b: bpy.types.Object) -> bool:
a_block = cls.get_camera_block(a)
a_tree = mathutils.bvhtree.BVHTree.FromPolygons(a_block["verts"], a_block["faces"])
b_block = cls.get_camera_block(b)
@@ -1405,7 +1479,7 @@ class Drawing(blenderbim.core.tool.Drawing):
return bool(a_tree.overlap(b_tree))
@classmethod
- def replace_text_literal_variables(cls, text, product):
+ def replace_text_literal_variables(cls, text: str, product: Optional[ifcopenshell.entity_instance] = None) -> str:
if not product:
return text
@@ -1502,19 +1576,27 @@ class Drawing(blenderbim.core.tool.Drawing):
@classmethod
def get_drawing_metadata(cls, drawing):
+ # fmt: off
return [
v.strip()
- for v in (ifcopenshell.util.element.get_psets(drawing)["EPset_Drawing"].get("Metadata", "") or "").split(",")
+ for v in (
+ ifcopenshell.util.element.get_psets(drawing)["EPset_Drawing"].get("Metadata", "") or ""
+ ).split(",")
if v
]
+ # fmt: on
@classmethod
def get_annotation_z_index(cls, drawing):
return ifcopenshell.util.element.get_pset(drawing, "EPset_Annotation", "ZIndex") or 0
@classmethod
- def get_annotation_symbol(cls, drawing):
- return ifcopenshell.util.element.get_pset(drawing, "EPset_Annotation", "Symbol")
+ def get_annotation_symbol(cls, element: ifcopenshell.entity_instance) -> Union[str, None]:
+ symbol = ifcopenshell.util.element.get_pset(element, "EPset_Annotation", "Symbol")
+ if not symbol:
+ # EPset_AnnotationSurveyArea is not standard! See bSI-4.3 proposal #660.
+ symbol = ifcopenshell.util.element.get_pset(element, "EPset_AnnotationSurveyArea", "PointType")
+ return symbol
@classmethod
def has_linework(cls, drawing):
@@ -1525,7 +1607,7 @@ class Drawing(blenderbim.core.tool.Drawing):
return ifcopenshell.util.element.get_psets(drawing).get("EPset_Drawing", {}).get("HasAnnotation", False)
@classmethod
- def get_drawing_elements(cls, drawing):
+ def get_drawing_elements(cls, drawing: ifcopenshell.entity_instance) -> set[ifcopenshell.entity_instance]:
"""returns a set of elements that are included in the drawing"""
ifc_file = tool.Ifc.get()
pset = ifcopenshell.util.element.get_psets(drawing).get("EPset_Drawing", {})
@@ -1539,6 +1621,10 @@ class Drawing(blenderbim.core.tool.Drawing):
else:
base_elements = set(ifc_file.by_type("IfcElement") + ifc_file.by_type("IfcSpatialElement"))
elements = {e for e in (elements & base_elements) if e.is_a() != "IfcSpace"}
+
+ # exclude annotations to avoid including annotations from other drawings
+ elements = {i for i in elements if not i.is_a("IfcAnnotation")}
+ # add annotations from the current drawing
annotations = tool.Drawing.get_group_elements(tool.Drawing.get_drawing_group(drawing))
elements.update(annotations)
@@ -1549,15 +1635,13 @@ class Drawing(blenderbim.core.tool.Drawing):
return elements
@classmethod
- def get_drawing_spaces(cls, drawing):
+ def get_drawing_spaces(cls, drawing: ifcopenshell.entity_instance) -> set[ifcopenshell.entity_instance]:
ifc_file = tool.Ifc.get()
pset = ifcopenshell.util.element.get_psets(drawing).get("EPset_Drawing", {})
- include = pset.get("Include", None)
elements = cls.get_elements_in_camera_view(
tool.Ifc.get_object(drawing), [tool.Ifc.get_object(e) for e in ifc_file.by_type("IfcSpace")]
)
- if include:
- elements = ifcopenshell.util.selector.filter_elements(ifc_file, include, elements=elements)
+ # NOTE: EPset_Drawing.Include is not used to avoid adding other elements besides spaces
exclude = pset.get("Exclude", None)
if exclude:
elements -= ifcopenshell.util.selector.filter_elements(ifc_file, exclude)
@@ -1609,15 +1693,15 @@ class Drawing(blenderbim.core.tool.Drawing):
bpy.ops.bim.activate_model()
@classmethod
- def activate_drawing(cls, camera):
+ def activate_drawing(cls, camera: bpy.types.Object) -> None:
area = tool.Blender.get_view3d_area()
is_local_view = area.spaces[0].local_view is not None
if is_local_view:
- #turn off local view before activating drawing, and then turn it on again.
+ # turn off local view before activating drawing, and then turn it on again.
for a in bpy.context.screen.areas:
- if a.type == 'VIEW_3D':
+ if a.type == "VIEW_3D":
override = bpy.context.copy()
- override['area'] = a
+ override["area"] = a
bpy.ops.view3d.localview(override)
bpy.context.scene.camera = camera
bpy.ops.view3d.localview(override)
@@ -1648,15 +1732,8 @@ class Drawing(blenderbim.core.tool.Drawing):
filtered_elements = cls.get_drawing_elements(drawing) | cls.get_drawing_spaces(drawing)
filtered_elements.add(drawing)
- for view_layer_object in bpy.context.view_layer.objects:
- element = tool.Ifc.get_entity(view_layer_object)
- if not element or element.is_a("IfcTypeProduct"):
- continue
- hide = element not in filtered_elements
- view_layer_object.hide_set(hide)
- view_layer_object.hide_render = hide
- subcontexts = []
+ subcontexts: list[ifcopenshell.entity_instance] = []
target_view = cls.get_drawing_target_view(drawing)
if target_view in ("PLAN_VIEW", "REFLECTED_PLAN_VIEW"):
@@ -1689,6 +1766,13 @@ class Drawing(blenderbim.core.tool.Drawing):
if subcontext:
subcontexts.append(context_filter)
+ # Hide everything first, then selectively show. This is significantly faster.
+ with bpy.context.temp_override(area=next(a for a in bpy.context.screen.areas if a.type == "VIEW_3D")):
+ bpy.ops.object.hide_view_set(unselected=False)
+ bpy.ops.object.hide_view_set(unselected=True)
+
+ # switch representations and hide elements without representations
+ element_obj_names = set()
for element in filtered_elements:
obj = tool.Ifc.get_object(element)
current_representation = tool.Geometry.get_active_representation(obj)
@@ -1717,12 +1801,23 @@ class Drawing(blenderbim.core.tool.Drawing):
has_context = True
break
- if not has_context:
- obj.hide_set(True)
- obj.hide_render = True
+ # Don't hide IfcAnnotations as some of them might exist without representations
+ if has_context or element.is_a("IfcAnnotation"):
+ element_obj_names.add(obj.name)
+
+ # Note that render visibility is only set on drawing generation time for speed.
+ [
+ obj.hide_set(False) # Show the object
+ for obj in bpy.context.view_layer.objects
+ if obj.name in element_obj_names or not tool.Ifc.get_entity(obj)
+ ]
+
+ cls.import_camera_props(drawing, camera)
@classmethod
- def get_elements_in_camera_view(cls, camera, objs):
+ def get_elements_in_camera_view(
+ cls, camera: bpy.types.Object, objs: list[ifcopenshell.entity_instance]
+ ) -> set[ifcopenshell.entity_instance]:
props = camera.data.BIMCameraProperties
x = props.width
y = props.height
@@ -1922,3 +2017,60 @@ class Drawing(blenderbim.core.tool.Drawing):
rotate180z = mathutils.Matrix.Rotation(math.radians(180.0), 4, "Z")
return mathutils.Matrix.Translation(location) @ rotation.to_matrix().to_4x4() @ rotate180z
return mathutils.Matrix.Translation(location) @ rotation.to_matrix().to_4x4()
+
+ @classmethod
+ def convert_svg_to_dxf(cls, svg_filepath: Path, dxf_filepath: Path) -> None:
+ import ezdxf
+ import xml.etree.ElementTree as ET
+
+ SVG = "{http://www.w3.org/2000/svg}"
+ IFC = "{http://www.ifcopenshell.org/ns}"
+
+ doc = ezdxf.new("R2010")
+ msp = doc.modelspace()
+ svg = ET.parse(svg_filepath).getroot()
+
+ def finalize_dxf():
+ doc.saveas(dxf_filepath)
+
+ drawing = svg.findall(f"{SVG}g[@{IFC}name]")
+ if not drawing:
+ finalize_dxf()
+ return
+ drawing = drawing[0]
+
+ NUMBER = r"-?\d+\.?\d+"
+ COORD = rf"{NUMBER},{NUMBER}"
+ POLYLINE_PATTERN = rf"M{COORD} (?:L{COORD} ?)+Z? ?"
+ MULTI_POLYLINE_PATTERN = rf"^({POLYLINE_PATTERN})+$"
+
+ for element_g in drawing.findall(f"{SVG}g"):
+ paths = element_g.findall(f"{SVG}path")
+
+ for path in paths:
+ path = path.attrib["d"]
+
+ if not re.match(MULTI_POLYLINE_PATTERN, path):
+ # print(f'Path "{path}" doesn\'t match expected pattern {MULTI_POLYLINE_PATTERN}')
+ continue
+
+ for polyline_path in re.findall(POLYLINE_PATTERN, path):
+ points = re.findall(rf"{NUMBER}", polyline_path)
+ points = [float(p) for p in points]
+ POINT_SIZE = 2
+
+ grouped_points = []
+ for i in range(0, len(points), POINT_SIZE):
+ point = points[i : i + POINT_SIZE]
+ point[1] *= -1
+ grouped_points.append(point)
+ points = grouped_points
+
+ # Z marks closed polylines
+ is_closed_polyline = polyline_path.rstrip().endswith("Z")
+ if is_closed_polyline or len(points) > 2:
+ msp.add_lwpolyline(points, close=is_closed_polyline)
+ else: # LINE
+ msp.add_line(*points)
+
+ finalize_dxf()
diff --git a/src/blenderbim/blenderbim/tool/geometry.py b/src/blenderbim/blenderbim/tool/geometry.py
index 77af31d01d..0a3ff34fdf 100644
--- a/src/blenderbim/blenderbim/tool/geometry.py
+++ b/src/blenderbim/blenderbim/tool/geometry.py
@@ -26,12 +26,13 @@ import ifcopenshell
import blenderbim.core.tool
import blenderbim.core.style
import blenderbim.core.spatial
+import blenderbim.core.geometry
import blenderbim.tool as tool
import blenderbim.bim.import_ifc
from math import radians, pi
from mathutils import Vector, Matrix
from blenderbim.bim.ifc import IfcStore
-from typing import List
+from typing import Union
class Geometry(blenderbim.core.tool.Geometry):
@@ -90,6 +91,17 @@ class Geometry(blenderbim.core.tool.Geometry):
if element.is_a("IfcRelSpaceBoundary"):
ifcopenshell.api.run("boundary.remove_boundary", tool.Ifc.get(), boundary=element)
return bpy.data.objects.remove(obj)
+ if element.is_a("IfcGridAxis"):
+ is_last_axis = False
+ # Deleting the last W axis is OK
+ if ((grid := element.PartOfU) and len(grid[0].UAxes) == 1) or (
+ (grid := element.PartOfV) and len(grid[0].VAxes) == 1
+ ):
+ is_last_axis = True
+ if is_last_axis:
+ return
+ ifcopenshell.api.run("grid.remove_grid_axis", tool.Ifc.get(), axis=element)
+ return bpy.data.objects.remove(obj)
collection = obj.BIMObjectProperties.collection
if collection:
@@ -314,7 +326,9 @@ class Geometry(blenderbim.core.tool.Geometry):
return ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW")
@classmethod
- def get_subcontext_parameters(cls, subcontext):
+ def get_subcontext_parameters(
+ cls, subcontext: ifcopenshell.entity_instance
+ ) -> tuple[Union[str, None], Union[str, None], Union[str, None]]:
return (
subcontext.ContextType,
subcontext.ContextIdentifier,
@@ -723,6 +737,9 @@ class Geometry(blenderbim.core.tool.Geometry):
@classmethod
def reload_representation(cls, obj):
+ """reload `obj` active representation"""
+ if not obj.data:
+ return
representation = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
blenderbim.core.geometry.switch_representation(
tool.Ifc,
@@ -883,7 +900,7 @@ class Geometry(blenderbim.core.tool.Geometry):
return inverse
@classmethod
- def get_shape_aspect_styles(cls, element, shape_aspect, representation_item) -> List[ifcopenshell.entity_instance]:
+ def get_shape_aspect_styles(cls, element, shape_aspect, representation_item) -> list[ifcopenshell.entity_instance]:
"""update `representation_item` style based on styles connected to the `shape_aspect`
through material constituents with the same name
"""
diff --git a/src/blenderbim/blenderbim/tool/ifc.py b/src/blenderbim/blenderbim/tool/ifc.py
index d17c819894..f961d03a02 100644
--- a/src/blenderbim/blenderbim/tool/ifc.py
+++ b/src/blenderbim/blenderbim/tool/ifc.py
@@ -20,45 +20,47 @@ import os
import bpy
import numpy as np
import ifcopenshell.api
+import ifcopenshell.util.element
import blenderbim.core.tool
import blenderbim.bim.handler
import blenderbim.tool as tool
-from blenderbim.bim.ifc import IfcStore
+from blenderbim.bim.ifc import IfcStore, IFC_CONNECTED_TYPE
+from typing import Optional, Union, Any
class Ifc(blenderbim.core.tool.Ifc):
@classmethod
- def run(cls, command, **kwargs):
+ def run(cls, command: str, **kwargs) -> Any:
return ifcopenshell.api.run(command, IfcStore.get_file(), **kwargs)
@classmethod
- def set(cls, ifc):
+ def set(cls, ifc: ifcopenshell.file) -> None:
IfcStore.file = ifc
@classmethod
- def get(cls):
+ def get(cls) -> ifcopenshell.file:
return IfcStore.get_file()
@classmethod
- def get_path(cls):
+ def get_path(cls) -> str:
return IfcStore.path
@classmethod
- def get_schema(cls):
+ def get_schema(cls) -> str:
if IfcStore.get_file():
return IfcStore.get_file().schema
@classmethod
- def has_changed_shading(cls, obj):
+ def has_changed_shading(cls, obj: bpy.types.Material) -> bool:
checksum = obj.BIMMaterialProperties.shading_checksum
return checksum != repr(np.array(obj.diffuse_color).tobytes())
@classmethod
- def is_edited(cls, obj):
+ def is_edited(cls, obj: bpy.types.Object) -> bool:
return list(obj.scale) != [1.0, 1.0, 1.0] or obj in IfcStore.edited_objs
@classmethod
- def is_moved(cls, obj):
+ def is_moved(cls, obj: bpy.types.Object) -> bool:
element = cls.get_entity(obj)
if not element or element.is_a("IfcTypeProduct") or element.is_a("IfcProject"):
return False
@@ -77,7 +79,7 @@ class Ifc(blenderbim.core.tool.Ifc):
return IfcStore.get_schema()
@classmethod
- def get_entity(cls, obj):
+ def get_entity(cls, obj: IFC_CONNECTED_TYPE) -> ifcopenshell.entity_instance:
ifc = IfcStore.get_file()
props = getattr(obj, "BIMObjectProperties", None)
if ifc and props and props.ifc_definition_id:
@@ -87,7 +89,7 @@ class Ifc(blenderbim.core.tool.Ifc):
pass
@classmethod
- def get_entity_by_id(cls, entity_id):
+ def get_entity_by_id(cls, entity_id) -> Union[ifcopenshell.entity_instance, None]:
"""useful to check whether entity_id is still exists in IFC"""
ifc_file = tool.Ifc.get()
try:
@@ -96,11 +98,11 @@ class Ifc(blenderbim.core.tool.Ifc):
return None
@classmethod
- def get_object(cls, element):
+ def get_object(cls, element: ifcopenshell.entity_instance) -> IFC_CONNECTED_TYPE:
return IfcStore.get_element(element.id())
@classmethod
- def rebuild_element_maps(cls):
+ def rebuild_element_maps(cls) -> None:
"""Rebuilds the id_map and guid_map
When any Blender object is stored outside a Blender PointerProperty,
@@ -153,15 +155,15 @@ class Ifc(blenderbim.core.tool.Ifc):
blenderbim.bim.handler.subscribe_to(obj, "diffuse_color", blenderbim.bim.handler.color_callback)
@classmethod
- def link(cls, element, obj):
+ def link(cls, element: ifcopenshell.entity_instance, obj: IFC_CONNECTED_TYPE) -> None:
IfcStore.link_element(element, obj)
@classmethod
- def edit(cls, obj):
+ def edit(cls, obj: bpy.types.Object) -> None:
IfcStore.edited_objs.add(obj)
@classmethod
- def finish_edit(cls, obj):
+ def finish_edit(cls, obj: bpy.types.Object) -> None:
try:
IfcStore.edited_objs.remove(obj)
except:
@@ -186,11 +188,13 @@ class Ifc(blenderbim.core.tool.Ifc):
return os.path.relpath(uri, ifc_path).replace("\\", "/")
@classmethod
- def unlink(cls, element=None, obj=None):
+ def unlink(
+ cls, element: Optional[ifcopenshell.entity_instance] = None, obj: Optional[IFC_CONNECTED_TYPE] = None
+ ) -> None:
IfcStore.unlink_element(element, obj)
@classmethod
- def get_all_element_occurrences(cls, element):
+ def get_all_element_occurrences(cls, element: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
if element.is_a("IfcElementType"):
element_type = element
occurrences = ifcopenshell.util.element.get_types(element_type)
diff --git a/src/blenderbim/blenderbim/tool/loader.py b/src/blenderbim/blenderbim/tool/loader.py
index 32952b461c..aac640d4da 100644
--- a/src/blenderbim/blenderbim/tool/loader.py
+++ b/src/blenderbim/blenderbim/tool/loader.py
@@ -26,6 +26,7 @@ import os
import numpy as np
from mathutils import Vector
from pathlib import Path
+from typing import Union
# Progressively we'll refactor loading elements into Blender objects into this
@@ -34,9 +35,12 @@ from pathlib import Path
# supplementary objects (e.g. drawings, structural analysis models, etc).
+OBJECT_DATA_TYPE = Union[bpy.types.Mesh, bpy.types.Curve]
+
+
class Loader(blenderbim.core.tool.Loader):
@classmethod
- def create_project_collection(self, name):
+ def create_project_collection(cls, name: str) -> bpy.types.Collection:
project_obj = tool.Ifc.get_object(tool.Ifc.get().by_type("IfcProject")[0])
project_collection = project_obj.BIMObjectProperties.collection
for collection in project_collection.children:
@@ -51,7 +55,7 @@ class Loader(blenderbim.core.tool.Loader):
return collection
@classmethod
- def get_mesh_name(cls, geometry):
+ def get_mesh_name(cls, geometry) -> str:
representation_id = geometry.id
if "-" in representation_id:
representation_id = int(re.sub(r"\D", "", representation_id.split("-")[0]))
@@ -62,11 +66,11 @@ class Loader(blenderbim.core.tool.Loader):
return "{}/{}".format(context_id, representation_id)
@classmethod
- def get_name(cls, element):
+ def get_name(cls, element: ifcopenshell.entity_instance) -> str:
return "{}/{}".format(element.is_a(), getattr(element, "Name", "None"))
@classmethod
- def link_mesh(cls, shape, mesh):
+ def link_mesh(cls, shape, mesh: OBJECT_DATA_TYPE) -> None:
geometry = shape.geometry if hasattr(shape, "geometry") else shape
if "-" in geometry.id:
mesh.BIMMeshProperties.ifc_definition_id = int(geometry.id.split("-")[0])
diff --git a/src/blenderbim/blenderbim/tool/material.py b/src/blenderbim/blenderbim/tool/material.py
index 70c64eeb83..8ebb745fa0 100644
--- a/src/blenderbim/blenderbim/tool/material.py
+++ b/src/blenderbim/blenderbim/tool/material.py
@@ -219,7 +219,7 @@ class Material(blenderbim.core.tool.Material):
material = tool.Ifc.get().by_type("IfcMaterial")[0]
else:
blenderbim.core.material.unassign_material(tool.Ifc, tool.Material, objects=[tool.Ifc.get_object(element)])
- tool.Ifc.run("material.assign_material", product=element, type="IfcMaterialProfileSet", material=material)
+ tool.Ifc.run("material.assign_material", products=[element], type="IfcMaterialProfileSet", material=material)
assinged_material = cls.get_material(element)
material_profile = tool.Ifc.run("material.add_profile", profile_set=assinged_material, material=material)
return material_profile
diff --git a/src/blenderbim/blenderbim/tool/model.py b/src/blenderbim/blenderbim/tool/model.py
index 95fe41da0c..32c2856389 100644
--- a/src/blenderbim/blenderbim/tool/model.py
+++ b/src/blenderbim/blenderbim/tool/model.py
@@ -20,6 +20,7 @@ import bpy
import json
import bmesh
import collections
+import collections.abc
import numpy as np
import ifcopenshell
import ifcopenshell.util.unit
@@ -36,23 +37,26 @@ from blenderbim.bim import import_ifc
from blenderbim.bim.module.geometry.helper import Helper
from blenderbim.bim.module.model.data import AuthoringData, RailingData, RoofData, WindowData, DoorData
from ifcopenshell.util.shape_builder import V, ShapeBuilder
+from typing import Optional, Union, TypeVar, Any
+
+T = TypeVar("T")
class Model(blenderbim.core.tool.Model):
@classmethod
- def convert_si_to_unit(cls, value):
+ def convert_si_to_unit(cls, value: T) -> T:
if isinstance(value, (tuple, list)):
return [v / cls.unit_scale for v in value]
return value / cls.unit_scale
@classmethod
- def convert_unit_to_si(cls, value):
+ def convert_unit_to_si(cls, value: T) -> T:
if isinstance(value, (tuple, list)):
return [v * cls.unit_scale for v in value]
return value * cls.unit_scale
@classmethod
- def convert_data_to_project_units(cls, data, non_si_props=[]):
+ def convert_data_to_project_units(cls, data: dict[str, Any], non_si_props: list[str] = []) -> dict[str, Any]:
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
for prop_name in data:
if prop_name in non_si_props:
@@ -65,7 +69,7 @@ class Model(blenderbim.core.tool.Model):
return data
@classmethod
- def convert_data_to_si_units(cls, data, non_si_props=[]):
+ def convert_data_to_si_units(cls, data: dict[str, Any], non_si_props: list[str] = []) -> dict[str, Any]:
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
for prop_name in data:
if prop_name in non_si_props:
@@ -78,34 +82,35 @@ class Model(blenderbim.core.tool.Model):
return data
@classmethod
- def export_curve(cls, position, edge_indices, points=None):
+ def export_curve(cls, position: Matrix, edge_indices: list[tuple[int, int]]) -> ifcopenshell.entity_instance:
position_i = position.inverted()
+ ifc_file = tool.Ifc.get()
if len(edge_indices) == 2:
diameter = edge_indices[0]
p1 = cls.bm.verts[diameter[0]].co
p2 = cls.bm.verts[diameter[1]].co
center = cls.convert_si_to_unit(list(position_i @ p1.lerp(p2, 0.5)))
radius = cls.convert_si_to_unit((p1 - p2).length / 2)
- return tool.Ifc.get().createIfcCircle(
- tool.Ifc.get().createIfcAxis2Placement2D(tool.Ifc.get().createIfcCartesianPoint(center[0:2])), radius
+ return ifc_file.createIfcCircle(
+ ifc_file.createIfcAxis2Placement2D(ifc_file.createIfcCartesianPoint(center[0:2])), radius
)
- if tool.Ifc.get().schema == "IFC2X3":
+ if ifc_file.schema == "IFC2X3":
points = []
for edge in edge_indices:
local_point = (position_i @ Vector(cls.bm.verts[edge[0]].co)).to_2d()
- points.append(tool.Ifc.get().createIfcCartesianPoint(cls.convert_si_to_unit(local_point)))
+ points.append(ifc_file.createIfcCartesianPoint(cls.convert_si_to_unit(local_point)))
points.append(points[0])
- return tool.Ifc.get().createIfcPolyline(points)
+ return ifc_file.createIfcPolyline(points)
segments = []
for segment in edge_indices:
if len(segment) == 2:
- segments.append(tool.Ifc.get().createIfcLineIndex([i + 1 for i in segment]))
+ segments.append(ifc_file.createIfcLineIndex([i + 1 for i in segment]))
elif len(segment) == 3:
- segments.append(tool.Ifc.get().createIfcArcIndex([i + 1 for i in segment]))
- return tool.Ifc.get().createIfcIndexedPolyCurve(cls.points, segments, False)
+ segments.append(ifc_file.createIfcArcIndex([i + 1 for i in segment]))
+ return ifc_file.createIfcIndexedPolyCurve(cls.points, segments, False)
@classmethod
- def export_points(cls, position, indices):
+ def export_points(cls, position: Matrix, indices: list[Vector]) -> ifcopenshell.entity_instance:
position_i = position.inverted()
points = []
for point in indices:
@@ -114,7 +119,10 @@ class Model(blenderbim.core.tool.Model):
return tool.Ifc.get().createIfcCartesianPointList2D(points)
@classmethod
- def export_profile(cls, obj, position=None):
+ def export_profile(
+ cls, obj: bpy.types.Object, position: Optional[Matrix] = None
+ ) -> Union[ifcopenshell.entity_instance, None]:
+ """Returns `None` in case if profile was invalid."""
if position is None:
position = Matrix()
@@ -150,7 +158,7 @@ class Model(blenderbim.core.tool.Model):
return profile
@classmethod
- def export_surface(cls, obj):
+ def export_surface(cls, obj: bpy.types.Object) -> Union[ifcopenshell.entity_instance, None]:
p1, p2, p3 = [v.co.copy() for v in obj.data.vertices[0:3]]
edge1 = p2 - p1
@@ -202,7 +210,7 @@ class Model(blenderbim.core.tool.Model):
return surface
@classmethod
- def generate_occurrence_name(cls, element_type: ifcopenshell.entity_instance, ifc_class: str):
+ def generate_occurrence_name(cls, element_type: ifcopenshell.entity_instance, ifc_class: str) -> str:
props = bpy.context.scene.BIMModelProperties
if props.occurrence_name_style == "CLASS":
return ifc_class[3:]
@@ -216,7 +224,8 @@ class Model(blenderbim.core.tool.Model):
return "Instance"
@classmethod
- def get_extrusion(cls, representation):
+ def get_extrusion(cls, representation: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]:
+ """return first found IfcExtrudedAreaSolid"""
item = representation.Items[0]
while True:
if item.is_a("IfcExtrudedAreaSolid"):
diff --git a/src/blenderbim/blenderbim/tool/pset.py b/src/blenderbim/blenderbim/tool/pset.py
index ed7d383bad..0b4db9e9cb 100644
--- a/src/blenderbim/blenderbim/tool/pset.py
+++ b/src/blenderbim/blenderbim/tool/pset.py
@@ -18,14 +18,18 @@
import bpy
import ifcopenshell
+import ifcopenshell.util.element
import blenderbim.core.tool
import blenderbim.tool as tool
import blenderbim.bim.schema
+from typing import Union
class Pset(blenderbim.core.tool.Pset):
@classmethod
- def get_element_pset(cls, element, pset_name):
+ def get_element_pset(
+ cls, element: ifcopenshell.entity_instance, pset_name: str
+ ) -> Union[ifcopenshell.entity_instance, None]:
pset = ifcopenshell.util.element.get_pset(element, pset_name)
if pset:
return tool.Ifc.get().by_id(pset["id"])
@@ -50,13 +54,14 @@ class Pset(blenderbim.core.tool.Pset):
return bpy.context.scene.WorkSchedulePsetProperties
elif obj_type == "Group":
return bpy.context.scene.GroupPsetProperties
+
@classmethod
def get_pset_name(cls, obj, obj_type):
pset = cls.get_pset_props(obj, obj_type)
return pset.pset_name
@classmethod
- def is_pset_applicable(cls, element, pset_name):
+ def is_pset_applicable(cls, element: ifcopenshell.entity_instance, pset_name: str) -> bool:
return bool(
pset_name
in blenderbim.bim.schema.ifc.psetqto.get_applicable_names(
diff --git a/src/blenderbim/blenderbim/tool/qto.py b/src/blenderbim/blenderbim/tool/qto.py
index c0fa802212..b96b638907 100644
--- a/src/blenderbim/blenderbim/tool/qto.py
+++ b/src/blenderbim/blenderbim/tool/qto.py
@@ -23,14 +23,17 @@ import blenderbim.tool as tool
import ifcopenshell
from mathutils import Vector
from ifcopenshell import util
-from blenderbim.bim.module.pset.qto_calculator import QtoCalculator
+import ifcopenshell.util.unit
+import ifcopenshell.util.element
+from blenderbim.bim.module.pset.qto_calculator import QtoCalculator, QuanityTypes
from blenderbim.bim.module.pset.calc_quantity_function_mapper import mapper
import blenderbim.bim.schema
+from typing import Optional, Union, Literal
class Qto(blenderbim.core.tool.Qto):
@classmethod
- def get_radius_of_selected_vertices(cls, obj):
+ def get_radius_of_selected_vertices(cls, obj: bpy.types.Object) -> float:
selected_verts = [v.co for v in obj.data.vertices if v.select]
total = Vector()
for v in selected_verts:
@@ -39,16 +42,16 @@ class Qto(blenderbim.core.tool.Qto):
return max([(v - circle_center).length for v in selected_verts])
@classmethod
- def set_qto_result(cls, result):
+ def set_qto_result(cls, result: float) -> None:
bpy.context.scene.BIMQtoProperties.qto_result = str(round(result, 3))
@classmethod
- def add_object_base_qto(cls, obj):
+ def add_object_base_qto(cls, obj: bpy.types.Object) -> Union[ifcopenshell.entity_instance, None]:
product = tool.Ifc.get_entity(obj)
return cls.add_product_base_qto(product)
@classmethod
- def add_product_base_qto(cls, product):
+ def add_product_base_qto(cls, product: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]:
base_quantity_name = cls.get_applicable_base_quantity_name(product)
if base_quantity_name:
return tool.Ifc.run(
@@ -58,7 +61,7 @@ class Qto(blenderbim.core.tool.Qto):
)
@classmethod
- def get_applicable_quantity_names(cls, qto_name):
+ def get_applicable_quantity_names(cls, qto_name: str) -> list[str]:
pset_template = blenderbim.bim.schema.ifc.psetqto.get_by_name(qto_name)
return (
[property.Name for property in pset_template.HasPropertyTemplates]
@@ -67,7 +70,9 @@ class Qto(blenderbim.core.tool.Qto):
)
@classmethod
- def get_applicable_base_quantity_name(cls, product=None):
+ def get_applicable_base_quantity_name(
+ cls, product: Optional[ifcopenshell.entity_instance] = None
+ ) -> Union[str, None]:
if not product:
return
applicable_qto_names = blenderbim.bim.schema.ifc.psetqto.get_applicable_names(
@@ -76,36 +81,44 @@ class Qto(blenderbim.core.tool.Qto):
return next((qto_name for qto_name in applicable_qto_names if "Qto_" in qto_name and "Base" in qto_name), None)
@classmethod
- def get_new_calculated_quantity(cls, qto_name, quantity_name, obj):
+ def get_new_calculated_quantity(cls, qto_name: str, quantity_name: str, obj: bpy.types.Object) -> float:
return QtoCalculator().calculate_quantity(qto_name, quantity_name, obj)
@classmethod
- def get_new_guessed_quantity(cls, obj, quantity_name, alternative_prop_names):
+ def get_new_guessed_quantity(
+ cls, obj: bpy.types.Object, quantity_name: str, alternative_prop_names: list[str]
+ ) -> Union[float, None]:
return QtoCalculator().guess_quantity(quantity_name, alternative_prop_names, obj)
@classmethod
- def get_rounded_value(cls, new_quantity):
+ def get_rounded_value(cls, new_quantity: float) -> float:
return round(new_quantity, 3)
@classmethod
- def get_calculated_object_quantities(cls, calculator, qto_name, obj):
+ def get_calculated_object_quantities(
+ cls, calculator: QtoCalculator, qto_name: str, obj: bpy.types.Object
+ ) -> dict[str, float]:
return {
- quantity_name: cls.get_rounded_value(calculator.calculate_quantity(qto_name, quantity_name, obj))
+ quantity_name: cls.get_rounded_value(value)
for quantity_name in cls.get_applicable_quantity_names(qto_name) or []
if cls.has_calculator(qto_name, quantity_name)
- and calculator.calculate_quantity(qto_name, quantity_name, obj) is not None
+ and (value := calculator.calculate_quantity(qto_name, quantity_name, obj)) is not None
}
@classmethod
- def has_calculator(cls, qto_name, quantity_name):
+ def has_calculator(cls, qto_name: str, quantity_name: str) -> bool:
return bool(mapper.get(qto_name, {}).get(quantity_name, None))
@classmethod
- def convert_to_project_units(cls, value, qto_name=None, quantity_name=None, quantity_type=None):
+ def convert_to_project_units(
+ cls,
+ value: float,
+ qto_name: Optional[str] = None,
+ quantity_name: Optional[str] = None,
+ quantity_type: Optional[QuanityTypes] = None,
+ ) -> Union[float, None]:
"""You can either specify `quantity_type` or provide `qto_name/quantity_name`
to let method figure the `quantity_type` from the templates
-
- `quantity_type` values are `Q_LENGTH`, `Q_AREA`, `Q_VOLUME`
"""
ifc_file = tool.Ifc.get()
quantity_to_unit_types = {
@@ -135,7 +148,9 @@ class Qto(blenderbim.core.tool.Qto):
return value
@classmethod
- def get_guessed_quantities(cls, obj, pset_qto_properties):
+ def get_guessed_quantities(
+ cls, obj: bpy.types.Object, pset_qto_properties: list[ifcopenshell.entity_instance]
+ ) -> dict[str, float]:
calculated_quantities = {}
for pset_qto_property in pset_qto_properties:
quantity_name = pset_qto_property.get_info()["Name"]
@@ -153,7 +168,7 @@ class Qto(blenderbim.core.tool.Qto):
return calculated_quantities
@classmethod
- def get_base_qto(cls, product):
+ def get_base_qto(cls, product: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]:
if not hasattr(product, "IsDefinedBy"):
return
for rel in product.IsDefinedBy or []:
@@ -166,7 +181,7 @@ class Qto(blenderbim.core.tool.Qto):
return rel.RelatingPropertyDefinition
@classmethod
- def get_related_cost_item_quantities(cls, product):
+ def get_related_cost_item_quantities(cls, product: ifcopenshell.entity_instance) -> list[dict]:
"""_summary_: Returns the related cost item and related quantities of the product
:param ifc-instance product: ifc instance
diff --git a/src/blenderbim/blenderbim/tool/root.py b/src/blenderbim/blenderbim/tool/root.py
index 6f5b0d95c1..8a9273eb10 100644
--- a/src/blenderbim/blenderbim/tool/root.py
+++ b/src/blenderbim/blenderbim/tool/root.py
@@ -18,9 +18,14 @@
import bpy
import ifcopenshell
+import ifcopenshell.api
import ifcopenshell.util.representation
+import ifcopenshell.util.element
import blenderbim.core.tool
+import blenderbim.core.aggregate
import blenderbim.core.geometry
+import blenderbim.core.material
+import blenderbim.core.style
import blenderbim.tool as tool
from mathutils import Vector
from blenderbim.bim.module.model.opening import FilledOpeningGenerator
@@ -97,15 +102,18 @@ class Root(blenderbim.core.tool.Root):
if not element:
continue
if hasattr(element, "ConnectedTo") and element.ConnectedTo:
- paths = [connection for connection in element.ConnectedTo if connection.is_a("IfcRelConnectsPathElements")]
+ paths = [
+ connection for connection in element.ConnectedTo if connection.is_a("IfcRelConnectsPathElements")
+ ]
for path in paths:
- relationships[element] = {"type": "path",
- "related_connection_type": path.RelatedConnectionType,
- "related_element": path.RelatedElement,
- "related_priorities": path.RelatedPriorities,
- "relating_connection_type": path.RelatingConnectionType,
- "relating_element": path.RelatingElement,
- "relating_priorities": path.RelatingPriorities,
+ relationships[element] = {
+ "type": "path",
+ "related_connection_type": path.RelatedConnectionType,
+ "related_element": path.RelatedElement,
+ "related_priorities": path.RelatedPriorities,
+ "relating_connection_type": path.RelatingConnectionType,
+ "relating_element": path.RelatingElement,
+ "relating_priorities": path.RelatingPriorities,
}
return relationships
@@ -234,7 +242,6 @@ class Root(blenderbim.core.tool.Root):
related_connection=data["related_connection_type"],
)
-
@classmethod
def recreate_aggregate(cls, old_to_new):
for old, new in old_to_new.items():
@@ -244,35 +251,34 @@ class Root(blenderbim.core.tool.Root):
new_aggregate = old_to_new[old_aggregate]
except:
blenderbim.core.aggregate.unassign_object(
- tool.Ifc,
- tool.Aggregate,
- tool.Collector,
- relating_obj=tool.Ifc.get_object(old_aggregate),
- related_obj=tool.Ifc.get_object(new[0]),
- )
+ tool.Ifc,
+ tool.Aggregate,
+ tool.Collector,
+ relating_obj=tool.Ifc.get_object(old_aggregate),
+ related_obj=tool.Ifc.get_object(new[0]),
+ )
continue
-
+
blenderbim.core.aggregate.assign_object(
- tool.Ifc,
- tool.Aggregate,
- tool.Collector,
- relating_obj=tool.Ifc.get_object(new_aggregate[0]),
- related_obj=tool.Ifc.get_object(new[0]),
- )
-
+ tool.Ifc,
+ tool.Aggregate,
+ tool.Collector,
+ relating_obj=tool.Ifc.get_object(new_aggregate[0]),
+ related_obj=tool.Ifc.get_object(new[0]),
+ )
+
# Make sure that the array children also get reassigned to the correct aggregate
pset = ifcopenshell.util.element.get_pset(new[0], "BBIM_Array")
if pset:
array_children = tool.Blender.Modifier.Array.get_all_children_objects(new[0])
for obj in array_children:
blenderbim.core.aggregate.assign_object(
- tool.Ifc,
- tool.Aggregate,
- tool.Collector,
- relating_obj=tool.Ifc.get_object(new_aggregate[0]),
- related_obj=tool.Ifc.get_object(tool.Ifc.get_entity(obj)),
- )
-
+ tool.Ifc,
+ tool.Aggregate,
+ tool.Collector,
+ relating_obj=tool.Ifc.get_object(new_aggregate[0]),
+ related_obj=tool.Ifc.get_object(tool.Ifc.get_entity(obj)),
+ )
@classmethod
def run_geometry_add_representation(
diff --git a/src/blenderbim/blenderbim/tool/search.py b/src/blenderbim/blenderbim/tool/search.py
index 834b99304c..cbad2ac02e 100644
--- a/src/blenderbim/blenderbim/tool/search.py
+++ b/src/blenderbim/blenderbim/tool/search.py
@@ -5,32 +5,41 @@ import blenderbim.core.tool
import blenderbim.tool as tool
import ifcopenshell.util.selector
from ifcopenshell.util.selector import Selector
+from blenderbim.bim.prop import BIMFacet
+from typing import Union, Literal
class Search(blenderbim.core.tool.Search):
@classmethod
- def get_group_query(cls, group):
+ def get_group_query(cls, group: ifcopenshell.entity_instance) -> str:
return json.loads(group.Description)["query"]
@classmethod
- def get_filter_groups(cls, module):
+ def get_filter_groups(cls, module: str) -> bpy.types.bpy_prop_collection:
if module == "search":
return bpy.context.scene.BIMSearchProperties.filter_groups
elif module == "csv":
return bpy.context.scene.CsvProperties.filter_groups
+ elif module == "diff":
+ return bpy.context.scene.DiffProperties.filter_groups
elif module == "drawing_include":
return bpy.context.active_object.data.BIMCameraProperties.include_filter_groups
elif module == "drawing_exclude":
return bpy.context.active_object.data.BIMCameraProperties.exclude_filter_groups
+ elif module.startswith("clash"):
+ _, clash_set_index, ab, clash_source_index = module.split("_")
+ return getattr(bpy.context.scene.BIMClashProperties.clash_sets[int(clash_set_index)], ab)[
+ int(clash_source_index)
+ ].filter_groups
@classmethod
- def import_filter_query(cls, query, filter_groups):
+ def import_filter_query(cls, query: str, filter_groups: bpy.types.bpy_prop_collection) -> None:
filter_groups.clear()
transformer = ImportFilterQueryTransformer(filter_groups)
transformer.transform(ifcopenshell.util.selector.filter_elements_grammar.parse(query))
@classmethod
- def export_filter_query(cls, filter_groups):
+ def export_filter_query(cls, filter_groups: bpy.types.bpy_prop_collection) -> str:
query = []
for filter_group in filter_groups:
filter_group_query = []
@@ -87,13 +96,15 @@ class Search(blenderbim.core.tool.Search):
return " + ".join(query)
@classmethod
- def get_comparison_and_value(cls, ifc_filter):
+ def get_comparison_and_value(
+ cls, ifc_filter: BIMFacet
+ ) -> Union[tuple[Literal["!="], str], tuple[Literal["="], str]]:
if ifc_filter.value.startswith("!="):
return ("!=", cls.wrap_value(ifc_filter, ifc_filter.value[2:].strip()))
return ("=", cls.wrap_value(ifc_filter, ifc_filter.value.strip()))
@classmethod
- def wrap_value(cls, ifc_filter, value):
+ def wrap_value(cls, ifc_filter: BIMFacet, value: str) -> str:
if value.startswith("/") and value.endswith("/"):
return value
elif value in ("NULL", "TRUE", "FALSE"):
@@ -101,7 +112,7 @@ class Search(blenderbim.core.tool.Search):
return '"' + value.replace('"', '\\"') + '"'
@classmethod
- def from_selector_query(cls, query):
+ def from_selector_query(cls, query: str) -> list[ifcopenshell.entity_instance]:
"""Returns a list of products from a selector query"""
return Selector().parse(tool.Ifc.get(), query)
@@ -143,7 +154,7 @@ class ImportFilterQueryTransformer(lark.Transformer):
if "pset" in arg:
new2.pset = arg["pset"]
if "comparison" in arg:
- new2.comparison = arg["comparison"]
+ new2.comparison = arg["comparison"] or "="
def facet(self, args):
return args[0]
@@ -198,7 +209,7 @@ class ImportFilterQueryTransformer(lark.Transformer):
return (
is_not
+ {
- "equals": "=",
+ "equals": "=" if is_not else "", # Blank because it's the default situation
"morethanequalto": ">=",
"lessthanequalto": "<=",
"morethan": ">",
diff --git a/src/blenderbim/blenderbim/tool/spatial.py b/src/blenderbim/blenderbim/tool/spatial.py
index 033b712384..48d2b54274 100644
--- a/src/blenderbim/blenderbim/tool/spatial.py
+++ b/src/blenderbim/blenderbim/tool/spatial.py
@@ -20,6 +20,7 @@ import bpy
import bmesh
import shapely
import ifcopenshell
+import ifcopenshell.util.element
import blenderbim.core.type
import blenderbim.core.tool
import blenderbim.core.root
@@ -34,7 +35,7 @@ from shapely import Polygon, MultiPolygon
class Spatial(blenderbim.core.tool.Spatial):
@classmethod
- def can_contain(cls, structure_obj, element_obj):
+ def can_contain(cls, structure_obj: bpy.types.Object, element_obj: bpy.types.Object) -> bool:
structure = tool.Ifc.get_entity(structure_obj)
element = tool.Ifc.get_entity(element_obj)
if not structure or not element:
@@ -52,7 +53,7 @@ class Spatial(blenderbim.core.tool.Spatial):
return True
@classmethod
- def can_reference(cls, structure, element):
+ def can_reference(cls, structure: ifcopenshell.entity_instance, element: ifcopenshell.entity_instance) -> bool:
if not structure or not element:
return False
if tool.Ifc.get_schema() == "IFC2X3":
@@ -150,9 +151,9 @@ class Spatial(blenderbim.core.tool.Spatial):
for product in products:
obj = tool.Ifc.get_object(product)
if obj and bpy.context.view_layer.objects.get(obj.name):
- obj.select_set(True)
if unhide:
obj.hide_set(False)
+ obj.select_set(True)
@classmethod
def filter_products(cls, products, action):
@@ -689,7 +690,7 @@ class Spatial(blenderbim.core.tool.Spatial):
instance_class = ifcopenshell.util.type.get_applicable_entities(ifc_class, tool.Ifc.get().schema)[0]
bpy.ops.bim.assign_class(obj=obj.name, ifc_class=instance_class)
element = tool.Ifc.get_entity(obj)
- tool.Ifc.run("type.assign_type", related_object=element, relating_type=relating_type)
+ tool.Ifc.run("type.assign_type", related_objects=[element], relating_type=relating_type)
@classmethod
def assign_relating_type_to_element(cls, ifc, Type, element, relating_type):
diff --git a/src/blenderbim/blenderbim/tool/style.py b/src/blenderbim/blenderbim/tool/style.py
index ee7f0b021b..30628cb1ef 100644
--- a/src/blenderbim/blenderbim/tool/style.py
+++ b/src/blenderbim/blenderbim/tool/style.py
@@ -23,6 +23,7 @@ import blenderbim.core.tool
import blenderbim.tool as tool
import blenderbim.bim.helper
from mathutils import Color
+from typing import Union
# fmt: off
TEXTURE_MAPS_BY_METHODS = {
@@ -95,7 +96,7 @@ class Style(blenderbim.core.tool.Style):
return obj.name
@classmethod
- def get_style(cls, obj):
+ def get_style(cls, obj: bpy.types.Material) -> Union[ifcopenshell.entity_instance, None]:
if obj.BIMMaterialProperties.ifc_style_id:
try:
return tool.Ifc.get().by_id(obj.BIMMaterialProperties.ifc_style_id)
@@ -103,7 +104,9 @@ class Style(blenderbim.core.tool.Style):
return
@classmethod
- def get_style_elements(cls, blender_material_or_style):
+ def get_style_elements(
+ cls, blender_material_or_style: Union[bpy.types.Material, ifcopenshell.entity_instance]
+ ) -> dict[str, ifcopenshell.entity_instance]:
if isinstance(blender_material_or_style, bpy.types.Material):
if not blender_material_or_style.BIMMaterialProperties.ifc_style_id:
return {}
@@ -454,7 +457,7 @@ class Style(blenderbim.core.tool.Style):
return results
@classmethod
- def get_style_ui_props_attributes(self, style_type):
+ def get_style_ui_props_attributes(cls, style_type):
props = bpy.context.scene.BIMStylesProperties
if style_type == "IfcExternallyDefinedSurfaceStyle":
return props.external_style_attributes
@@ -499,7 +502,7 @@ class Style(blenderbim.core.tool.Style):
blenderbim.bim.helper.import_attributes2(style, attributes)
@classmethod
- def has_blender_external_style(cls, style_elements):
+ def has_blender_external_style(cls, style_elements: dict[str, ifcopenshell.entity_instance]) -> bool:
external_style = style_elements.get("IfcExternallyDefinedSurfaceStyle", None)
return bool(external_style and external_style.Location and external_style.Location.endswith(".blend"))
diff --git a/src/blenderbim/docs/_static/custom.css b/src/blenderbim/docs/_static/custom.css
index ecd7ac3d44..03f5b9016f 100644
--- a/src/blenderbim/docs/_static/custom.css
+++ b/src/blenderbim/docs/_static/custom.css
@@ -1,12 +1,44 @@
+@import url("https://fonts.googleapis.com/css2?family=Nunito:wght@200;300;400;500;600;700;800&display=swap");
+@import url('https://fonts.googleapis.com/css2?family=Inconsolata:wght@200;300;400;500;600;700&display=swap');
+h1, h2, h3, h4 {
+ font-weight: normal;
+ color: #7cbf33;
+ background: -webkit-linear-gradient(0deg, #7cbf33, #049344);
+ background-clip: border-box;
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+}
+a {
+ text-decoration: none;
+}
+.toc-tree .reference:hover {
+ color: #d98014;
+}
+.sidebar-brand-text {
+ font-size: 1rem;
+}
.blockbutton {
- border: 1px solid var(--color-admonition-title--seealso);
- background-color: var(--color-admonition-title-background--seealso);
- padding: 5px;
- text-align: center;
- border-radius: 5px;
max-width: 500px;
margin-left: auto;
margin-right: auto;
+ text-align: center;
+}
+.blockbutton a {
+ display: block;
+ padding-top: 15px;
+ padding-bottom: 15px;
+ border-radius: 5px;
+ background: #40b74c;
+ width: 100%;
+ height: 100%;
+ color: #fff;
+ text-decoration: none;
+}
+.blockbutton a:hover {
+ background: #70ba35;
+}
+.highlight .hll {
+ background-color: #ffc2;
}
section img {
display: block;
diff --git a/src/blenderbim/docs/conf.py b/src/blenderbim/docs/conf.py
index a589be30ca..69afbca790 100644
--- a/src/blenderbim/docs/conf.py
+++ b/src/blenderbim/docs/conf.py
@@ -36,11 +36,11 @@
# -- Project information -----------------------------------------------------
project = "BlenderBIM Add-on"
-copyright = "2020-2023 IfcOpenShell Contributors"
+copyright = "2020-2024 IfcOpenShell Contributors"
author = "IfcOpenShell Contributors"
# The full version, including alpha/beta/rc tags
-release = "0.0.231104"
+release = "0.0.240402"
# -- General configuration ---------------------------------------------------
@@ -48,7 +48,7 @@ release = "0.0.231104"
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
-extensions = ["sphinx.ext.autodoc", "sphinx.ext.autosectionlabel"]
+extensions = ["sphinx.ext.autodoc", "sphinx.ext.autosectionlabel", "sphinx_copybutton"]
# Auto add document prefixes to help guarantee uniqueness of automatic section references.
autosectionlabel_prefix_document = True
@@ -75,3 +75,59 @@ html_theme = "furo"
html_static_path = ["_static"]
html_css_files = ["custom.css"]
+
+# Code block styles. Dark styling helps important code examples "pop" on the
+# page even on light themes.
+pygments_style = "one-dark"
+pygments_dark_style = "one-dark"
+
+html_logo = "https://blenderbim.org/assets/images/blender/blender-logo.png"
+html_theme_options = {
+ "source_repository": "https://github.com/IfcOpenShell/IfcOpenShell/",
+ "source_branch": "v0.7.0",
+ "source_directory": "src/blenderbim/docs/",
+ "light_css_variables": {
+ "color-brand-primary": "#39b54a",
+ "color-brand-content": "#39b54a",
+ "color-brand-visited": "#d9e021",
+ "color-background-primary": "#f7f7f6",
+ "color-background-secondary": "#eeeeec",
+ "color-background-border": "#cfd0cb",
+ "color-foreground-primary": "#2e3436",
+ "color-sidebar-item-background--hover": "#f7f7f6",
+ "color-link--hover": "#d98014",
+ "font-stack": "Nunito, -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji"
+ },
+ "dark_css_variables": {
+ "color-brand-primary": "#39b54a",
+ "color-brand-content": "#39b54a",
+ "color-brand-visited": "#d9e021",
+ "color-background-primary": "#2e3436",
+ "color-background-border": "#2e3436",
+ "color-foreground-primary": "#eeeeec",
+ "color-sidebar-item-background--hover": "#2e3436",
+ "color-link--hover": "#d98014",
+ "font-stack": "Nunito, -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji"
+ },
+
+ "footer_icons": [
+ {
+ "name": "IfcOpenShell",
+ "url": "https://ifcopenshell.org",
+ "html": """
+
+ """,
+ "class": "",
+ },
+ {
+ "name": "GitHub",
+ "url": "https://github.com/IfcOpenShell/IfcOpenShell/tree/v0.7.0/src/blenderbim/docs",
+ "html": """
+
+ """,
+ "class": "",
+ },
+ ],
+}
diff --git a/src/blenderbim/docs/devs/code_style.rst b/src/blenderbim/docs/devs/code_style.rst
new file mode 100644
index 0000000000..2ee2b5f204
--- /dev/null
+++ b/src/blenderbim/docs/devs/code_style.rst
@@ -0,0 +1,20 @@
+Code style
+============
+
+
+Black code formatter
+-------------------------------
+For Python code formatting, we use `Black code formatter `__ with ``--line-length 120``.
+
+``black`` can be installed using ``pip install black`` and files can be formatted with the following example command:
+
+.. code-block:: bash
+
+ black --line-length 120 src/blenderbim/blenderbim/bim/module/qto/operator.py
+
+Using PowerShell, you can run the Black formatter on the last commit in the repository.
+You can change ``~1`` to ``~n`` to affect ``n`` commits.
+
+.. code-block:: powershell
+
+ git diff HEAD HEAD~1 --name-only | where {$_ -like "*.py"} | foreach-object { start $_ && black --line-length 120 $_ }
diff --git a/src/blenderbim/docs/devs/installation.rst b/src/blenderbim/docs/devs/installation.rst
index a7b47335ab..a9b5135e66 100644
--- a/src/blenderbim/docs/devs/installation.rst
+++ b/src/blenderbim/docs/devs/installation.rst
@@ -22,8 +22,8 @@ You will need to choose which build to download.
- If you are on Blender >=4.1, choose py311
- If you are on Blender >=3.1 and <=4.0, choose py10
- If you are on Blender >=2.93 and <3.1, choose py39
-- Choose ``linux``, ``macos``, ``macosm1`` (for Apple M1 devices), or ``win``
- depending on your operating system
+- Choose ``linux``, ``macos`` (Apple Intel), ``macosm1`` (Apple Silicon), or
+ ``win`` depending on your operating system
Sometimes, a build may be delayed, or contain broken code. We try to avoid this,
but it happens.
@@ -43,11 +43,11 @@ compile IfcOpenShell is out of scope of this document.
You can create your own package by using the Makefile as shown below. You can
choose between a ``PLATFORM`` of ``linux``, ``macos``, ``macosm1``, and ``win``.
-You can choose between a ``PYVERSION`` of ``py39``, or ``py310``.
+You can choose between a ``PYVERSION`` of ``py311``, ``py310``, or ``py39``.
::
$ cd src/blenderbim
- $ make dist PLATFORM=linux PYVERSION=py310
+ $ make dist PLATFORM=linux PYVERSION=py311
$ ls dist/
This will give you a fully packaged Blender add-on zip that you can distribute
@@ -258,10 +258,12 @@ Required Python modules to be stored in ``libs/site/packages/`` are:
ifcclash
bimtester
ifccobie
- ifcdiff
ifccsv
+ ifcdiff
+ ifc4d
+ ifc5d
ifcpatch
- ifcp6
+ ifctester
pystache
svgwrite
dateutil
@@ -276,17 +278,13 @@ Required Python modules to be stored in ``libs/site/packages/`` are:
elementpath
six
lark-parser
- hppfcl
behave
parse
parse_type
xlsxwriter
odfpy
defusedxml
- boto3
- botocore
jmespath
- s3transfer
ifcjson
Notes:
diff --git a/src/blenderbim/docs/devs/system_support.rst b/src/blenderbim/docs/devs/system_support.rst
index 3bd7bcb807..9d8519e7b3 100644
--- a/src/blenderbim/docs/devs/system_support.rst
+++ b/src/blenderbim/docs/devs/system_support.rst
@@ -6,9 +6,13 @@ the Python version shipped by the Blender Foundation for the most recent three
major Blender versions:
- 64-bit Linux on Python 3.10
-- 64-bit MacOS on Python 3.10
-- 64-bit MacOS M1 on Python 3.10
+- 64-bit Linux on Python 3.11
+- 64-bit MacOS Intel on Python 3.10
+- 64-bit MacOS Intel on Python 3.11
+- 64-bit MacOS Silicon on Python 3.10
+- 64-bit MacOS Silicon on Python 3.11
- 64-bit Windows on Python 3.10
+- 64-bit Windows on Python 3.11
Note that developer builds may exist for older versions of Python but there will
be no guarantee of the uptime or stability of these builds.
diff --git a/src/blenderbim/docs/devs/writing_docs.rst b/src/blenderbim/docs/devs/writing_docs.rst
index b40bccf4a3..f888b398da 100644
--- a/src/blenderbim/docs/devs/writing_docs.rst
+++ b/src/blenderbim/docs/devs/writing_docs.rst
@@ -12,6 +12,8 @@ Writing technical documentation
All documentation is written in ReStructured Text and is available in the
`BlenderBIM Add-on docs directory
`_.
+You can press the edit button on the top right on any documentation page to
+quickly edit their content.
You can link to `external websites
`_.
@@ -86,3 +88,27 @@ Tables can be very annoying to format. You can use a CSV table instead.
"ABC", "01", "02"
"DEF", "03", "04"
+
+Building documentation
+----------------------
+
+If you want to build the documentation locally, the documentation system uses
+`Sphinx `_. First, install the theme and
+theme dependencies:
+
+.. code-block:: console
+
+ $ pip install furo
+ $ pip install sphinx-autoapi
+ $ pip install sphinx-copybutton
+
+Now you can generate the documentation:
+
+.. code-block:: console
+
+ $ cd /path/to/ifcopenshell/src/blenderbim/docs/
+ $ make html
+ $ cd _build/html
+ $ python -m http.server
+
+You will now have a local webserver running hosting the documentation.
diff --git a/src/blenderbim/docs/index.rst b/src/blenderbim/docs/index.rst
index 662b914e17..b8f21e5a6b 100644
--- a/src/blenderbim/docs/index.rst
+++ b/src/blenderbim/docs/index.rst
@@ -40,6 +40,7 @@ Learn how to model a small building and create simple architectural plans, secti
devs/installation
devs/hello_world
+ devs/code_style
devs/running_tests
devs/writing_docs
devs/translations
diff --git a/src/blenderbim/docs/users/creating_an_ifc_model.rst b/src/blenderbim/docs/users/creating_an_ifc_model.rst
index 623956d9b6..f9cf3c6031 100644
--- a/src/blenderbim/docs/users/creating_an_ifc_model.rst
+++ b/src/blenderbim/docs/users/creating_an_ifc_model.rst
@@ -70,8 +70,8 @@ In the top left **File** menu, Save your new IFC model on your computer.
Congratulations! You have now created your first OpenBIM model from Blender! You
can open the IFC file in any other program, and you will see something similar
to the image below. Three simple open source online viewers you can test with
-are `IfcPipeline `__, `IFC.JS
-`__, and `3DViewer
+are `IfcPipeline `__, `ThatOpenEditor
+`__, and `3DViewer
`__.
.. image:: images/ifc-pipeline.png
diff --git a/src/blenderbim/docs/users/installation.rst b/src/blenderbim/docs/users/installation.rst
index fd23a05832..c429c89012 100644
--- a/src/blenderbim/docs/users/installation.rst
+++ b/src/blenderbim/docs/users/installation.rst
@@ -25,7 +25,7 @@ Installation
.. warning::
- If you are not using Blender version >=3.1, please follow the **Unstable installation** instructions. :doc:`Read more <../devs/installation>`
+ If you are not using Blender version >=4.1, please follow the **Unstable installation** instructions. :doc:`Read more <../devs/installation>`
3. **Install the BlenderBIM Add-on**
@@ -130,29 +130,7 @@ directory in your Blender add-ons directory.
FAQ
---
-1. **I get an error similar to "ImportError: IfcOpenShell not built for 'linux/64bit/python3.7'"**
-
- Check which BlenderBIM Add-on build you are using. The zip will have either
- ``py39`` or ``py310`` in the name. If you are using a Mac, also make sure
- you are using the M1 version if you have a newer Mac. See the instructions
- in the :ref:`devs/installation:unstable installation` section to check that
- you have installed the correct version.
-
-2. **I am on Ubuntu and get an error similar to "ImportError:
- /lib/x86_64-linux-gnu/libm.so.6: version GLIBC_2.29 not found"**
-
- Our latest package which uses IfcOpenShell v0.7.0 is built using Ubuntu 20 LTS.
- If you have an older Ubuntu version, you can either upgrade to 19.10 or above,
- or you'll need to compile IfcOpenShell yourself.
-
-3. **I get an error saying "ModuleNotFoundError: No module named 'numpy'"**"
-
- If you have installed Blender from another source instead of from
- `Blender.org `__, such as from your
- distro's package repositories, then you may be missing some modules like
- ``numpy``. Try installing it manually like ``apt install python-numpy``.
-
-4. **Some other error prevents me from installing or doing basic functions with
+1. **Some other error prevents me from installing or doing basic functions with
the add-on. Is it specific to my environment?**
Sometimes it is helpful to try installing and using the BlenderBIM Add-on on
@@ -167,6 +145,31 @@ FAQ
If this fixes your issue, consider disabling other add-ons one by one until
you find a conflict as a next step to isolating the issue.
+2. **I get an error similar to "ImportError: IfcOpenShell not built for 'linux/64bit/python3.10'"**
+
+ If you are using a Mac, be sure to use the Mac Silicon version if you have a
+ newer Mac. The only exception is if you have installed Blender using Steam
+ on a Mac, in which case you need to use the Mac Intel download.
+
+ For all other scenarios, check the BlenderBIM Add-on zip file which you
+ downloaded. The zip will have either ``py39``, ``py310``, or ``py311`` in
+ the name. See the instructions in the :ref:`devs/installation:unstable
+ installation` section to check that you have installed the correct version.
+
+3. **I am on Ubuntu and get an error similar to "ImportError:
+ /lib/x86_64-linux-gnu/libm.so.6: version GLIBC_2.29 not found"**
+
+ Our latest package which uses IfcOpenShell v0.7.0 is built using Ubuntu 20 LTS.
+ If you have an older Ubuntu version, you can either upgrade to 19.10 or above,
+ or you'll need to compile IfcOpenShell yourself.
+
+4. **I get an error saying "ModuleNotFoundError: No module named 'numpy'"**"
+
+ If you have installed Blender from another source instead of from
+ `Blender.org `__, such as from your
+ distro's package repositories, then you may be missing some modules like
+ ``numpy``. Try installing it manually like ``apt install python-numpy``.
+
5. **I get an error similar to RuntimeError: Instance #1234 not found**
Blender saves and loads projects to a ``.blend`` file. However. the
diff --git a/src/blenderbim/docs/users/other_addons.rst b/src/blenderbim/docs/users/other_addons.rst
index 88d4c4ff7a..7ae799a6fb 100644
--- a/src/blenderbim/docs/users/other_addons.rst
+++ b/src/blenderbim/docs/users/other_addons.rst
@@ -29,7 +29,9 @@ Some of these add-ons are not shipped with Blender:
- `Sverchok `__ - Sverchok is a visual
programming add-on for Blender that allows you to generate parametric
geometry, create scripts for non-programmers, model solids from FreeCAD, and
- much more.
+ much more. There is also
+ `IfcSverchok `__
+ that adds IFC features to Sverchok.
- `BlenderGIS `__ - BlenderGIS lets you
import GIS data, grab elevation data from the web, and generate TINs from
survey points and contours.
diff --git a/src/blenderbim/scripts/entourage-library.blend b/src/blenderbim/scripts/entourage-library.blend
new file mode 100644
index 0000000000..f23774dea8
Binary files /dev/null and b/src/blenderbim/scripts/entourage-library.blend differ
diff --git a/src/blenderbim/scripts/generate_au_library.py b/src/blenderbim/scripts/generate_au_library.py
index e1895fb44b..e75cf739b0 100644
--- a/src/blenderbim/scripts/generate_au_library.py
+++ b/src/blenderbim/scripts/generate_au_library.py
@@ -188,7 +188,7 @@ class LibraryGenerator:
def create_layer_set_type(self, name, data):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class=data["type"], name=name)
element.Description = data["Description"]
- rel = ifcopenshell.api.run("material.assign_material", self.file, product=element, type="IfcMaterialLayerSet")
+ rel = ifcopenshell.api.run("material.assign_material", self.file, products=[element], type="IfcMaterialLayerSet")
layer_set = rel.RelatingMaterial
for layer_data in data["Layers"]:
layer = ifcopenshell.api.run(
@@ -201,7 +201,7 @@ class LibraryGenerator:
def create_layer_type(self, ifc_class, name, thickness):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class=ifc_class, name=name)
- rel = ifcopenshell.api.run("material.assign_material", self.file, product=element, type="IfcMaterialLayerSet")
+ rel = ifcopenshell.api.run("material.assign_material", self.file, products=[element], type="IfcMaterialLayerSet")
layer_set = rel.RelatingMaterial
layer = ifcopenshell.api.run("material.add_layer", self.file, layer_set=layer_set, material=self.materials["TBD"]["ifc"])
layer.LayerThickness = thickness
@@ -210,7 +210,7 @@ class LibraryGenerator:
def create_profile_type(self, ifc_class, name, profile):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class=ifc_class, name=name)
- rel = ifcopenshell.api.run("material.assign_material", self.file, product=element, type="IfcMaterialProfileSet")
+ rel = ifcopenshell.api.run("material.assign_material", self.file, products=[element], type="IfcMaterialProfileSet")
profile_set = rel.RelatingMaterial
material_profile = ifcopenshell.api.run(
"material.add_profile", self.file, profile_set=profile_set, material=self.materials["TBD"]["ifc"]
diff --git a/src/blenderbim/scripts/generate_demo_library.py b/src/blenderbim/scripts/generate_demo_library.py
index 26ab3ad278..14f97bc52d 100644
--- a/src/blenderbim/scripts/generate_demo_library.py
+++ b/src/blenderbim/scripts/generate_demo_library.py
@@ -205,7 +205,7 @@ class LibraryGenerator:
def create_layer_type(self, ifc_class, name, thickness):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class=ifc_class, name=name)
- rel = ifcopenshell.api.run("material.assign_material", self.file, product=element, type="IfcMaterialLayerSet")
+ rel = ifcopenshell.api.run("material.assign_material", self.file, products=[element], type="IfcMaterialLayerSet")
layer_set = rel.RelatingMaterial
layer = ifcopenshell.api.run("material.add_layer", self.file, layer_set=layer_set, material=self.material)
layer.LayerThickness = thickness
@@ -214,7 +214,7 @@ class LibraryGenerator:
def create_profile_type(self, ifc_class, name, profile):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class=ifc_class, name=name)
- rel = ifcopenshell.api.run("material.assign_material", self.file, product=element, type="IfcMaterialProfileSet")
+ rel = ifcopenshell.api.run("material.assign_material", self.file, products=[element], type="IfcMaterialProfileSet")
profile_set = rel.RelatingMaterial
material_profile = ifcopenshell.api.run(
"material.add_profile", self.file, profile_set=profile_set, material=self.material
diff --git a/src/blenderbim/scripts/generate_entourage_library.py b/src/blenderbim/scripts/generate_entourage_library.py
new file mode 100644
index 0000000000..a84c73e4ef
--- /dev/null
+++ b/src/blenderbim/scripts/generate_entourage_library.py
@@ -0,0 +1,138 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2024 Dion Moult
+#
+# 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 .
+
+import os
+import bpy
+import ifcopenshell
+import ifcopenshell.api
+import blenderbim.tool as tool
+from pathlib import Path
+
+
+# When run from Blender
+BLEND_DIR = os.path.dirname(bpy.data.filepath)
+OUT_PATH = os.path.join(BLEND_DIR, "..", "blenderbim", "bim", "data", "libraries", "IFC4 Entourage Library.ifc")
+
+
+class LibraryGenerator:
+ def generate(self, library_name, output_filename):
+ ifcopenshell.api.pre_listeners = {}
+ ifcopenshell.api.post_listeners = {}
+
+ self.materials = {}
+
+ self.file = ifcopenshell.api.run("project.create_file")
+ self.project = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject", name=library_name)
+ self.library = ifcopenshell.api.run(
+ "root.create_entity", self.file, ifc_class="IfcProjectLibrary", name=library_name
+ )
+ ifcopenshell.api.run(
+ "project.assign_declaration", self.file, definition=self.library, relating_context=self.project
+ )
+ unit = ifcopenshell.api.run("unit.add_si_unit", self.file, unit_type="LENGTHUNIT", prefix="MILLI")
+ ifcopenshell.api.run("unit.assign_unit", self.file, units=[unit])
+
+ model = ifcopenshell.api.run("context.add_context", self.file, context_type="Model")
+ plan = ifcopenshell.api.run("context.add_context", self.file, context_type="Plan")
+ self.representations = {
+ "Model/Body/MODEL_VIEW": ifcopenshell.api.run(
+ "context.add_context",
+ self.file,
+ context_type="Model",
+ context_identifier="Body",
+ target_view="MODEL_VIEW",
+ parent=model,
+ ),
+ "Plan/Body/PLAN_VIEW": ifcopenshell.api.run(
+ "context.add_context",
+ self.file,
+ context_type="Plan",
+ context_identifier="Body",
+ target_view="PLAN_VIEW",
+ parent=plan,
+ ),
+ "Model/Body/PLAN_VIEW": ifcopenshell.api.run(
+ "context.add_context",
+ self.file,
+ context_type="Model",
+ context_identifier="Body",
+ target_view="PLAN_VIEW",
+ parent=model,
+ ),
+ "Model/Body/SECTION_VIEW": ifcopenshell.api.run(
+ "context.add_context",
+ self.file,
+ context_type="Model",
+ context_identifier="Body",
+ target_view="SECTION_VIEW",
+ parent=model,
+ ),
+ }
+
+ # Manually modeled trees
+ for obj in bpy.data.objects:
+ if not obj.type == "MESH":
+ continue
+ if "Plan/" in obj.name or "Model/" in obj.name:
+ continue
+ representations = {"Model/Body/MODEL_VIEW": obj.name}
+ for rep_key in self.representations.keys():
+ rep_obj = bpy.data.objects.get(obj.name + " " + rep_key)
+ if rep_obj:
+ representations[rep_key] = rep_obj.name
+ self.create_type("IfcBuildingElementProxyType", obj.name, representations)
+
+ self.file.write(output_filename)
+
+ def create_type(self, ifc_class, name, representations):
+ element = ifcopenshell.api.run(
+ "root.create_entity", self.file, ifc_class=ifc_class, predefined_type="ENTOURAGE", name=name
+ )
+ for rep_name, obj_name in representations.items():
+ obj = bpy.data.objects.get(obj_name)
+ representation = ifcopenshell.api.run(
+ "geometry.add_representation",
+ self.file,
+ context=self.representations[rep_name],
+ blender_object=obj,
+ geometry=obj.data,
+ total_items=max(1, len(obj.material_slots)),
+ )
+ styles = []
+ for slot in obj.material_slots:
+ style = ifcopenshell.api.run("style.add_style", self.file, name=slot.material.name)
+ ifcopenshell.api.run(
+ "style.add_surface_style",
+ self.file,
+ style=style,
+ ifc_class="IfcSurfaceStyleShading",
+ attributes=tool.Style.get_surface_shading_attributes(slot.material),
+ )
+ styles.append(style)
+ if styles:
+ ifcopenshell.api.run(
+ "style.assign_representation_styles", self.file, shape_representation=representation, styles=styles
+ )
+ ifcopenshell.api.run(
+ "geometry.assign_representation", self.file, product=element, representation=representation
+ )
+ ifcopenshell.api.run("project.assign_declaration", self.file, definition=element, relating_context=self.library)
+
+
+if __name__ == "__main__":
+ LibraryGenerator().generate("Entourage Assets Library", output_filename=OUT_PATH)
diff --git a/src/blenderbim/scripts/generate_furniture_library.py b/src/blenderbim/scripts/generate_furniture_library.py
index 2837096df7..6d38170ed0 100644
--- a/src/blenderbim/scripts/generate_furniture_library.py
+++ b/src/blenderbim/scripts/generate_furniture_library.py
@@ -1803,7 +1803,7 @@ class LibraryGenerator:
def create_layer_set_type(self, name, data):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class=data["type"], name=name)
element.Description = data["Description"]
- rel = ifcopenshell.api.run("material.assign_material", self.file, product=element, type="IfcMaterialLayerSet")
+ rel = ifcopenshell.api.run("material.assign_material", self.file, products=[element], type="IfcMaterialLayerSet")
layer_set = rel.RelatingMaterial
for layer_data in data["Layers"]:
layer = ifcopenshell.api.run(
@@ -1816,7 +1816,7 @@ class LibraryGenerator:
def create_layer_type(self, ifc_class, name, thickness):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class=ifc_class, name=name)
- rel = ifcopenshell.api.run("material.assign_material", self.file, product=element, type="IfcMaterialLayerSet")
+ rel = ifcopenshell.api.run("material.assign_material", self.file, products=[element], type="IfcMaterialLayerSet")
layer_set = rel.RelatingMaterial
layer = ifcopenshell.api.run(
"material.add_layer", self.file, layer_set=layer_set, material=self.materials["TBD"]["ifc"]
@@ -1827,7 +1827,7 @@ class LibraryGenerator:
def create_profile_type(self, ifc_class, name, profile):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class=ifc_class, name=name)
- rel = ifcopenshell.api.run("material.assign_material", self.file, product=element, type="IfcMaterialProfileSet")
+ rel = ifcopenshell.api.run("material.assign_material", self.file, products=[element], type="IfcMaterialProfileSet")
profile_set = rel.RelatingMaterial
material_profile = ifcopenshell.api.run(
"material.add_profile",
diff --git a/src/blenderbim/scripts/generate_steel_profiles_library.py b/src/blenderbim/scripts/generate_steel_profiles_library.py
index 8f7765eb5d..685435660f 100644
--- a/src/blenderbim/scripts/generate_steel_profiles_library.py
+++ b/src/blenderbim/scripts/generate_steel_profiles_library.py
@@ -175,7 +175,7 @@ class LibraryGenerator:
def create_profile_type(self, ifc_class, name, profile):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class=ifc_class, name=name)
- rel = ifcopenshell.api.run("material.assign_material", self.file, product=element, type="IfcMaterialProfileSet")
+ rel = ifcopenshell.api.run("material.assign_material", self.file, products=[element], type="IfcMaterialProfileSet")
profile_set = rel.RelatingMaterial
material_profile = ifcopenshell.api.run(
"material.add_profile", self.file, profile_set=profile_set, material=self.material
diff --git a/src/blenderbim/scripts/obj2ifc-meshlab.py b/src/blenderbim/scripts/obj2ifc-meshlab.py
index c0130fb68a..2ecb5684fb 100644
--- a/src/blenderbim/scripts/obj2ifc-meshlab.py
+++ b/src/blenderbim/scripts/obj2ifc-meshlab.py
@@ -85,7 +85,9 @@ class Obj2Ifc:
Name=mesh.label() or self.basename,
Representation=representation,
)
- ifcopenshell.api.run("spatial.assign_container", self.file, product=product, relating_structure=self.storey)
+ ifcopenshell.api.run(
+ "spatial.assign_container", self.file, products=[product], relating_structure=self.storey
+ )
product.ObjectPlacement = self.placement
self.file.write(self.outfile)
@@ -125,9 +127,9 @@ class Obj2Ifc:
self.storey = ifcopenshell.api.run(
"root.create_entity", self.file, ifc_class="IfcBuildingStorey", name="My Storey"
)
- ifcopenshell.api.run("aggregate.assign_object", self.file, product=site, relating_object=project)
- ifcopenshell.api.run("aggregate.assign_object", self.file, product=building, relating_object=site)
- ifcopenshell.api.run("aggregate.assign_object", self.file, product=self.storey, relating_object=building)
+ ifcopenshell.api.run("aggregate.assign_object", self.file, products=[site], relating_object=project)
+ ifcopenshell.api.run("aggregate.assign_object", self.file, products=[building], relating_object=site)
+ ifcopenshell.api.run("aggregate.assign_object", self.file, products=[self.storey], relating_object=building)
ifcopenshell.api.run("geometry.edit_object_placement", self.file, product=site)
ifcopenshell.api.run("geometry.edit_object_placement", self.file, product=building)
diff --git a/src/blenderbim/scripts/obj2ifc.py b/src/blenderbim/scripts/obj2ifc.py
index e26a83898a..2999fbb6b6 100644
--- a/src/blenderbim/scripts/obj2ifc.py
+++ b/src/blenderbim/scripts/obj2ifc.py
@@ -79,7 +79,9 @@ class Obj2Ifc:
Name=mesh.name or self.basename,
Representation=representation,
)
- ifcopenshell.api.run("spatial.assign_container", self.file, product=product, relating_structure=self.storey)
+ ifcopenshell.api.run(
+ "spatial.assign_container", self.file, products=[product], relating_structure=self.storey
+ )
product.ObjectPlacement = self.placement
self.file.write(self.outfile)
@@ -118,9 +120,9 @@ class Obj2Ifc:
self.storey = ifcopenshell.api.run(
"root.create_entity", self.file, ifc_class="IfcBuildingStorey", name="My Storey"
)
- ifcopenshell.api.run("aggregate.assign_object", self.file, product=site, relating_object=project)
- ifcopenshell.api.run("aggregate.assign_object", self.file, product=building, relating_object=site)
- ifcopenshell.api.run("aggregate.assign_object", self.file, product=self.storey, relating_object=building)
+ ifcopenshell.api.run("aggregate.assign_object", self.file, products=[site], relating_object=project)
+ ifcopenshell.api.run("aggregate.assign_object", self.file, products=[building], relating_object=site)
+ ifcopenshell.api.run("aggregate.assign_object", self.file, products=[self.storey], relating_object=building)
ifcopenshell.api.run("geometry.edit_object_placement", self.file, product=site)
ifcopenshell.api.run("geometry.edit_object_placement", self.file, product=building)
diff --git a/src/blenderbim/scripts/pkg_resources.tar.gz b/src/blenderbim/scripts/pkg_resources.tar.gz
new file mode 100644
index 0000000000..e2c00cda0d
Binary files /dev/null and b/src/blenderbim/scripts/pkg_resources.tar.gz differ
diff --git a/src/blenderbim/test/bim/feature/geometry.feature b/src/blenderbim/test/bim/feature/geometry.feature
index 465469537f..8e5a8c7214 100644
--- a/src/blenderbim/test/bim/feature/geometry.feature
+++ b/src/blenderbim/test/bim/feature/geometry.feature
@@ -438,7 +438,7 @@ Scenario: Override duplicate move - copying an aggregate
And the object "IfcElementAssembly/Assembly.001" exists
And the object "IfcElementAssembly/Assembly.001" is in the collection "IfcElementAssembly/Assembly.001"
And the collection "IfcElementAssembly/Assembly.001" is in the collection "IfcBuildingStorey/My Storey"
-
+
Scenario: Override duplicate move - copying objects with connection
Given an empty IFC project
And I load the demo construction library
@@ -467,7 +467,7 @@ Scenario: Override duplicate move - copying objects with connection
Then the object "IfcSlab/Slab.001" exists
And the variable "slab_name" is "[o.name for o in bpy.context.selected_objects if o.name == 'IfcSlab/Slab.001'][0]"
Then the object "{wall_name}" has a connection with "{slab_name}"
-
+
Scenario: Override duplicate move - copying walls with mitre joint
Given an empty IFC project
And I load the demo construction library
@@ -477,24 +477,24 @@ Scenario: Override duplicate move - copying walls with mitre joint
And I press "bim.hotkey(hotkey='S_A')"
And the cursor is at "0.5,0,0"
And I press "bim.hotkey(hotkey='S_A')"
- And the object "IfcWall/Wall" is selected
- And additionally the object "IfcWall/Wall.001" is selected
+ And the object "IfcWall/Wall.001" is selected
+ And additionally the object "IfcWall/Wall" is selected
When I press "bim.hotkey(hotkey='S_Y')"
- Then the object "IfcWall/Wall.001" dimensions are "0.5,0.1,3"
- And the object "IfcWall/Wall.001" bottom left corner is at "0.5,0,0"
- And the object "IfcWall/Wall" dimensions are "1.1,0.1,3"
- And the object "IfcWall/Wall" bottom left corner is at "0.5,0.1,0"
- And the object "IfcWall/Wall" top right corner is at "0.6,-1,3"
+ Then the object "IfcWall/Wall" dimensions are "0.5,0.1,3"
+ And the object "IfcWall/Wall" bottom left corner is at "0.5,0,0"
+ And the object "IfcWall/Wall.001" dimensions are "1.1,0.1,3"
+ And the object "IfcWall/Wall.001" bottom left corner is at "0.5,0.1,0"
+ And the object "IfcWall/Wall.001" top right corner is at "0.6,-1,3"
When I deselect all objects
- And the object "IfcWall/Wall" is selected
- And additionally the object "IfcWall/Wall.001" is selected
+ And the object "IfcWall/Wall.001" is selected
+ And additionally the object "IfcWall/Wall" is selected
When I duplicate the selected objects
Then the object "IfcWall/Wall.002" exists
And the variable "wall_name1" is "[o.name for o in bpy.context.selected_objects if o.name == 'IfcWall/Wall.002'][0]"
Then the object "IfcWall/Wall.003" exists
And the variable "wall_name2" is "[o.name for o in bpy.context.selected_objects if o.name == 'IfcWall/Wall.003'][0]"
Then the object "{wall_name1}" has a connection with "{wall_name2}"
-
+
Scenario: Override duplicate move linked - without active IFC data
Given an empty Blender session
And I add a cube
@@ -544,14 +544,14 @@ Scenario: Override paste buffer - with active IFC data
And the object "IfcWall/Cube" is selected
And additionally the object "IfcBuildingStorey/My Storey" is selected
When I press "view3d.copybuffer"
+ # IFC elements unlinked on paste for safety
And I press "bim.override_paste_buffer"
Then the object "IfcWall/Cube" exists
And the object "IfcWall/Cube" is an "IfcWall"
- And the object "IfcWall/Cube.001" exists
- And the object "IfcWall/Cube.001" is an "IfcWall"
- And the object "IfcWall/Cube.001" has a "Tessellation" representation of "Model/Body/MODEL_VIEW"
- And the object "IfcBuildingStorey/My Storey.001" exists
- And the object "IfcBuildingStorey/My Storey.001" is an "IfcBuildingStorey"
+ And the object "Cube.001" exists
+ And the object "Cube.001" is not an IFC element
+ And the object "My Storey.001" exists
+ And the object "My Storey.001" is not an IFC element
Scenario: Duplicate linked aggregate
Given I load the IFC test file "/test/files/linked-aggregates.ifc"
@@ -559,8 +559,8 @@ Scenario: Duplicate linked aggregate
When I duplicate linked aggregate the selected objects
Then the object "IfcWall/Wall_01.001" exists
And the object "IfcWall/Wall_02.001" exists
- And the object "IfcElementAssembly/Assembly_02" exists
- Then the object "IfcElementAssembly/Assembly" and "IfcElementAssembly/Assembly_02" belong to the same Linked Aggregate Group
+ And the object "IfcElementAssembly/Assembly_01" exists
+ Then the object "IfcElementAssembly/Assembly" and "IfcElementAssembly/Assembly_01" belong to the same Linked Aggregate Group
Scenario: Refresh linked aggregate
Given I load the IFC test file "/test/files/linked-aggregates.ifc"
@@ -588,7 +588,7 @@ Scenario: Refresh linked aggregate - after deleting an object
When I refresh linked aggregate the selected object
Then the object "IfcWall/Wall_01" does not exist
And the object "IfcWall/Wall_02" exists
-
+
Scenario: Refresh linked aggregate - after duplicating an object
Given I load the IFC test file "/test/files/linked-aggregates.ifc"
And the object "IfcWall/Wall_01" is selected
@@ -606,4 +606,4 @@ Scenario: Refresh linked aggregate - after duplicating an object
Then the object "IfcWall/Wall_01.001" exists
And the object "IfcWall/Wall_02.001" exists
And the object "IfcWall/Wall_03.001" exists
-
+
diff --git a/src/blenderbim/test/bim/feature/model.feature b/src/blenderbim/test/bim/feature/model.feature
index bdaa4a45f0..bf9208b012 100644
--- a/src/blenderbim/test/bim/feature/model.feature
+++ b/src/blenderbim/test/bim/feature/model.feature
@@ -107,11 +107,11 @@ Scenario: Add a wall perpendicular to an existing wall
And I press "bim.hotkey(hotkey='S_A')"
And the cursor is at "0.5,0,0"
When I press "bim.hotkey(hotkey='S_A')"
- Then the object "IfcWall/Wall.001" dimensions are "1,0.1,3"
- And the object "IfcWall/Wall.001" bottom left corner is at "0,0,0"
- And the object "IfcWall/Wall" dimensions are "1,0.1,3"
- And the object "IfcWall/Wall" bottom left corner is at "0.5,0,0"
- And the object "IfcWall/Wall" top right corner is at "0.6,-1,3"
+ Then the object "IfcWall/Wall" dimensions are "1,0.1,3"
+ And the object "IfcWall/Wall" bottom left corner is at "0,0,0"
+ And the object "IfcWall/Wall.001" dimensions are "1,0.1,3"
+ And the object "IfcWall/Wall.001" bottom left corner is at "0.5,0,0"
+ And the object "IfcWall/Wall.001" top right corner is at "0.6,-1,3"
Scenario: Extend one wall to another
Given an empty IFC project
@@ -122,35 +122,17 @@ Scenario: Extend one wall to another
And I press "bim.hotkey(hotkey='S_A')"
And the cursor is at "0.5,0,0"
And I press "bim.hotkey(hotkey='S_A')"
- And the object "IfcWall/Wall" is moved to "0.5,-1,0"
- And the object "IfcWall/Wall" is selected
- And additionally the object "IfcWall/Wall.001" is selected
+ And the object "IfcWall/Wall.001" is moved to "0.5,-1,0"
+ And the object "IfcWall/Wall.001" is selected
+ And additionally the object "IfcWall/Wall" is selected
When I press "bim.hotkey(hotkey='S_E')"
- Then the object "IfcWall/Wall.001" dimensions are "1,0.1,3"
- And the object "IfcWall/Wall.001" bottom left corner is at "0,0,0"
- And the object "IfcWall/Wall" dimensions are "2,0.1,3"
- And the object "IfcWall/Wall" bottom left corner is at "0.5,0,0"
- And the object "IfcWall/Wall" top right corner is at "0.6,-2,3"
+ Then the object "IfcWall/Wall" dimensions are "1,0.1,3"
+ And the object "IfcWall/Wall" bottom left corner is at "0,0,0"
+ And the object "IfcWall/Wall.001" dimensions are "2,0.1,3"
+ And the object "IfcWall/Wall.001" bottom left corner is at "0.5,0,0"
+ And the object "IfcWall/Wall.001" top right corner is at "0.6,-2,3"
Scenario: Join two walls with a butt joint - first wall has priority
- Given an empty IFC project
- And I load the demo construction library
- And I set "scene.BIMModelProperties.ifc_class" to "IfcWallType"
- And the variable "element_type" is "[e for e in {ifc}.by_type('IfcWallType') if e.Name == 'WAL100'][0].id()"
- And I set "scene.BIMModelProperties.relating_type_id" to "{element_type}"
- And I press "bim.hotkey(hotkey='S_A')"
- And the cursor is at "0.5,0,0"
- And I press "bim.hotkey(hotkey='S_A')"
- And the object "IfcWall/Wall" is selected
- And additionally the object "IfcWall/Wall.001" is selected
- When I press "bim.hotkey(hotkey='S_T')"
- Then the object "IfcWall/Wall.001" dimensions are "0.4,0.1,3"
- And the object "IfcWall/Wall.001" bottom left corner is at "0.6,0,0"
- And the object "IfcWall/Wall" dimensions are "1.1,0.1,3"
- And the object "IfcWall/Wall" bottom left corner is at "0.5,0.1,0"
- And the object "IfcWall/Wall" top right corner is at "0.6,-1,3"
-
-Scenario: Join two walls with a butt joint - second wall has priority
Given an empty IFC project
And I load the demo construction library
And I set "scene.BIMModelProperties.ifc_class" to "IfcWallType"
@@ -162,13 +144,13 @@ Scenario: Join two walls with a butt joint - second wall has priority
And the object "IfcWall/Wall.001" is selected
And additionally the object "IfcWall/Wall" is selected
When I press "bim.hotkey(hotkey='S_T')"
- Then the object "IfcWall/Wall.001" dimensions are "0.5,0.1,3"
- And the object "IfcWall/Wall.001" bottom left corner is at "0.5,0,0"
- And the object "IfcWall/Wall" dimensions are "1,0.1,3"
- And the object "IfcWall/Wall" bottom left corner is at "0.5,0,0"
- And the object "IfcWall/Wall" top right corner is at "0.6,-1,3"
+ Then the object "IfcWall/Wall" dimensions are "0.4,0.1,3"
+ And the object "IfcWall/Wall" bottom left corner is at "0.6,0,0"
+ And the object "IfcWall/Wall.001" dimensions are "1.1,0.1,3"
+ And the object "IfcWall/Wall.001" bottom left corner is at "0.5,0.1,0"
+ And the object "IfcWall/Wall.001" top right corner is at "0.6,-1,3"
-Scenario: Join two walls with a mitre joint
+Scenario: Join two walls with a butt joint - second wall has priority
Given an empty IFC project
And I load the demo construction library
And I set "scene.BIMModelProperties.ifc_class" to "IfcWallType"
@@ -179,12 +161,30 @@ Scenario: Join two walls with a mitre joint
And I press "bim.hotkey(hotkey='S_A')"
And the object "IfcWall/Wall" is selected
And additionally the object "IfcWall/Wall.001" is selected
- When I press "bim.hotkey(hotkey='S_Y')"
- Then the object "IfcWall/Wall.001" dimensions are "0.5,0.1,3"
+ When I press "bim.hotkey(hotkey='S_T')"
+ Then the object "IfcWall/Wall" dimensions are "0.5,0.1,3"
+ And the object "IfcWall/Wall" bottom left corner is at "0.5,0,0"
+ And the object "IfcWall/Wall.001" dimensions are "1,0.1,3"
And the object "IfcWall/Wall.001" bottom left corner is at "0.5,0,0"
- And the object "IfcWall/Wall" dimensions are "1.1,0.1,3"
- And the object "IfcWall/Wall" bottom left corner is at "0.5,0.1,0"
- And the object "IfcWall/Wall" top right corner is at "0.6,-1,3"
+ And the object "IfcWall/Wall.001" top right corner is at "0.6,-1,3"
+
+Scenario: Join two walls with a mitre joint
+ Given an empty IFC project
+ And I load the demo construction library
+ And I set "scene.BIMModelProperties.ifc_class" to "IfcWallType"
+ And the variable "element_type" is "[e for e in {ifc}.by_type('IfcWallType') if e.Name == 'WAL100'][0].id()"
+ And I set "scene.BIMModelProperties.relating_type_id" to "{element_type}"
+ And I press "bim.hotkey(hotkey='S_A')"
+ And the cursor is at "0.5,0,0"
+ And I press "bim.hotkey(hotkey='S_A')"
+ And the object "IfcWall/Wall.001" is selected
+ And additionally the object "IfcWall/Wall" is selected
+ When I press "bim.hotkey(hotkey='S_Y')"
+ Then the object "IfcWall/Wall" dimensions are "0.5,0.1,3"
+ And the object "IfcWall/Wall" bottom left corner is at "0.5,0,0"
+ And the object "IfcWall/Wall.001" dimensions are "1.1,0.1,3"
+ And the object "IfcWall/Wall.001" bottom left corner is at "0.5,0.1,0"
+ And the object "IfcWall/Wall.001" top right corner is at "0.6,-1,3"
Scenario: Change the height of a wall
Given an empty IFC project
diff --git a/src/blenderbim/test/bim/feature/project.feature b/src/blenderbim/test/bim/feature/project.feature
index 14a8a952c3..7433bcfd30 100644
--- a/src/blenderbim/test/bim/feature/project.feature
+++ b/src/blenderbim/test/bim/feature/project.feature
@@ -313,49 +313,65 @@ Scenario: Unload project
Scenario: Link IFC
Given an empty IFC project
- When I press "bim.link_ifc(filepath='{cwd}/test/files/basic.blend')"
- Then "scene.BIMProjectProperties.links['{cwd}/test/files/basic.blend'].is_loaded" is "True"
- And the object "IfcWall/Wall" exists
- And the object "IfcSlab/Slab" exists
- And the object "IfcElementAssembly/Empty" exists
- And the object "IfcBeam/Beam" exists
- And the object "IfcBuildingStorey/Ground Floor" exists
- And the object "IfcBuildingStorey/Level 1" exists
+ When I press "bim.link_ifc(filepath='{cwd}/test/files/basic.ifc')"
+ Then "scene.BIMProjectProperties.links['{cwd}/test/files/basic.ifc'].is_loaded" is "True"
+ And the collection "IfcProject/basic.ifc" exists
+ And the object "Chunk" exists
+ And the object "Chunk" is placed in the collection "IfcProject/basic.ifc"
Scenario: Toggle link visibility - wireframe mode
Given an empty IFC project
- And I press "bim.link_ifc(filepath='{cwd}/test/files/basic.blend')"
- When I press "bim.toggle_link_visibility(link='{cwd}/test/files/basic.blend', mode='WIREFRAME')"
- Then nothing happens
+ And I press "bim.link_ifc(filepath='{cwd}/test/files/basic.ifc')"
+ When I press "bim.toggle_link_visibility(link='{cwd}/test/files/basic.ifc', mode='WIREFRAME')"
+ Then "scene.BIMProjectProperties.links['{cwd}/test/files/basic.ifc'].is_wireframe" is "True"
+ And the object "Chunk" should display as "WIRE"
+ When I press "bim.toggle_link_visibility(link='{cwd}/test/files/basic.ifc', mode='WIREFRAME')"
+ Then "scene.BIMProjectProperties.links['{cwd}/test/files/basic.ifc'].is_wireframe" is "False"
+ And the object "Chunk" should display as "TEXTURED"
+
+Scenario: Toggle link selectability
+ Given an empty IFC project
+ And I press "bim.link_ifc(filepath='{cwd}/test/files/basic.ifc')"
+ When I press "bim.toggle_link_selectability(link='{cwd}/test/files/basic.ifc')"
+ Then "scene.BIMProjectProperties.links['{cwd}/test/files/basic.ifc'].is_selectable" is "False"
+ And the collection "IfcProject/basic.ifc" is unselectable
+ When I press "bim.toggle_link_selectability(link='{cwd}/test/files/basic.ifc')"
+ Then "scene.BIMProjectProperties.links['{cwd}/test/files/basic.ifc'].is_selectable" is "True"
+ And the collection "IfcProject/basic.ifc" is selectable
Scenario: Toggle link visibility - visible mode
Given an empty IFC project
- And I press "bim.link_ifc(filepath='{cwd}/test/files/basic.blend')"
- When I press "bim.toggle_link_visibility(link='{cwd}/test/files/basic.blend', mode='VISIBLE')"
- Then nothing happens
+ And I press "bim.link_ifc(filepath='{cwd}/test/files/basic.ifc')"
+ When I press "bim.toggle_link_visibility(link='{cwd}/test/files/basic.ifc', mode='VISIBLE')"
+ Then "scene.BIMProjectProperties.links['{cwd}/test/files/basic.ifc'].is_hidden" is "True"
+ And the collection "IfcProject/basic.ifc" exclude status is "True"
+ When I press "bim.toggle_link_visibility(link='{cwd}/test/files/basic.ifc', mode='VISIBLE')"
+ Then "scene.BIMProjectProperties.links['{cwd}/test/files/basic.ifc'].is_hidden" is "False"
+ And the collection "IfcProject/basic.ifc" exclude status is "False"
Scenario: Unload link
Given an empty Blender session
- And I press "bim.link_ifc(filepath='{cwd}/test/files/basic.blend')"
- When I press "bim.unload_link(filepath='{cwd}/test/files/basic.blend')"
- Then "scene.BIMProjectProperties.links['{cwd}/test/files/basic.blend'].is_loaded" is "False"
- And "scene.collection.children.get('IfcProject/My Project')" is "None"
+ And I press "bim.link_ifc(filepath='{cwd}/test/files/basic.ifc')"
+ When I press "bim.unload_link(filepath='{cwd}/test/files/basic.ifc')"
+ Then "scene.BIMProjectProperties.links['{cwd}/test/files/basic.ifc'].is_loaded" is "False"
+ And the collection "IfcProject/basic.ifc" does not exist
Scenario: Load link
Given an empty Blender session
- And I press "bim.link_ifc(filepath='{cwd}/test/files/basic.blend')"
- And I press "bim.unload_link(filepath='{cwd}/test/files/basic.blend')"
- When I press "bim.load_link(filepath='{cwd}/test/files/basic.blend')"
- Then "scene.BIMProjectProperties.links['{cwd}/test/files/basic.blend'].is_loaded" is "True"
- And "scene.collection.children['IfcProject/My Project'].users" is "2"
+ And I press "bim.link_ifc(filepath='{cwd}/test/files/basic.ifc')"
+ And I press "bim.unload_link(filepath='{cwd}/test/files/basic.ifc')"
+ When I press "bim.load_link(filepath='{cwd}/test/files/basic.ifc')"
+ Then "scene.BIMProjectProperties.links['{cwd}/test/files/basic.ifc'].is_loaded" is "True"
+ And the collection "IfcProject/basic.ifc" exists in viewlayer
Scenario: Unlink IFC
Given an empty Blender session
- And I press "bim.link_ifc(filepath='{cwd}/test/files/basic.blend')"
- And I press "bim.unload_link(filepath='{cwd}/test/files/basic.blend')"
- When I press "bim.unlink_ifc(filepath='{cwd}/test/files/basic.blend')"
- Then "scene.BIMProjectProperties.links.get('{cwd}/test/files/basic.blend')" is "None"
- And "scene.collection.children.get('IfcProject/My Project')" is "None"
+ And I press "bim.link_ifc(filepath='{cwd}/test/files/basic.ifc')"
+ And I press "bim.unload_link(filepath='{cwd}/test/files/basic.ifc')"
+ When I press "bim.unlink_ifc(filepath='{cwd}/test/files/basic.ifc')"
+ Then "scene.BIMProjectProperties.links.get('{cwd}/test/files/basic.ifc')" is "None"
+ And "scene.collection.children.get('IfcProject/basic.ifc')" is "None"
+ And the object "Chunk" does not exist
Scenario: Export IFC - blank project
Given an empty IFC project
diff --git a/src/blenderbim/test/bim/feature/pset.feature b/src/blenderbim/test/bim/feature/pset.feature
index 033c64627b..c769559e1f 100644
--- a/src/blenderbim/test/bim/feature/pset.feature
+++ b/src/blenderbim/test/bim/feature/pset.feature
@@ -387,10 +387,10 @@ Scenario: Edit pset length property
When I set "active_object.PsetProperties.properties['NosingLength'].metadata.length_value" to "0.45"
Then "active_object.PsetProperties.properties['NosingLength'].metadata.float_value" is roughly "450"
-
+
When I press "bim.edit_pset(obj='IfcStairFlight/StairFlight', obj_type='Object')"
Then nothing happens
-
+
Scenario: Edit qset length property
Given an empty IFC project
And I press "mesh.add_clever_stair"
@@ -401,15 +401,14 @@ Scenario: Edit qset length property
# Testing Q_LENGTH type of prop
Then "active_object.PsetProperties.properties['Length'].metadata.special_type" is "LENGTH"
- And "active_object.PsetProperties.properties['Length'].metadata.float_value" is roughly "2492.57495"
+ And "active_object.PsetProperties.properties['Length'].metadata.float_value" is roughly "2492.57397"
And "active_object.PsetProperties.properties['Length'].metadata.length_value" is roughly "2.49257"
When I set "active_object.PsetProperties.properties['Length'].metadata.float_value" to "350"
- Then "active_object.PsetProperties.properties['Length'].metadata.float_value" is roughly "350"
+ Then "active_object.PsetProperties.properties['Length'].metadata.length_value" is roughly "0.35"
When I set "active_object.PsetProperties.properties['Length'].metadata.length_value" to "0.45"
Then "active_object.PsetProperties.properties['Length'].metadata.float_value" is roughly "450"
-
+
When I press "bim.edit_pset(obj='IfcStairFlight/StairFlight', obj_type='Object')"
Then nothing happens
-
\ No newline at end of file
diff --git a/src/blenderbim/test/bim/feature/root.feature b/src/blenderbim/test/bim/feature/root.feature
index ea2a032a27..14a47a6666 100644
--- a/src/blenderbim/test/bim/feature/root.feature
+++ b/src/blenderbim/test/bim/feature/root.feature
@@ -24,7 +24,7 @@ Scenario: Unlink object
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
- And I press "bim.add_material"
+ And I press "bim.add_style()"
When I press "bim.unlink_object(obj='IfcWall/Cube')"
Then the object "Cube" is not an IFC element
And the material "Material" is an IFC style
diff --git a/src/blenderbim/test/bim/feature/search.feature b/src/blenderbim/test/bim/feature/search.feature
index 80ec5a2c10..b82d780569 100644
--- a/src/blenderbim/test/bim/feature/search.feature
+++ b/src/blenderbim/test/bim/feature/search.feature
@@ -8,9 +8,9 @@ Scenario: Select all walls
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
- And I add a new item to "scene.IfcSelectorProperties.groups"
- And I add a new item to "scene.IfcSelectorProperties.groups[0].queries"
- And I set "scene.IfcSelectorProperties.groups[0].queries[0].selector" to "IFC Class"
- And I set "scene.IfcSelectorProperties.groups[0].queries[0].active_option" to "124: IfcWall"
- When I press "bim.filter_model_elements(option='select')"
+ And I press "bim.add_filter_group(module='search')"
+ And I set "scene.BIMSearchProperties.facet" to "entity"
+ And I press "bim.add_filter(index=0, type='entity', module='search')"
+ And I set "scene.BIMSearchProperties.filter_groups[0].filters[0].value" to "IfcWall"
+ When I press "bim.search"
Then the object "IfcWall/Cube" is selected
diff --git a/src/blenderbim/test/bim/feature/style.feature b/src/blenderbim/test/bim/feature/style.feature
index 478b65624f..7fc685144a 100644
--- a/src/blenderbim/test/bim/feature/style.feature
+++ b/src/blenderbim/test/bim/feature/style.feature
@@ -18,7 +18,7 @@ Scenario: Remove style
When I press "bim.remove_style(style={style})"
Then nothing happens
-Scenario: Add style
+Scenario: Add style to current blender material
Given an empty IFC project
And I add a cube
And I add a material
@@ -33,32 +33,39 @@ Scenario: Unlink style
When I press "bim.unlink_style"
Then the material "Material" is not an IFC style
-Scenario: Enable editing style
+Scenario: Add a new style
Given an empty IFC project
- And I add a cube
- And I add a material
- And I press "bim.add_style"
- When I press "bim.enable_editing_style"
+ And I press "bim.load_styles(style_type='IfcSurfaceStyle')"
+ And I press "bim.enable_adding_presentation_style"
+ When I press "bim.add_presentation_style"
Then nothing happens
-Scenario: Disable editing style
+Scenario: Disable adding a new style
Given an empty IFC project
- And I add a cube
- And I add a material
- And I press "bim.add_style"
- And I press "bim.enable_editing_style"
- When I press "bim.disable_editing_style"
+ And I press "bim.load_styles(style_type='IfcSurfaceStyle')"
+ When I press "bim.disable_adding_presentation_style"
Then nothing happens
Scenario: Edit style
Given an empty IFC project
- And I add a cube
- And I add a material
- And I press "bim.add_style"
- And I press "bim.enable_editing_style"
+ And I press "bim.load_styles(style_type='IfcSurfaceStyle')"
+ And I press "bim.enable_adding_presentation_style"
+ And I press "bim.add_presentation_style"
+ And the variable "style" is "{ifc}.by_type('IfcSurfaceStyle')[0].id()"
+ And I press "bim.enable_editing_style(style={style})"
When I press "bim.edit_style"
Then nothing happens
+Scenario: Disable editing style
+ Given an empty IFC project
+ And I press "bim.load_styles(style_type='IfcSurfaceStyle')"
+ And I press "bim.enable_adding_presentation_style"
+ And I press "bim.add_presentation_style"
+ And the variable "style" is "{ifc}.by_type('IfcSurfaceStyle')[0].id()"
+ And I press "bim.enable_editing_style(style={style})"
+ When I press "bim.disable_editing_style"
+ Then nothing happens
+
Scenario: Load styles
Given an empty IFC project
And I add a cube
diff --git a/src/blenderbim/test/bim/test_feature.py b/src/blenderbim/test/bim/test_feature.py
index a8c6397057..07b178ddc0 100644
--- a/src/blenderbim/test/bim/test_feature.py
+++ b/src/blenderbim/test/bim/test_feature.py
@@ -29,11 +29,12 @@ from blenderbim.bim.module.model.data import AuthoringData
from pytest_bdd import scenarios, given, when, then, parsers
from mathutils import Vector
from math import radians
+from pathlib import Path
scenarios("feature")
variables = {
- "cwd": os.getcwd(),
+ "cwd": Path.cwd().as_posix(),
"ifc": "IfcStore.get_file()",
"pset_ifc": "IfcStore.pset_template_file",
"classification_ifc": "IfcStore.classification_file",
@@ -44,15 +45,13 @@ webbrowser.open = lambda x: True
def replace_variables(value):
- if "{cwd}" in value and os.name == "nt":
- value = value.replace("/", "\\").replace("{cwd}", os.getcwd()).replace("\\", "\\\\")
for key, new_value in variables.items():
value = value.replace("{" + key + "}", str(new_value))
return value
def is_x(number, x):
- return abs(number - x) < 1e-6
+ return abs(number - x) < 1e-5
def vectors_are_equal(v1, v2):
@@ -177,7 +176,7 @@ def the_material_name_colour_is_set_to_colour(name, colour):
@given("I add an array modifier")
-def i_add_a_cube():
+def i_add_an_array_modifier():
bpy.ops.object.modifier_add(type="ARRAY")
@@ -202,11 +201,13 @@ def i_press_operator_and_expect_error(operator, error_msg):
else:
exec(f"bpy.ops.{operator}()")
assert False, f"Operator bpy.ops.{operator} ran without exception '{error_msg}'"
- except Exception as e:
+ except Exception as e:
actual_error_msg = str(e).strip()
if str(e).strip() != error_msg:
traceback.print_exc()
- assert False, f"Got different exception running bpy.ops.{operator} - '{actual_error_msg}' instead of '{error_msg}'"
+ msg = f"Got different exception running bpy.ops.{operator} - '{actual_error_msg}' instead of '{error_msg}'"
+ assert False, msg
+
@given(parsers.parse('I press "{operator}"'))
@when(parsers.parse('I press "{operator}"'))
@@ -233,12 +234,14 @@ def i_evaluate_expression(expression):
def i_duplicate_the_selected_objects():
bpy.ops.bim.override_object_duplicate_move()
blenderbim.bim.handler.active_object_callback()
-
+
+
@when("I duplicate linked aggregate the selected objects")
-def i_duplicate_the_selected_objects():
+def i_duplicate_linked_aggregate_the_selected_objects():
bpy.ops.bim.object_duplicate_move_linked_aggregate()
blenderbim.bim.handler.active_object_callback()
+
@when("I refresh linked aggregate the selected object")
def i_refresh_the_selected_objects():
bpy.ops.bim.refresh_linked_aggregate()
@@ -291,12 +294,18 @@ def the_object_name_is_scaled_to_scale(name, scale):
@given(parsers.parse('the object "{name}" is placed in the collection "{collection}"'))
@when(parsers.parse('the object "{name}" is placed in the collection "{collection}"'))
-def the_object_name_is_placed_in_the_collection_collection(name, collection):
+def the_object_name_is_placed_in_the_collection_collection(name: str, collection: str) -> None:
obj = the_object_name_exists(name)
[c.objects.unlink(obj) for c in obj.users_collection]
bpy.data.collections.get(collection).objects.link(obj)
+@then(parsers.parse('the object "{name}" is placed in the collection "{collection}"'))
+def then_the_object_name_is_placed_in_the_collection_collection(name: str, collection: str) -> None:
+ obj = the_object_name_exists(name)
+ assert obj in bpy.data.collections.get(collection).objects[:]
+
+
@given(parsers.parse('additionally the object "{name}" is selected'))
@when(parsers.parse('additionally the object "{name}" is selected'))
def additionally_the_object_name_is_selected(name):
@@ -329,7 +338,7 @@ def i_set_prop_to_value(prop, value):
@given(parsers.parse('I set "{prop}" to ""'))
@when(parsers.parse('I set "{prop}" to ""'))
-def i_set_prop_to_value(prop):
+def i_set_prop_to_empty_string(prop):
try:
eval(f"bpy.context.{prop}")
except:
@@ -363,7 +372,7 @@ def nothing_happens():
@then(parsers.parse('the object "{name}" exists'))
-def the_object_name_exists(name) -> bpy.types.Object:
+def the_object_name_exists(name: str) -> bpy.types.Object:
obj = bpy.data.objects.get(name)
if not obj:
assert False, f'The object "{name}" does not exist'
@@ -377,6 +386,7 @@ def the_object_name_does_not_exist(name) -> bpy.types.Object:
assert True, f'The object "{name}" exists'
return obj
+
@given(parsers.parse('the collection "{name}" exists'))
@when(parsers.parse('the collection "{name}" exists'))
@then(parsers.parse('the collection "{name}" exists'))
@@ -387,6 +397,33 @@ def the_collection_name_exists(name) -> bpy.types.Collection:
return obj
+@then(parsers.parse('the collection "{name}" is selectable'))
+def the_collection_name_is_selectable(name: str) -> None:
+ col = the_collection_name_exists(name)
+ assert col.hide_select == False
+
+
+@then(parsers.parse('the collection "{name}" is unselectable'))
+def the_collection_name_is_unselectable(name: str) -> None:
+ col = the_collection_name_exists(name)
+ assert col.hide_select == True
+
+
+@then(parsers.parse('the collection "{name}" exists in viewlayer'))
+def the_collection_exists_in_viewlayer(name: str) -> bpy.types.LayerCollection:
+ col = the_collection_name_exists(name)
+ layer = tool.Blender.get_layer_collection(col)
+ if not layer:
+ assert False, f'The collection "{name}" is not present in the current viewlayer'
+ return layer
+
+
+@then(parsers.parse('the collection "{name}" exclude status is "{exclude}"'))
+def the_collection_exclude_status_is(name: str, exclude: str) -> None:
+ layer = the_collection_exists_in_viewlayer(name)
+ assert layer.exclude == (exclude == "True")
+
+
@then(parsers.parse('the object "{name1}" and "{name2}" are different elements'))
def the_object_name1_and_name2_are_different_elements(name1, name2):
ifc = an_ifc_file_exists()
@@ -413,7 +450,7 @@ def the_object_name_has_a_representation_type_of_context(name, type, context):
@given(parsers.parse('the object "{name}" data is a "{type}" representation of "{context}"'))
@then(parsers.parse('the object "{name}" data is a "{type}" representation of "{context}"'))
-def the_object_name_has_a_representation_type_of_context(name, type, context):
+def the_object_name_data_is_a_type_representation_of_context(name, type, context):
ifc = an_ifc_file_exists()
context, subcontext, target_view = context.split("/")
rep = ifc.by_id(the_object_name_exists(name).data.BIMMeshProperties.ifc_definition_id)
@@ -518,7 +555,7 @@ def the_object_name_has_no_data(name):
@then(parsers.parse('the object "{name}" has data which is an IFC representation'))
-def the_object_name_is_not_an_ifc_element(name):
+def the_object_name_has_ifc_representation_data(name):
id = the_object_name_exists(name).data.BIMMeshProperties.ifc_definition_id
assert id != 0, f"The ID is {id}"
@@ -552,7 +589,7 @@ def the_material_name_is_not_an_ifc_style(name):
@then(parsers.parse('the material "{name}" colour is "{colour}"'))
-def the_material_name_colour_is_set_to_colour(name, colour):
+def then_the_material_name_colour_is_set_to_colour(name, colour):
diffuse_color = list(the_material_name_exists(name).diffuse_color)
assert diffuse_color == [float(c) for c in colour.split(",")], f"The colour is {diffuse_color}"
@@ -616,7 +653,7 @@ def prop_is_value(prop, value):
@then(parsers.parse('"{prop}" is roughly "{value}"'))
-def prop_is_value(prop, value):
+def prop_is_roughly_value(prop, value):
prop = replace_variables(prop)
value = replace_variables(value)
is_value = False
@@ -661,6 +698,7 @@ def the_object_name_has_a_thickness_thick_layered_material_containing_the_materi
assert is_x(total_thickness, float(thickness))
assert material_name in material_names
+
@then(parsers.parse('the object "{name}" has no IFC materials'))
def the_object_has_no_ifc_materials(name):
element = tool.Ifc.get_entity(the_object_name_exists(name))
@@ -705,12 +743,6 @@ def the_collection_name1_is_in_the_collection_name2(name1, name2):
assert bpy.data.collections.get(name2).children.get(name1)
-@then(parsers.parse('the object "{name}" does not exist'))
-def the_object_name_does_not_exist(name):
- obj = bpy.data.objects.get(name)
- assert obj is None or len(obj.users_collection) == 0, f"Object {name} exists"
-
-
@then(parsers.parse('the collection "{name}" does not exist'))
def the_collection_name_does_not_exist(name):
obj = bpy.data.collections.get(name)
@@ -753,7 +785,7 @@ def the_object_name_dimensions_are_dimensions(name, dimensions):
@then(parsers.parse('the object "{name}" top right corner is at "{location}"'))
-def the_object_name_is_at_location(name, location):
+def the_object_name_top_right_corner_is_at_location(name, location):
obj = the_object_name_exists(name)
obj_corner = obj.matrix_world @ Vector(obj.bound_box[6])
assert (
@@ -762,7 +794,7 @@ def the_object_name_is_at_location(name, location):
@then(parsers.parse('the object "{name}" bottom left corner is at "{location}"'))
-def the_object_name_is_at_location(name, location):
+def the_object_name_bottom_left_corner_is_at_location(name, location):
obj = the_object_name_exists(name)
obj_corner = obj.matrix_world @ Vector(obj.bound_box[0])
assert (
@@ -791,11 +823,13 @@ def the_file_name_should_contain_value(name, value):
def the_object_name_has_no_modifiers(name):
assert len(the_object_name_exists(name).modifiers) == 0
+
@given(parsers.parse('I load the IFC test file "{filepath}"'))
def i_load_the_ifc_test_file(filepath):
filepath = f"{variables['cwd']}{filepath}"
bpy.ops.bim.load_project(filepath=filepath, use_relative_path=True)
+
@given("I load the demo construction library")
@when("I load the demo construction library")
def i_add_a_construction_library():
@@ -836,7 +870,7 @@ def move_cursor_bottom_left():
@given(parsers.parse("I prepare to undo"))
@when(parsers.parse("I prepare to undo"))
@then(parsers.parse("I prepare to undo"))
-def hit_undo():
+def prepare_undo():
bpy.ops.ed.undo_push(message="UNDO STEP")
@@ -847,6 +881,7 @@ def hit_undo():
bpy.ops.ed.undo_push(message="UNDO STEP")
bpy.ops.ed.undo()
+
@then(parsers.parse('the object "{obj_name1}" has a connection with "{obj_name2}"'))
def the_obj1_has_a_connection_with_obj2(obj_name1, obj_name2):
element1 = replace_variables(obj_name1)
@@ -866,9 +901,11 @@ def the_obj1_has_a_connection_with_obj2(obj_name1, obj_name2):
relationships[conn.RelatedElement] = conn.RelatingElement
for key, value in relationships.items():
- assert (key.id() == element1.id() and value.id() == element2.id()) or (key.id() == element2.id() and value.id() == element1.id()), f"The object {obj_name1} is connected to {obj_name2}"
+ assert (key.id() == element1.id() and value.id() == element2.id()) or (
+ key.id() == element2.id() and value.id() == element1.id()
+ ), f"The object {obj_name1} is connected to {obj_name2}"
+
-
@then(parsers.parse('the object "{obj_name1}" and "{obj_name2}" belong to the same Linked Aggregate Group'))
def the_obj1_and_obj2_belong_the_same_linked_aggregate_group(obj_name1, obj_name2):
element1 = replace_variables(obj_name1)
@@ -892,6 +929,7 @@ def the_obj1_and_obj2_belong_the_same_linked_aggregate_group(obj_name1, obj_name
assert groups[0] == groups[1], "Objects do not belong to the same Linked Aggregate group"
+
@when(parsers.parse('the object layer length is set to "{value}"'))
def the_obj_layer_lenght_is_set_to(value):
value = float(value)
@@ -900,11 +938,11 @@ def the_obj_layer_lenght_is_set_to(value):
except:
assert False, f"Property BIMModelProperties.length does not exist when trying to set to value {value}"
- print(50*"@", bpy.context.selected_objects)
-
+ print(50 * "@", bpy.context.selected_objects)
+
bpy.context.scene.BIMModelProperties.length = value
bpy.ops.bim.change_layer_length(length=value)
-
+
# These definitions are not to be used in tests but simply in debugging failing tests
@@ -953,5 +991,3 @@ def run_pdb():
import pdb
pdb.set_trace()
-
-
diff --git a/src/blenderbim/test/core/test_aggregate.py b/src/blenderbim/test/core/test_aggregate.py
index ca8cadcbdc..e4f3efe58d 100644
--- a/src/blenderbim/test/core/test_aggregate.py
+++ b/src/blenderbim/test/core/test_aggregate.py
@@ -38,7 +38,7 @@ class TestAssignObject:
ifc.get_entity("relating_obj").should_be_called().will_return("relating_object")
ifc.get_entity("related_obj").should_be_called().will_return("related_object")
ifc.run(
- "aggregate.assign_object", product="related_object", relating_object="relating_object"
+ "aggregate.assign_object", products=["related_object"], relating_object="relating_object"
).should_be_called().will_return("rel")
aggregate.disable_editing("related_obj").should_be_called()
collector.assign("relating_obj").should_be_called()
@@ -53,8 +53,8 @@ class TestUnassignObject:
def test_run(self, ifc, aggregate, collector):
ifc.get_entity("related_obj").should_be_called().will_return("element")
aggregate.get_container("element").should_be_called().will_return("container")
- ifc.run("spatial.assign_container", product="element", relating_structure="container").should_be_called()
- ifc.run("aggregate.unassign_object", product="element").should_be_called().will_return("rel")
+ ifc.run("spatial.assign_container", products=["element"], relating_structure="container").should_be_called()
+ ifc.run("aggregate.unassign_object", products=["element"]).should_be_called()
collector.assign("relating_obj").should_be_called()
collector.assign("related_obj").should_be_called()
subject.unassign_object(ifc, aggregate, collector, relating_obj="relating_obj", related_obj="related_obj")
diff --git a/src/blenderbim/test/core/test_brick.py b/src/blenderbim/test/core/test_brick.py
index 157a363682..e410fbe9b9 100644
--- a/src/blenderbim/test/core/test_brick.py
+++ b/src/blenderbim/test/core/test_brick.py
@@ -124,21 +124,21 @@ class TestAssignBrickReference:
ifc.run("library.add_reference", library="library").should_be_called().will_return("reference")
brick.export_brick_attributes("brick_uri").should_be_called().will_return("attributes")
ifc.run("library.edit_reference", reference="reference", attributes="attributes").should_be_called()
- ifc.run("library.assign_reference", product="element", reference="reference").should_be_called()
+ ifc.run("library.assign_reference", products=["element"], reference="reference").should_be_called()
brick.get_brickifc_project().should_be_called().will_return("project")
brick.add_brickifc_reference("brick_uri", "element", "project").should_be_called()
subject.assign_brick_reference(ifc, brick, element="element", library="library", brick_uri="brick_uri")
def test_assigning_to_an_existing_reference(self, ifc, brick):
brick.get_library_brick_reference("library", "brick_uri").should_be_called().will_return("reference")
- ifc.run("library.assign_reference", product="element", reference="reference").should_be_called()
+ ifc.run("library.assign_reference", products=["element"], reference="reference").should_be_called()
brick.get_brickifc_project().should_be_called().will_return("project")
brick.add_brickifc_reference("brick_uri", "element", "project").should_be_called()
subject.assign_brick_reference(ifc, brick, element="element", library="library", brick_uri="brick_uri")
def test_adding_a_brickifc_project_if_it_doesnt_exist(self, ifc, brick):
brick.get_library_brick_reference("library", "brick_uri").should_be_called().will_return("reference")
- ifc.run("library.assign_reference", product="element", reference="reference").should_be_called()
+ ifc.run("library.assign_reference", products=["element"], reference="reference").should_be_called()
brick.get_brickifc_project().should_be_called().will_return(None)
brick.get_namespace("brick_uri").should_be_called().will_return("namespace")
brick.add_brickifc_project("namespace").should_be_called().will_return("project")
@@ -277,4 +277,4 @@ class TestSetBrickListRoot:
class TestRemoveBrickRelation:
def test_run(self, brick):
brick.remove_relation("brick_uri", "predicate", "object").should_be_called()
- subject.remove_brick_relation(brick, brick_uri="brick_uri", predicate="predicate", object="object")
\ No newline at end of file
+ subject.remove_brick_relation(brick, brick_uri="brick_uri", predicate="predicate", object="object")
diff --git a/src/blenderbim/test/core/test_document.py b/src/blenderbim/test/core/test_document.py
index 708c899e48..9811c887a8 100644
--- a/src/blenderbim/test/core/test_document.py
+++ b/src/blenderbim/test/core/test_document.py
@@ -133,11 +133,11 @@ class TestRemoveDocument:
class TestAssignDocument:
def test_run(self, ifc):
- ifc.run("document.assign_document", product="product", document="document").should_be_called()
+ ifc.run("document.assign_document", products=["product"], document="document").should_be_called()
subject.assign_document(ifc, product="product", document="document")
class TestUnassignDocument:
def test_run(self, ifc):
- ifc.run("document.unassign_document", product="product", document="document").should_be_called()
+ ifc.run("document.unassign_document", products=["product"], document="document").should_be_called()
subject.unassign_document(ifc, product="product", document="document")
diff --git a/src/blenderbim/test/core/test_drawing.py b/src/blenderbim/test/core/test_drawing.py
index 55c6dfc3f8..a2b6608f5d 100644
--- a/src/blenderbim/test/core/test_drawing.py
+++ b/src/blenderbim/test/core/test_drawing.py
@@ -365,7 +365,7 @@ class TestAddDrawing:
attributes={"Identification": "X", "Name": "name", "Scope": "DRAWING"},
).should_be_called()
ifc.run("document.edit_reference", reference="reference", attributes={"Location": "uri"}).should_be_called()
- ifc.run("document.assign_document", product="element", document="reference").should_be_called()
+ ifc.run("document.assign_document", products=["element"], document="reference").should_be_called()
drawing.import_drawings().should_be_called()
subject.add_drawing(ifc, collector, drawing, target_view="target_view", location_hint="location_hint")
@@ -378,7 +378,7 @@ class TestDuplicateDrawing:
drawing.copy_representation("drawing", "new_drawing").should_be_called()
drawing.set_name("new_drawing", "unique_name").should_be_called()
drawing.get_drawing_group("new_drawing").should_be_called().will_return("group")
- ifc.run("group.unassign_group", group="group", product="new_drawing").should_be_called()
+ ifc.run("group.unassign_group", group="group", products=["new_drawing"]).should_be_called()
ifc.run("group.add_group").should_be_called().will_return("new_group")
ifc.run(
"group.edit_group", group="new_group", attributes={"Name": "unique_name", "ObjectType": "DRAWING"}
@@ -387,11 +387,11 @@ class TestDuplicateDrawing:
drawing.get_group_elements("group").should_be_called().will_return(["drawing", "annotation"])
ifc.run("root.copy_class", product="annotation").should_be_called().will_return("new_annotation")
drawing.copy_representation("annotation", "new_annotation").should_be_called()
- ifc.run("group.unassign_group", group="group", product="new_annotation").should_be_called()
+ ifc.run("group.unassign_group", group="group", products=["new_annotation"]).should_be_called()
ifc.run("group.assign_group", group="new_group", products=["new_annotation"]).should_be_called()
drawing.get_drawing_document("new_drawing").should_be_called().will_return("old_reference")
- ifc.run("document.unassign_document", product="new_drawing", document="old_reference").should_be_called()
+ ifc.run("document.unassign_document", products=["new_drawing"], document="old_reference").should_be_called()
ifc.run("document.add_information").should_be_called().will_return("information")
ifc.run("document.add_reference", information="information").should_be_called().will_return("reference")
@@ -405,7 +405,7 @@ class TestDuplicateDrawing:
ifc.run(
"document.edit_reference", reference="reference", attributes={"Location": "drawing_path"}
).should_be_called()
- ifc.run("document.assign_document", product="new_drawing", document="reference").should_be_called()
+ ifc.run("document.assign_document", products=["new_drawing"], document="reference").should_be_called()
drawing.import_drawings().should_be_called()
subject.duplicate_drawing(ifc, drawing, drawing="drawing", should_duplicate_annotations=True)
diff --git a/src/blenderbim/test/core/test_library.py b/src/blenderbim/test/core/test_library.py
index 2809722e81..4722cde5c0 100644
--- a/src/blenderbim/test/core/test_library.py
+++ b/src/blenderbim/test/core/test_library.py
@@ -114,12 +114,12 @@ class TestEditLibraryReference:
class TestAssignLibraryReference:
def test_run(self, ifc):
ifc.get_entity("obj").should_be_called().will_return("product")
- ifc.run("library.assign_reference", product="product", reference="reference").should_be_called()
+ ifc.run("library.assign_reference", products=["product"], reference="reference").should_be_called()
subject.assign_library_reference(ifc, obj="obj", reference="reference")
class TestUnassignLibraryReference:
def test_run(self, ifc):
ifc.get_entity("obj").should_be_called().will_return("product")
- ifc.run("library.unassign_reference", product="product", reference="reference").should_be_called()
+ ifc.run("library.unassign_reference", products=["product"], reference="reference").should_be_called()
subject.unassign_library_reference(ifc, obj="obj", reference="reference")
diff --git a/src/blenderbim/test/core/test_nest.py b/src/blenderbim/test/core/test_nest.py
index 56f73cacbb..2eb0ffbc33 100644
--- a/src/blenderbim/test/core/test_nest.py
+++ b/src/blenderbim/test/core/test_nest.py
@@ -38,7 +38,7 @@ class TestAssignObject:
ifc.get_entity("relating_obj").should_be_called().will_return("relating_object")
ifc.get_entity("related_obj").should_be_called().will_return("related_object")
ifc.run(
- "nest.assign_object", related_object="related_object", relating_object="relating_object"
+ "nest.assign_object", related_objects=["related_object"], relating_object="relating_object"
).should_be_called().will_return("rel")
nest.disable_editing("related_obj").should_be_called()
collector.assign("relating_obj").should_be_called()
@@ -52,8 +52,8 @@ class TestUnassignObject:
def test_run(self, ifc, nest, collector):
ifc.get_entity("related_obj").should_be_called().will_return("element")
nest.get_container("element").should_be_called().will_return("container")
- ifc.run("spatial.assign_container", product="element", relating_structure="container").should_be_called()
- ifc.run("nest.unassign_object", related_object="element").should_be_called().will_return("rel")
+ ifc.run("spatial.assign_container", products=["element"], relating_structure="container").should_be_called()
+ ifc.run("nest.unassign_object", related_objects=["element"]).should_be_called().will_return("rel")
collector.assign("relating_obj").should_be_called()
collector.assign("related_obj").should_be_called()
subject.unassign_object(ifc, nest, collector, relating_obj="relating_obj", related_obj="related_obj")
diff --git a/src/blenderbim/test/core/test_spatial.py b/src/blenderbim/test/core/test_spatial.py
index 88210a9682..9e01aefde4 100644
--- a/src/blenderbim/test/core/test_spatial.py
+++ b/src/blenderbim/test/core/test_spatial.py
@@ -23,14 +23,14 @@ from test.core.bootstrap import ifc, collector, spatial
class TestReferenceStructure:
def test_run(self, ifc, spatial):
spatial.can_reference("structure", "element").should_be_called().will_return(True)
- ifc.run("spatial.reference_structure", product="element", relating_structure="structure").should_be_called()
+ ifc.run("spatial.reference_structure", products=["element"], relating_structure="structure").should_be_called()
subject.reference_structure(ifc, spatial, structure="structure", element="element")
class TestDereferenceStructure:
def test_run(self, ifc, spatial):
spatial.can_reference("structure", "element").should_be_called().will_return(True)
- ifc.run("spatial.dereference_structure", product="element", relating_structure="structure").should_be_called()
+ ifc.run("spatial.dereference_structure", products=["element"], relating_structure="structure").should_be_called()
subject.dereference_structure(ifc, spatial, structure="structure", element="element")
@@ -40,7 +40,7 @@ class TestAssignContainer:
ifc.get_entity("structure_obj").should_be_called().will_return("structure")
ifc.get_entity("element_obj").should_be_called().will_return("element")
ifc.run(
- "spatial.assign_container", product="element", relating_structure="structure"
+ "spatial.assign_container", products=["element"], relating_structure="structure"
).should_be_called().will_return("rel")
spatial.disable_editing("element_obj").should_be_called()
collector.assign("element_obj").should_be_called()
@@ -72,7 +72,7 @@ class TestChangeSpatialLevel:
class TestRemoveContainer:
def test_run(self, ifc, collector):
ifc.get_entity("obj").should_be_called().will_return("element")
- ifc.run("spatial.remove_container", product="element").should_be_called()
+ ifc.run("spatial.unassign_container", products=["element"]).should_be_called()
collector.assign("obj").should_be_called()
subject.remove_container(ifc, collector, obj="obj")
diff --git a/src/blenderbim/test/core/test_system.py b/src/blenderbim/test/core/test_system.py
index f77cae4cd1..51c4dc9bae 100644
--- a/src/blenderbim/test/core/test_system.py
+++ b/src/blenderbim/test/core/test_system.py
@@ -74,13 +74,13 @@ class TestDisableEditingSystem:
class TestAssignSystem:
def test_run(self, ifc):
- ifc.run("system.assign_system", product="product", system="system").should_be_called()
+ ifc.run("system.assign_system", products=["product"], system="system").should_be_called()
subject.assign_system(ifc, system="system", product="product")
class TestUnassignSystem:
def test_run(self, ifc):
- ifc.run("system.unassign_system", product="product", system="system").should_be_called()
+ ifc.run("system.unassign_system", products=["product"], system="system").should_be_called()
subject.unassign_system(ifc, system="system", product="product")
diff --git a/src/blenderbim/test/core/test_type.py b/src/blenderbim/test/core/test_type.py
index 0a084f8438..5800f04ea4 100644
--- a/src/blenderbim/test/core/test_type.py
+++ b/src/blenderbim/test/core/test_type.py
@@ -22,7 +22,7 @@ from test.core.bootstrap import ifc, type
class TestAssignType:
def test_assigning_and_switching_to_an_existing_type_data(self, ifc, type):
- ifc.run("type.assign_type", related_object="element", relating_type="type").should_be_called()
+ ifc.run("type.assign_type", related_objects=["element"], relating_type="type").should_be_called()
type.has_material_usage("element").should_be_called().will_return(False)
ifc.get_object("type").should_be_called().will_return("type_obj")
type.get_object_data("type_obj").should_be_called().will_return("type_obj_data")
@@ -32,7 +32,7 @@ class TestAssignType:
subject.assign_type(ifc, type, element="element", type="type")
def test_assigning_and_not_changing_data_if_the_type_has_no_data(self, ifc, type):
- ifc.run("type.assign_type", related_object="element", relating_type="type").should_be_called()
+ ifc.run("type.assign_type", related_objects=["element"], relating_type="type").should_be_called()
type.has_material_usage("element").should_be_called().will_return(False)
ifc.get_object("type").should_be_called().will_return("type_obj")
type.get_object_data("type_obj").should_be_called().will_return(None)
diff --git a/src/blenderbim/test/tool/test_aggregate.py b/src/blenderbim/test/tool/test_aggregate.py
index 21a1b7002c..6540d2fcb7 100644
--- a/src/blenderbim/test/tool/test_aggregate.py
+++ b/src/blenderbim/test/tool/test_aggregate.py
@@ -125,5 +125,5 @@ class TestGetContainer(NewFile):
tool.Ifc.set(ifc)
element = ifc.createIfcWall()
container = ifc.createIfcBuildingStorey()
- ifcopenshell.api.run("spatial.assign_container", ifc, product=element, relating_structure=container)
+ ifcopenshell.api.run("spatial.assign_container", ifc, products=[element], relating_structure=container)
assert subject.get_container(element) == container
diff --git a/src/blenderbim/test/tool/test_blender.py b/src/blenderbim/test/tool/test_blender.py
index e05e98f98a..328363bafb 100644
--- a/src/blenderbim/test/tool/test_blender.py
+++ b/src/blenderbim/test/tool/test_blender.py
@@ -20,6 +20,7 @@ import bpy
import ifcopenshell
import blenderbim.core.tool
import blenderbim.tool as tool
+import pytest
from test.bim.bootstrap import NewFile
from blenderbim.tool.blender import Blender as subject
@@ -44,3 +45,17 @@ class TestCopyNodeGraph(NewFile):
subject.copy_node_graph(material_to, material_from)
assert len(material_to_nodes) == 2
+
+
+class TestSortPanelsForRegister(NewFile):
+ def test_run(self):
+ items = ["A", "B", "C", "D"]
+ items_to_parents = {"A": "D", "D": "C", "C": "B"}
+ sorted_items = subject.sort_panels_for_register(items, items_to_parents)
+ assert tuple(sorted_items) == ("B", "C", "D", "A")
+
+ with pytest.raises(AssertionError):
+ subject.sort_panels_for_register(items, {"A": "K"})
+
+ with pytest.raises(AssertionError):
+ subject.sort_panels_for_register(items, {"J": "A"})
diff --git a/src/blenderbim/test/tool/test_brick.py b/src/blenderbim/test/tool/test_brick.py
index b70b9b2240..b5d5a1f973 100644
--- a/src/blenderbim/test/tool/test_brick.py
+++ b/src/blenderbim/test/tool/test_brick.py
@@ -320,8 +320,8 @@ class TestGetParentSpace(NewFile):
element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcBuildingStorey")
subelement = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcSpace")
project = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcProject")
- ifcopenshell.api.run("aggregate.assign_object", ifc, product=subelement, relating_object=element)
- ifcopenshell.api.run("aggregate.assign_object", ifc, product=element, relating_object=project)
+ ifcopenshell.api.run("aggregate.assign_object", ifc, products=[subelement], relating_object=element)
+ ifcopenshell.api.run("aggregate.assign_object", ifc, products=[element], relating_object=project)
assert subject.get_parent_space(subelement) == element
assert subject.get_parent_space(element) is None
diff --git a/src/blenderbim/test/tool/test_collector.py b/src/blenderbim/test/tool/test_collector.py
index 94e9bee0cc..f9bf18d0f1 100644
--- a/src/blenderbim/test/tool/test_collector.py
+++ b/src/blenderbim/test/tool/test_collector.py
@@ -18,6 +18,8 @@
import bpy
import ifcopenshell
+import ifcopenshell.api
+import ifcopenshell.util.element
import blenderbim.core.tool
import blenderbim.tool as tool
from blenderbim.tool.collector import Collector as subject
@@ -39,7 +41,7 @@ class TestAssign(NewFile):
ifcopenshell.api.run(
"spatial.assign_container",
tool.Ifc.get(),
- product=wall_element,
+ products=[wall_element],
relating_structure=tool.Ifc.get().by_type("IfcSite")[0],
)
subject.assign(wall_obj)
@@ -66,7 +68,7 @@ class TestAssign(NewFile):
"aggregate.assign_object",
tool.Ifc.get(),
relating_object=tool.Ifc.get().by_type("IfcSite")[0],
- product=space_element,
+ products=[space_element],
)
subject.assign(space_obj)
assert len(space_obj.users_collection) == 1
@@ -82,7 +84,7 @@ class TestAssign(NewFile):
"aggregate.assign_object",
tool.Ifc.get(),
relating_object=tool.Ifc.get().by_type("IfcSite")[0],
- product=space_element,
+ products=[space_element],
)
subject.assign(space_obj)
subject.assign(space_obj)
@@ -101,7 +103,7 @@ class TestAssign(NewFile):
"aggregate.assign_object",
tool.Ifc.get(),
relating_object=tool.Ifc.get().by_type("IfcSite")[0],
- product=space_element,
+ products=[space_element],
)
subject.assign(space_obj)
assert len(space_obj.users_collection) == 1
@@ -119,7 +121,7 @@ class TestAssign(NewFile):
"aggregate.assign_object",
tool.Ifc.get(),
relating_object=element,
- product=subelement,
+ products=[subelement],
)
subject.assign(element_obj)
assert len(element_obj.users_collection) == 1
@@ -159,7 +161,7 @@ class TestAssign(NewFile):
"aggregate.assign_object",
tool.Ifc.get(),
relating_object=tool.Ifc.get().by_type("IfcSite")[0],
- product=space_element,
+ products=[space_element],
)
subject.assign(space_obj)
assert bpy.context.scene.collection.children.find(space_collection.name) == -1
@@ -178,7 +180,7 @@ class TestAssign(NewFile):
"aggregate.assign_object",
tool.Ifc.get(),
relating_object=element,
- product=subelement,
+ products=[subelement],
)
subject.assign(element_obj)
subject.assign(subelement_obj)
@@ -194,14 +196,14 @@ class TestAssign(NewFile):
ifcopenshell.api.run(
"spatial.assign_container",
tool.Ifc.get(),
- product=element,
+ products=[element],
relating_structure=tool.Ifc.get().by_type("IfcSite")[0],
)
ifcopenshell.api.run(
"aggregate.assign_object",
tool.Ifc.get(),
relating_object=element,
- product=subelement,
+ products=[subelement],
)
subject.assign(subelement_obj)
assert subelement_obj.users_collection[0].name == "IfcSite/My Site"
@@ -234,7 +236,7 @@ class TestAssign(NewFile):
ifcopenshell.api.run(
"spatial.assign_container",
tool.Ifc.get(),
- product=element,
+ products=[element],
relating_structure=tool.Ifc.get().by_type("IfcSite")[0],
)
bpy.context.scene.collection.objects.link(element_obj)
@@ -253,7 +255,7 @@ class TestAssign(NewFile):
ifcopenshell.api.run(
"spatial.assign_container",
tool.Ifc.get(),
- product=element,
+ products=[element],
relating_structure=tool.Ifc.get().by_type("IfcSite")[0],
)
bpy.context.scene.collection.objects.link(element_obj)
diff --git a/src/blenderbim/test/tool/test_document.py b/src/blenderbim/test/tool/test_document.py
index 3ef56744e0..8b22ce19d0 100644
--- a/src/blenderbim/test/tool/test_document.py
+++ b/src/blenderbim/test/tool/test_document.py
@@ -18,6 +18,7 @@
import bpy
import ifcopenshell
+import ifcopenshell.api
import blenderbim.core.tool
import blenderbim.tool as tool
from test.bim.bootstrap import NewFile
@@ -188,7 +189,7 @@ class TestImportReferences(NewFile):
assert len(props.documents) == 1
assert props.documents[0].ifc_definition_id == reference.id()
assert props.documents[0].name == "Unnamed"
- assert props.documents[0].identification == "*"
+ assert props.documents[0].identification == "X"
assert props.documents[0].is_information is False
diff --git a/src/blenderbim/test/tool/test_geometry.py b/src/blenderbim/test/tool/test_geometry.py
index 48f443ad19..0837fe50a3 100644
--- a/src/blenderbim/test/tool/test_geometry.py
+++ b/src/blenderbim/test/tool/test_geometry.py
@@ -199,7 +199,7 @@ class TestGetElementType(NewFile):
ifc = tool.Ifc.get()
element = ifc.createIfcWall()
type = ifc.createIfcWallType()
- ifcopenshell.api.run("type.assign_type", ifc, related_object=element, relating_type=type)
+ ifcopenshell.api.run("type.assign_type", ifc, related_objects=[element], relating_type=type)
assert subject.get_element_type(element) == type
@@ -209,7 +209,7 @@ class TestGetElementsOfType(NewFile):
ifc = tool.Ifc.get()
element = ifc.createIfcWall()
type = ifc.createIfcWallType()
- ifcopenshell.api.run("type.assign_type", ifc, related_object=element, relating_type=type)
+ ifcopenshell.api.run("type.assign_type", ifc, related_objects=[element], relating_type=type)
assert subject.get_elements_of_type(type) == (element,)
diff --git a/src/blenderbim/test/tool/test_material.py b/src/blenderbim/test/tool/test_material.py
index 467eb34afe..6fd0a8ac68 100644
--- a/src/blenderbim/test/tool/test_material.py
+++ b/src/blenderbim/test/tool/test_material.py
@@ -79,7 +79,7 @@ class TestGetElementsByMaterial(NewFile):
tool.Ifc.set(ifc)
element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall")
material = ifcopenshell.api.run("material.add_material", ifc)
- ifcopenshell.api.run("material.assign_material", ifc, product=element, material=material)
+ ifcopenshell.api.run("material.assign_material", ifc, products=[element], material=material)
assert subject.get_elements_by_material(material) == {element}
diff --git a/src/blenderbim/test/tool/test_misc.py b/src/blenderbim/test/tool/test_misc.py
index 02ed0ca22d..bf7e0f5ce7 100644
--- a/src/blenderbim/test/tool/test_misc.py
+++ b/src/blenderbim/test/tool/test_misc.py
@@ -40,7 +40,7 @@ class TestGetObjectStorey(test.bim.bootstrap.NewFile):
wall = ifc.createIfcWall()
tool.Ifc.link(wall, obj)
storey = ifc.createIfcBuildingStorey()
- ifcopenshell.api.run("spatial.assign_container", ifc, product=wall, relating_structure=storey)
+ ifcopenshell.api.run("spatial.assign_container", ifc, products=[wall], relating_structure=storey)
assert subject.get_object_storey(obj) == storey
def test_only_returning_a_building_storey(self):
@@ -50,7 +50,7 @@ class TestGetObjectStorey(test.bim.bootstrap.NewFile):
wall = ifc.createIfcWall()
tool.Ifc.link(wall, obj)
building = ifc.createIfcBuilding()
- ifcopenshell.api.run("spatial.assign_container", ifc, product=wall, relating_structure=building)
+ ifcopenshell.api.run("spatial.assign_container", ifc, products=[wall], relating_structure=building)
assert subject.get_object_storey(obj) is None
def test_returning_nothing_if_uncontained(self):
@@ -86,8 +86,8 @@ class TestGetStoreyHeight(test.bim.bootstrap.NewFile):
storey.Elevation = 3000
storey2 = ifc.createIfcBuildingStorey()
storey2.Elevation = 5000
- ifcopenshell.api.run("aggregate.assign_object", ifc, product=storey, relating_object=building)
- ifcopenshell.api.run("aggregate.assign_object", ifc, product=storey2, relating_object=building)
+ ifcopenshell.api.run("aggregate.assign_object", ifc, products=[storey], relating_object=building)
+ ifcopenshell.api.run("aggregate.assign_object", ifc, products=[storey2], relating_object=building)
assert subject.get_storey_height_in_si(storey, 1) == 2.0
def test_getting_a_double_storey_height(self):
@@ -103,9 +103,9 @@ class TestGetStoreyHeight(test.bim.bootstrap.NewFile):
storey2.Elevation = 5000
storey3 = ifc.createIfcBuildingStorey()
storey3.Elevation = 9000
- ifcopenshell.api.run("aggregate.assign_object", ifc, product=storey, relating_object=building)
- ifcopenshell.api.run("aggregate.assign_object", ifc, product=storey2, relating_object=building)
- ifcopenshell.api.run("aggregate.assign_object", ifc, product=storey3, relating_object=building)
+ ifcopenshell.api.run("aggregate.assign_object", ifc, products=[storey], relating_object=building)
+ ifcopenshell.api.run("aggregate.assign_object", ifc, products=[storey2], relating_object=building)
+ ifcopenshell.api.run("aggregate.assign_object", ifc, products=[storey3], relating_object=building)
assert subject.get_storey_height_in_si(storey, 2) == 6.0
def test_only_considering_storeys_in_the_same_building(self):
@@ -116,7 +116,7 @@ class TestGetStoreyHeight(test.bim.bootstrap.NewFile):
storey.Elevation = 3000
storey2 = ifc.createIfcBuildingStorey()
storey2.Elevation = 5000
- ifcopenshell.api.run("aggregate.assign_object", ifc, product=storey, relating_object=building)
+ ifcopenshell.api.run("aggregate.assign_object", ifc, products=[storey], relating_object=building)
assert subject.get_storey_height_in_si(storey, 1) is None
def test_returning_none_if_the_storey_height_is_undefined(self):
@@ -124,7 +124,7 @@ class TestGetStoreyHeight(test.bim.bootstrap.NewFile):
tool.Ifc.set(ifc)
building = ifc.createIfcBuilding()
storey = ifc.createIfcBuildingStorey()
- ifcopenshell.api.run("aggregate.assign_object", ifc, product=storey, relating_object=building)
+ ifcopenshell.api.run("aggregate.assign_object", ifc, products=[storey], relating_object=building)
assert subject.get_storey_height_in_si(storey, 1) is None
diff --git a/src/blenderbim/test/tool/test_nest.py b/src/blenderbim/test/tool/test_nest.py
index fcf244b59a..f45a9c7556 100644
--- a/src/blenderbim/test/tool/test_nest.py
+++ b/src/blenderbim/test/tool/test_nest.py
@@ -70,5 +70,5 @@ class TestGetContainer(NewFile):
tool.Ifc.set(ifc)
element = ifc.createIfcWall()
container = ifc.createIfcBuildingStorey()
- ifcopenshell.api.run("spatial.assign_container", ifc, product=element, relating_structure=container)
+ ifcopenshell.api.run("spatial.assign_container", ifc, products=[element], relating_structure=container)
assert subject.get_container(element) == container
diff --git a/src/blenderbim/test/tool/test_root.py b/src/blenderbim/test/tool/test_root.py
index 448fb4bc4d..7e0e40fed4 100644
--- a/src/blenderbim/test/tool/test_root.py
+++ b/src/blenderbim/test/tool/test_root.py
@@ -18,6 +18,7 @@
import bpy
import ifcopenshell
+import ifcopenshell.api
import blenderbim.core.tool
import blenderbim.tool as tool
from test.bim.bootstrap import NewFile
@@ -97,7 +98,7 @@ class TestGetElementType(NewFile):
ifc = tool.Ifc.get()
element = ifc.createIfcWall()
type = ifc.createIfcWallType()
- ifcopenshell.api.run("type.assign_type", ifc, related_object=element, relating_type=type)
+ ifcopenshell.api.run("type.assign_type", ifc, related_objects=[element], relating_type=type)
assert subject.get_element_type(element) == type
diff --git a/src/blenderbim/test/tool/test_spatial.py b/src/blenderbim/test/tool/test_spatial.py
index 7aafc1e882..602a41b8f9 100644
--- a/src/blenderbim/test/tool/test_spatial.py
+++ b/src/blenderbim/test/tool/test_spatial.py
@@ -154,7 +154,7 @@ class TestGetContainer(NewFile):
ifc = ifcopenshell.file()
site = ifc.createIfcSite()
wall = ifc.createIfcWall()
- ifcopenshell.api.run("spatial.assign_container", ifc, product=wall, relating_structure=site)
+ ifcopenshell.api.run("spatial.assign_container", ifc, products=[wall], relating_structure=site)
assert subject.get_container(wall) == site
@@ -163,7 +163,7 @@ class TestGetDecomposedElements(NewFile):
ifc = ifcopenshell.file()
site = ifc.createIfcSite()
wall = ifc.createIfcWall()
- ifcopenshell.api.run("spatial.assign_container", ifc, product=wall, relating_structure=site)
+ ifcopenshell.api.run("spatial.assign_container", ifc, products=[wall], relating_structure=site)
assert subject.get_decomposed_elements(site) == [wall]
@@ -268,4 +268,4 @@ class TestSelectProducts(NewFile):
bpy.context.scene.collection.objects.link(obj)
tool.Ifc.link(product, obj)
subject.select_products([product])
- assert obj in bpy.context.selected_objects
\ No newline at end of file
+ assert obj in bpy.context.selected_objects
diff --git a/src/blenderbim/test/tool/test_system.py b/src/blenderbim/test/tool/test_system.py
index 419c46cc7f..93235545b1 100644
--- a/src/blenderbim/test/tool/test_system.py
+++ b/src/blenderbim/test/tool/test_system.py
@@ -262,7 +262,7 @@ class TestSelectSystemProducts(NewFile):
tool.Ifc().set(ifc)
element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcPump")
system = ifcopenshell.api.run("system.add_system", ifc, ifc_class="IfcSystem")
- ifcopenshell.api.run("system.assign_system", ifc, product=element, system=system)
+ ifcopenshell.api.run("system.assign_system", ifc, products=[element], system=system)
obj = bpy.data.objects.new("Object", None)
bpy.context.scene.collection.objects.link(obj)
tool.Ifc.link(element, obj)
diff --git a/src/blenderbim/test/tool/test_type.py b/src/blenderbim/test/tool/test_type.py
index 4e3a7342f5..bf497e021c 100644
--- a/src/blenderbim/test/tool/test_type.py
+++ b/src/blenderbim/test/tool/test_type.py
@@ -154,7 +154,7 @@ class TestGetTypeOccurrences(NewFile):
tool.Ifc.set(ifc)
wall_type = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWallType")
wall = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall")
- ifcopenshell.api.run("type.assign_type", ifc, related_object=wall, relating_type=wall_type)
+ ifcopenshell.api.run("type.assign_type", ifc, related_objects=[wall], relating_type=wall_type)
assert subject.get_type_occurrences(wall_type) == (wall,)
diff --git a/src/bsdd/Makefile b/src/bsdd/Makefile
new file mode 100644
index 0000000000..6880922653
--- /dev/null
+++ b/src/bsdd/Makefile
@@ -0,0 +1,34 @@
+# bSDD - Python bSDD library
+# Copyright (C) 2021 Dion Moult
+#
+# This file is part of bSDD.
+#
+# bSDD is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# bSDD is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with bSDD. If not, see .
+
+VERSION:=`date '+%y%m%d'`
+SED:=sed -i
+ifeq ($(UNAME_S),Darwin)
+SED:=sed -i '' -e
+endif
+
+.PHONY: test
+test:
+ pytest -p no:pytest-blender test
+
+.PHONY: dist
+dist:
+ rm -rf dist
+ $(SED) "s/999999/$(VERSION)/" pyproject.toml
+ python -m build
+ $(SED) "s/$(VERSION)/999999/" pyproject.toml
diff --git a/src/bsdd/__about__.py b/src/bsdd/__about__.py
deleted file mode 100644
index 47782d2864..0000000000
--- a/src/bsdd/__about__.py
+++ /dev/null
@@ -1,4 +0,0 @@
-# SPDX-FileCopyrightText: 2024-present IfcOpenShell Developers
-#
-# SPDX-License-Identifier: LGPL-3.0-or-later
-__version__ = "0.1.0"
diff --git a/src/bsdd/bsdd.py b/src/bsdd/bsdd.py
index 8c5fe9298a..7cc7301668 100644
--- a/src/bsdd/bsdd.py
+++ b/src/bsdd/bsdd.py
@@ -23,7 +23,7 @@ import urllib
import requests
import webbrowser
import http.server
-from typing import TypedDict, Literal
+from typing import TypedDict, Literal, Optional
Status = Literal["Preview", "Active", "Inactive"]
ClassTypes = Literal["Class", "GroupOfProperties", "AlternativeUse", "Material"]
@@ -592,7 +592,7 @@ class Client:
}
return self.get(endpoint, params)
- def get_properties(self, dictionary_uri: str, language_code: str = "",
+ def get_properties(self, dictionary_uri: str, offset: int = 0, limit: int = 100, language_code: str = "",
version: int = 1) -> DictionaryPropertiesResponseContractV1:
"""
Get Dictionary with its properties
@@ -601,11 +601,14 @@ class Client:
params = {
"Uri": dictionary_uri,
"languageCode": language_code,
+ "offset": offset,
+ "limit": limit
}
return self.get(endpoint, params)
def get_class(self, class_uri: str, include_class_properties: bool = True,
include_child_class_reference: bool = True, include_class_relations: bool = True,
+ include_reverse_relations: bool = False, reverse_relation_dictionary_uris: Optional[list[str]]=None,
language_code: str = "", version: int = 1) -> ClassContractV1:
"""
Get Class details
@@ -617,8 +620,11 @@ class Client:
"includeClassProperties": include_class_properties,
"includeChildClassReferences": include_child_class_reference,
"includeClassRelations": include_class_relations,
+ "IncludeReverseRelations": include_reverse_relations,
+ "ReverseRelationDictionaryUris": reverse_relation_dictionary_uris,
"languageCode": language_code,
}
+ params = {k: v for k, v in params.items() if v is not None}
return self.get(endpoint, params)
def get_property(self, uri, include_classes=False, language_code="",
@@ -649,7 +655,7 @@ class Client:
return self.get(endpoint, params)
def search_text(self, search_text: str, type_filter="All", dictionary_uris=None,
- version: int = 1) -> TextSearchResponseContractV1:
+ version: int = 1, offset: int = 0, limit: int = 100) -> TextSearchResponseContractV1:
"""
Search the bSDD database using free text, get list of Classes and/or Properties matching the text.
Pagination options are for Classes and Properties combined.
@@ -663,13 +669,15 @@ class Client:
"SearchText": search_text,
"TypeFilter": type_filter,
"DictionaryUris": dictionary_uris,
+ "offset": offset,
+ "limit": limit
}
return self.get(endpoint, params)
pass
def search_in_dictionary(self, dictionary_uri: str, search_text: str = "", language_code: str = "",
related_ifc_entity: str = "",
- version: int = "1") -> SearchInDictionaryResponseContractV1:
+ version: int = 1, offset: int = 0, limit: int = 100) -> SearchInDictionaryResponseContractV1:
"""
Search the bSDD database, get list of Classes without details.
This version uses new naming and returns one Dictionary instead of a list with always one Dictionary.
@@ -681,11 +689,13 @@ class Client:
"SearchText": search_text,
"LanguageCode": language_code,
"RelatedIfcEntity": related_ifc_entity,
+ "offset": offset,
+ "limit": limit
}
return self.get(endpoint, params)
def search_class(self, search_text: str, dictionary_uris=None, related_ifc_entities=None,
- version: int = 1) -> ClassSearchResponseContractV1:
+ version: int = 1, offset: int = 0, limit: int = 100) -> ClassSearchResponseContractV1:
"""
Search the bSDD database using free text, get list of Classes matching the text and optional additional filters.
this API replaces ClassificationSearch
@@ -700,6 +710,8 @@ class Client:
"SearchText": search_text,
"DictionaryUris": dictionary_uris,
"RelatedIfcEntities": related_ifc_entities,
+ "offset": offset,
+ "limit": limit
}
return self.get(endpoint, params)
diff --git a/src/bsdd/pyproject.toml b/src/bsdd/pyproject.toml
index 72683258eb..2b9e471050 100644
--- a/src/bsdd/pyproject.toml
+++ b/src/bsdd/pyproject.toml
@@ -4,31 +4,32 @@ build-backend = "hatchling.build"
[project]
name = "bsdd"
-dynamic = ["version"]
-description = ''
+version = "0.0.999999"
+authors = [
+ { name="Dion Moult", email="dion@thinkmoult.com" },
+]
+description = "Library to interact with the buildingSMART Data Dictionary"
readme = "README.md"
requires-python = ">=3.8"
-keywords = ["building smart data dictionaries"]
+keywords = ["IFC", "bSDD", "BIM"]
classifiers = [
- "Development Status :: 4 - Beta",
- "Programming Language :: Python",
- "Programming Language :: Python :: 3.8",
- "Programming Language :: Python :: 3.9",
- "Programming Language :: Python :: 3.10",
- "Programming Language :: Python :: 3.11",
- "Programming Language :: Python :: 3.12",
- "Programming Language :: Python :: Implementation :: CPython",
- "Programming Language :: Python :: Implementation :: PyPy",
+ "Development Status :: 4 - Beta",
+ "Programming Language :: Python",
+ "Programming Language :: Python :: 3.8",
+ "Programming Language :: Python :: 3.9",
+ "Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: 3.11",
+ "Programming Language :: Python :: 3.12",
+ "Programming Language :: Python :: Implementation :: CPython",
+ "Programming Language :: Python :: Implementation :: PyPy",
+ "License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)",
]
dependencies = ["requests"]
[project.urls]
-Documentation = "https://github.com/IfcOpenShell/src/bsdd#readme"
-Issues = "https://github.com/IfcOpenShell/issues"
-Source = "https://github.com/IfcOpenShell/src/bsdd"
-
-[tool.hatch.version]
-path = "__about__.py"
+Homepage = "http://ifcopenshell.org"
+Documentation = "https://docs.ifcopenshell.org"
+Issues = "https://github.com/IfcOpenShell/IfcOpenShell/issues"
[tool.hatch.envs.default]
dependencies = [
@@ -39,6 +40,5 @@ dependencies = [
[[tool.hatch.envs.all.matrix]]
python = ["3.8", "3.9", "3.10", "3.11", "3.12"]
-
[tool.pytest.ini_options]
-pythonpath = [".", "src"]
\ No newline at end of file
+pythonpath = [".", "src"]
diff --git a/src/bsdd/tests/test_bsdd.py b/src/bsdd/tests/test_bsdd.py
index 56ae193737..2cc1fd3935 100644
--- a/src/bsdd/tests/test_bsdd.py
+++ b/src/bsdd/tests/test_bsdd.py
@@ -35,3 +35,7 @@ def test_search_class():
assert len(li) < 8 # I think it should be 4 but just validating it isn't overfetching with some space for future change
for l in li:
assert l in [_["name"] for _ in ss_heat_pump_sys["classes"]]
+
+def test_get_properties():
+ pr = client.get_properties(ifc4x3_uri, offset=0, limit=5)
+ assert len(pr["properties"]) == 5
diff --git a/src/ifc4d/ifc4d/csv4d2ifc.py b/src/ifc4d/ifc4d/csv4d2ifc.py
index a5f6a460a9..4da00339c6 100644
--- a/src/ifc4d/ifc4d/csv4d2ifc.py
+++ b/src/ifc4d/ifc4d/csv4d2ifc.py
@@ -118,6 +118,7 @@ class Csv2Ifc:
def create_boilerplate_ifc(self):
self.file = ifcopenshell.file(schema="IFC4")
+ ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject")
self.work_plan = self.file.create_entity("IfcWorkPlan")
def create_tasks(self, tasks, parent=None):
diff --git a/src/ifc4d/ifc4d/msp2ifc.py b/src/ifc4d/ifc4d/msp2ifc.py
index 159d8279e9..face82e7e9 100644
--- a/src/ifc4d/ifc4d/msp2ifc.py
+++ b/src/ifc4d/ifc4d/msp2ifc.py
@@ -18,6 +18,7 @@
import datetime
from datetime import timedelta, date
+from typing import List
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.date
@@ -25,7 +26,7 @@ import xml.etree.ElementTree as ET
class MSP2Ifc:
- def __init__(self):
+ def __init__(self, optionalColumns: List[str] = []):
self.xml = None
self.file = None
self.ns = None
@@ -33,6 +34,7 @@ class MSP2Ifc:
self.project = {}
self.calendars = {}
self.tasks = {}
+ self.optionalColumns = optionalColumns
self.resources = {}
self.RESOURCE_TYPES_MAPPING = {"1": "LABOR", "0": "MATERIAL", "2": None}
@@ -106,6 +108,18 @@ class MSP2Ifc:
"ifc": None,
}
+ # retrieve optional columns
+ # If first column = "all" then retrieve all columns
+ if len(self.optionalColumns) and self.optionalColumns[0] == "all":
+ self.optionalColumns = [child.tag.split("}")[1] for child in task]
+
+ for column in self.optionalColumns:
+ if not self.tasks[task_id].get(column):
+ self.tasks[task_id][column] = task.find(f"pr:{column}", self.ns).text if task.find(f"pr:{column}", self.ns) else None
+
+
+
+
def parse_calendar_xml(self, project):
def parse_working_times(day):
working_times = []
@@ -194,12 +208,14 @@ class MSP2Ifc:
def create_boilerplate_ifc(self):
self.file = ifcopenshell.file(schema="IFC4")
+ ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject")
self.work_plan = self.file.create_entity("IfcWorkPlan")
def create_tasks(self, work_schedule):
for task_id in self.tasks:
task = self.tasks[task_id]
- if task["OutlineLevel"] == 0:
+ # Outline Level can be None or 0
+ if not task["OutlineLevel"]:
self.create_task(task, work_schedule=work_schedule)
def create_work_schedule(self):
@@ -267,6 +283,18 @@ class MSP2Ifc:
for subtask_id in task["subtasks"]:
self.create_task(self.tasks[subtask_id], parent_task=task)
+
+ # create pset for optional columns
+ if len(self.optionalColumns):
+ pset = ifcopenshell.api.run("pset.add_pset", self.file, product=task["ifc"] , name="Pset_MSP_Task")
+
+ ifcopenshell.api.run(
+ "pset.edit_pset",
+ self.file,
+ pset=pset,
+ properties= {name: str(task[name]) for name in self.optionalColumns if task[name]}
+ )
+
def process_working_week(self, week, calendar):
day_map = {
"1": 7, # Sunday
diff --git a/src/ifc5d/ifc5d/csv2ifc.py b/src/ifc5d/ifc5d/csv2ifc.py
index cb7b8869ff..1e708d1e8f 100644
--- a/src/ifc5d/ifc5d/csv2ifc.py
+++ b/src/ifc5d/ifc5d/csv2ifc.py
@@ -196,6 +196,7 @@ class Csv2Ifc:
def create_boilerplate_ifc(self):
self.file = ifcopenshell.file(schema="IFC4")
+ ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject")
def has_property(self, product, property_name):
diff --git a/src/ifcbimtester/CITATION.cff b/src/ifcbimtester/CITATION.cff
index d2d6ca6abb..a7222b5a28 100644
--- a/src/ifcbimtester/CITATION.cff
+++ b/src/ifcbimtester/CITATION.cff
@@ -11,7 +11,7 @@ authors:
- name: "IfcOpenShell contributors"authors:
repository-code: >-
https://github.com/IfcOpenShell/IfcOpenShell/tree/v0.7.0/src/ifcbimtester
-url: 'https://blenderbim.org/docs-python/bimtester.html'
+url: 'https://docs.ifcopenshell.org/bimtester.html'
abstract: Wrapper for Gherkin based unit testing for IFC models
keywords:
- Gherkin
diff --git a/src/ifcclash/Makefile b/src/ifcclash/Makefile
new file mode 100644
index 0000000000..ec05e23471
--- /dev/null
+++ b/src/ifcclash/Makefile
@@ -0,0 +1,34 @@
+# IfcClash - IFC-based clash detection.
+# Copyright (C) 2020-2024 Dion Moult
+#
+# This file is part of IfcClash.
+#
+# IfcClash is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# IfcClash is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with IfcClash. If not, see .
+
+VERSION:=`date '+%y%m%d'`
+SED:=sed -i
+ifeq ($(UNAME_S),Darwin)
+SED:=sed -i '' -e
+endif
+
+.PHONY: qa
+qa:
+ black .
+
+.PHONY: dist
+dist:
+ rm -rf dist
+ $(SED) "s/999999/$(VERSION)/" pyproject.toml
+ python -m build
+ $(SED) "s/$(VERSION)/999999/" pyproject.toml
diff --git a/src/ifcclash/ifcclash/collider.py b/src/ifcclash/ifcclash/collider.py
deleted file mode 100644
index 62a912df5e..0000000000
--- a/src/ifcclash/ifcclash/collider.py
+++ /dev/null
@@ -1,121 +0,0 @@
-# IfcClash - IFC-based clash detection.
-# Copyright (C) 2020, 2021 Dion Moult
-#
-# This file is part of IfcClash.
-#
-# IfcClash is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# IfcClash is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with IfcClash. If not, see .
-
-import hppfcl
-import numpy as np
-import ifcopenshell
-
-
-class Collider:
- def __init__(self, logger):
- self.logger = logger
- self.groups = {}
- self.tree = ifcopenshell.geom.tree()
-
- def create_group(self, name):
- self.logger.info(f"Creating group {name}")
- self.groups[name] = {"elements": {}, "objects": {}}
-
- def create_objects(self, name, ifc_file, iterator, elements):
- import time
-
- start = time.time()
- self.logger.info(f"Adding objects {name}")
- assert iterator.initialize()
- while True:
- self.tree.add_element(iterator.get_native())
- shape = iterator.get()
- self.create_object(name, shape.guid, shape)
- if not iterator.next():
- break
- self.logger.info(f"Tree finished {time.time() - start}")
- start = time.time()
- self.groups[name]["elements"].update({e.GlobalId: e for e in elements})
- self.logger.info(f"Element metadata finished {time.time() - start}")
- start = time.time()
-
- def create_object(self, group_name, id, shape):
- obj = hppfcl.CollisionObject(
- self.create_bvh(shape.geometry), self.create_transform(shape.transformation.matrix.data)
- )
- self.groups[group_name]["objects"][id] = obj
-
- def collide_internal(self, name):
- return self.collide_narrowphase(name, name, self.collide_broadphase(name, name))
-
- def collide_group(self, name1, name2):
- return self.collide_narrowphase(name1, name2, self.collide_broadphase(name1, name2))
-
- def collide_broadphase(self, name1, name2):
- import time
-
- start = time.time()
- self.logger.info("Starting broadphase")
- potential_collisions = []
- checked_collisions = set()
- for id, element in self.groups[name1]["elements"].items():
- checked_collisions.add(id)
- box_filter = self.tree.select_box(element)
- pairs = [
- {"id1": id, "id2": e.GlobalId}
- for e in box_filter
- if e.GlobalId not in checked_collisions and e.GlobalId in self.groups[name2]["elements"]
- ]
- potential_collisions.extend(pairs)
- self.logger.info(f"Finished broadphase {time.time() - start}")
- return potential_collisions
-
- def collide_narrowphase(self, name1, name2, potential_collisions):
- import time
-
- start = time.time()
- self.logger.info("Starting narrowphase")
- collisions = []
- for data in potential_collisions:
- result = hppfcl.CollisionResult()
- hppfcl.collide(
- self.groups[name1]["objects"][data["id1"]],
- self.groups[name2]["objects"][data["id2"]],
- hppfcl.CollisionRequest(),
- result,
- )
- if result.isCollision():
- collisions.append({"id1": data["id1"], "id2": data["id2"], "collision": result})
- self.logger.info(f"Finished narrowphase {time.time() - start}")
- return collisions
-
- def create_transform(self, m):
- mat = np.array([[m[0], m[3], m[6], m[9]], [m[1], m[4], m[7], m[10]], [m[2], m[5], m[8], m[11]], [0, 0, 0, 1]])
- mat.transpose()
- return hppfcl.Transform3f(mat[:3, :3], mat[:3, 3])
-
- def create_bvh(self, mesh):
- v = mesh.verts
- f = mesh.faces
- mesh_verts = np.array([[v[i], v[i + 1], v[i + 2]] for i in range(0, len(v), 3)])
- mesh_faces = [(int(f[i]), int(f[i + 1]), int(f[i + 2])) for i in range(0, len(f), 3)]
-
- bvh = hppfcl.BVHModelOBB()
- bvh.beginModel(len(mesh_faces), len(mesh_verts))
- vertices = hppfcl.StdVec_Vec3f()
- [vertices.append(v) for v in mesh_verts]
- triangles = hppfcl.StdVec_Triangle()
- [triangles.append(hppfcl.Triangle(f[0], f[1], f[2])) for f in mesh_faces]
- bvh.addSubModel(vertices, triangles)
- bvh.endModel()
- return bvh
diff --git a/src/ifcclash/ifcclash/ifcclash.py b/src/ifcclash/ifcclash/ifcclash.py
index 1406f40aee..a967be3ce3 100644
--- a/src/ifcclash/ifcclash/ifcclash.py
+++ b/src/ifcclash/ifcclash/ifcclash.py
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
# IfcClash - IFC-based clash detection.
-# Copyright (C) 2020, 2021 Dion Moult
+# Copyright (C) 2020-2024 Dion Moult
#
# This file is part of IfcClash.
#
@@ -19,14 +19,13 @@
# along with IfcClash. If not, see .
-import numpy as np
import json
-import sys
+import time
+import numpy as np
import multiprocessing
import ifcopenshell
import ifcopenshell.geom
import ifcopenshell.util.selector
-from . import collider
class Clasher:
@@ -34,55 +33,78 @@ class Clasher:
self.settings = settings
self.geom_settings = ifcopenshell.geom.settings()
self.clash_sets = []
- self.collider = collider.Collider(self.settings.logger)
- self.selector = ifcopenshell.util.selector.Selector()
+ self.logger = self.settings.logger
+ self.groups = {}
self.ifcs = {}
+ self.tree = None
def clash(self):
- existing_limit = sys.getrecursionlimit()
for clash_set in self.clash_sets:
self.process_clash_set(clash_set)
def process_clash_set(self, clash_set):
- self.collider.create_group("a")
+ self.tree = ifcopenshell.geom.tree()
+ self.create_group("a")
for source in clash_set["a"]:
source["ifc"] = self.load_ifc(source["file"])
self.add_collision_objects("a", source["ifc"], source.get("mode", None), source.get("selector", None))
- if "b" in clash_set:
- self.collider.create_group("b")
+ if "b" in clash_set and clash_set["b"]:
+ self.create_group("b")
for source in clash_set["b"]:
source["ifc"] = self.load_ifc(source["file"])
self.add_collision_objects("b", source["ifc"], source.get("mode", None), source.get("selector", None))
- results = self.collider.collide_group("a", "b")
+ b = "b"
else:
- results = self.collider.collide_internal("a")
+ b = "a"
+
+ mode = clash_set["mode"]
+ if mode == "intersection":
+ results = self.tree.clash_intersection_many(
+ list(self.groups["a"]["elements"].values()),
+ list(self.groups[b]["elements"].values()),
+ tolerance=clash_set["tolerance"],
+ check_all=clash_set["check_all"],
+ )
+ elif mode == "collision":
+ results = self.tree.clash_collision_many(
+ list(self.groups["a"]["elements"].values()),
+ list(self.groups[b]["elements"].values()),
+ allow_touching=clash_set["allow_touching"],
+ )
+ elif mode == "clearance":
+ results = self.tree.clash_clearance_many(
+ list(self.groups["a"]["elements"].values()),
+ list(self.groups[b]["elements"].values()),
+ clearance=clash_set["clearance"],
+ check_all=clash_set["check_all"],
+ )
processed_results = {}
for result in results:
- element1 = self.get_element(clash_set["a"], result["id1"])
- if "b" in clash_set:
- element2 = self.get_element(clash_set["b"], result["id2"])
- else:
- element2 = self.get_element(clash_set["a"], result["id2"])
+ element1 = result.a
+ element2 = result.b
- contact = result["collision"].getContacts()[0]
- processed_results[f"{result['id1']}-{result['id2']}"] = {
- "a_global_id": result["id1"],
- "b_global_id": result["id2"],
+ processed_results[f"{element1.get_argument(0)}-{element2.get_argument(0)}"] = {
+ "a_global_id": element1.get_argument(0),
+ "b_global_id": element2.get_argument(0),
"a_ifc_class": element1.is_a(),
"b_ifc_class": element2.is_a(),
- "a_name": element1.Name,
- "b_name": element2.Name,
- "normal": list(contact.normal),
- "position": list(contact.pos),
- "penetration_depth": contact.penetration_depth,
+ "a_name": element1.get_argument(2),
+ "b_name": element2.get_argument(2),
+ "type": ["protrusion", "pierce", "collision", "clearance"][result.clash_type],
+ "p1": list(result.p1),
+ "p2": list(result.p2),
+ "distance": result.distance,
}
clash_set["clashes"] = processed_results
+ self.logger.info(f"Found clashes: {len(processed_results.keys())}")
+
+ def create_group(self, name):
+ self.logger.info(f"Creating group {name}")
+ self.groups[name] = {"elements": {}, "objects": {}}
def load_ifc(self, path):
- import time
-
start = time.time()
self.settings.logger.info(f"Loading IFC {path}")
ifc = self.ifcs.get(path, None)
@@ -93,21 +115,35 @@ class Clasher:
return ifc
def add_collision_objects(self, name, ifc_file, mode=None, selector=None):
- import time
-
start = time.time()
self.settings.logger.info("Creating iterator")
- if not mode:
- elements = ifc_file.by_type("IfcElement")
+ if not mode or mode == "a" or not selector:
+ elements = set(ifc_file.by_type("IfcElement"))
+ elements -= set(ifc_file.by_type("IfcFeatureElement"))
elif mode == "e":
- elements = set(ifc_file.by_type("IfcElement")) - set(self.selector.parse(ifc_file, selector))
+ elements = set(ifc_file.by_type("IfcElement"))
+ elements -= set(ifc_file.by_type("IfcFeatureElement"))
+ elements -= set(ifcopenshell.util.selector.filter_elements(ifc_file, selector))
elif mode == "i":
- elements = self.selector.parse(ifc_file, selector)
+ elements = set(ifcopenshell.util.selector.filter_elements(ifc_file, selector))
iterator = ifcopenshell.geom.iterator(
self.geom_settings, ifc_file, multiprocessing.cpu_count(), include=elements
)
self.settings.logger.info(f"Iterator creation finished {time.time() - start}")
- self.collider.create_objects(name, ifc_file, iterator, elements)
+
+ start = time.time()
+ self.logger.info(f"Adding objects {name}")
+ assert iterator.initialize()
+ while True:
+ self.tree.add_element(iterator.get())
+ shape = iterator.get()
+ if not iterator.next():
+ break
+ self.logger.info(f"Tree finished {time.time() - start}")
+ start = time.time()
+ self.groups[name]["elements"].update({e.GlobalId: e for e in elements})
+ self.logger.info(f"Element metadata finished {time.time() - start}")
+ start = time.time()
def export(self):
if len(self.settings.output) > 4 and self.settings.output[-4:] == ".bcf":
@@ -123,7 +159,9 @@ class Clasher:
title = f'{clash["a_ifc_class"]}/{clash["a_name"]} and {clash["b_ifc_class"]}/{clash["b_name"]}'
topic = bcfxml.add_topic(title, title, "IfcClash")
viewpoint = topic.add_viewpoint_from_point_and_guids(
- np.array(clash["position"]), clash["a_global_id"], clash["b_global_id"],
+ np.array(clash["position"]),
+ clash["a_global_id"],
+ clash["b_global_id"],
)
snapshot = self.get_viewpoint_snapshot(viewpoint)
if snapshot:
@@ -147,15 +185,6 @@ class Clasher:
with open(self.settings.output, "w", encoding="utf-8") as clashes_file:
json.dump(clash_sets, clashes_file, indent=4)
- def get_element(self, clash_set, global_id):
- for source in clash_set:
- try:
- element = source["ifc"].by_guid(global_id)
- if element:
- return element
- except:
- pass
-
def smart_group_clashes(self, clash_sets, max_clustering_distance):
from sklearn.cluster import OPTICS
from collections import defaultdict
diff --git a/src/ifcclash/pyproject.toml b/src/ifcclash/pyproject.toml
new file mode 100644
index 0000000000..148c16a0e4
--- /dev/null
+++ b/src/ifcclash/pyproject.toml
@@ -0,0 +1,29 @@
+[build-system]
+requires = ["setuptools>=61.0"]
+build-backend = "setuptools.build_meta"
+
+[project]
+name = "ifcclash"
+version = "0.0.999999"
+authors = [
+ { name="Dion Moult", email="dion@thinkmoult.com" },
+]
+description = "IFC clash detection library to handle intersections, collisions, and clearance checks"
+readme = "README.md"
+keywords = ["IFC", "clash", "BIM"]
+classifiers = [
+ "Programming Language :: Python :: 3",
+ "License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)",
+]
+dependencies = [
+ "ifcopenshell",
+]
+
+[project.urls]
+Homepage = "http://ifcopenshell.org"
+Documentation = "https://docs.ifcopenshell.org"
+Issues = "https://github.com/IfcOpenShell/IfcOpenShell/issues"
+
+[tool.setuptools.packages.find]
+include = ["ifcclash"]
+exclude = ["test*"]
diff --git a/src/ifccsv/Makefile b/src/ifccsv/Makefile
new file mode 100644
index 0000000000..b3cbbf8b49
--- /dev/null
+++ b/src/ifccsv/Makefile
@@ -0,0 +1,34 @@
+# IfcCSV - A utility to interact with IFC data through CSV.
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of IfcCSV.
+#
+# IfcCSV is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# IfcCSV is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with IfcCSV. If not, see .
+
+VERSION:=`date '+%y%m%d'`
+SED:=sed -i
+ifeq ($(UNAME_S),Darwin)
+SED:=sed -i '' -e
+endif
+
+.PHONY: qa
+qa:
+ black .
+
+.PHONY: dist
+dist:
+ rm -rf dist
+ $(SED) "s/999999/$(VERSION)/" pyproject.toml
+ python -m build
+ $(SED) "s/$(VERSION)/999999/" pyproject.toml
diff --git a/src/ifccsv/ifccsv.py b/src/ifccsv/ifccsv.py
index 474de0b93a..bcb7f4855f 100755
--- a/src/ifccsv/ifccsv.py
+++ b/src/ifccsv/ifccsv.py
@@ -29,6 +29,7 @@ import ifcopenshell.util.selector
import ifcopenshell.util.element
import ifcopenshell.util.schema
from statistics import mean
+from typing import Optional, Union
try:
from odf.namespaces import OFFICENS
@@ -239,13 +240,19 @@ class IfcCsv:
index = attributes.index(data["name"])
formatting_indices[index] = data["format"]
- for row in self.results:
- for index, format_query in formatting_indices.items():
+ for index, format_query in formatting_indices.items():
+ for row in self.results:
if row[index] == null:
continue
row[index] = '"' + str(row[index]).replace('"', '\\"') + '"'
row[index] = ifcopenshell.util.selector.format(format_query.replace("{{value}}", row[index]))
+ if self.summaries[index] is not None:
+ summary_label, summary_value = self.summaries[index].split(": ")
+ summary_value = '"' + str(summary_value).replace('"', '\\"') + '"'
+ summary_value = ifcopenshell.util.selector.format(format_query.replace("{{value}}", summary_value))
+ self.summaries[index] = summary_label + ": " + str(summary_value)
+
def sort_results(self, sort, attributes, include_global_id):
if not self.results:
return
@@ -269,7 +276,7 @@ class IfcCsv:
elif not include_global_id:
self.results = sorted(self.results, key=lambda x: x[0])
- def export_csv(self, output, delimiter=None):
+ def export_csv(self, output: str, delimiter: Optional[str] = None) -> None:
with open(output, "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f, delimiter=delimiter)
writer.writerow(self.headers)
@@ -387,8 +394,16 @@ class IfcCsv:
self.import_xlsx(ifc_file, table, attributes, null, empty, bool_true, bool_false)
def import_csv(
- self, ifc_file, table, attributes=None, delimiter=",", null="-", empty="", bool_true="YES", bool_false="NO"
- ):
+ self,
+ ifc_file: ifcopenshell.file,
+ table: str,
+ attributes: Optional[list[Union[str, None]]] = None,
+ delimiter: str = ",",
+ null: str = "-",
+ empty: str = "",
+ bool_true: str = "YES",
+ bool_false: str = "NO",
+ ) -> None:
with open(table, newline="", encoding="utf-8") as f:
reader = csv.reader(f, delimiter=delimiter)
headers = []
@@ -421,7 +436,17 @@ class IfcCsv:
for _, row in df.iterrows():
self.process_row(ifc_file, row.tolist(), headers, attributes, null, empty, bool_true, bool_false)
- def process_row(self, ifc_file, row, headers, attributes, null, empty, bool_true, bool_false):
+ def process_row(
+ self,
+ ifc_file: ifcopenshell.file,
+ row: list[str],
+ headers: list[str],
+ attributes: list[Union[str, None]],
+ null: str,
+ empty: str,
+ bool_true: str,
+ bool_false: str,
+ ) -> None:
try:
element = ifc_file.by_guid(row[0])
except:
@@ -448,9 +473,7 @@ if __name__ == "__main__":
parser.add_argument("-s", "--spreadsheet", type=str, default="data.csv", help="The spreadsheet file")
parser.add_argument("-f", "--format", type=str, default="csv", help="The format, chosen from csv, ods, or xlsx")
parser.add_argument("-d", "--delimiter", type=str, default=",", help="The delimiter in CSV. Defaults to a comma.")
- parser.add_argument(
- "-n", "--null", type=str, default="N/A", help="How to represent null values. Defaults to N/A."
- )
+ parser.add_argument("-n", "--null", type=str, default="N/A", help="How to represent null values. Defaults to N/A.")
parser.add_argument(
"-e", "--empty", type=str, default="-", help="How to represent empty strings. Defaults to a hyphen."
)
diff --git a/src/ifccsv/pyproject.toml b/src/ifccsv/pyproject.toml
new file mode 100644
index 0000000000..94d69c0ec7
--- /dev/null
+++ b/src/ifccsv/pyproject.toml
@@ -0,0 +1,28 @@
+[build-system]
+requires = ["setuptools>=61.0"]
+build-backend = "setuptools.build_meta"
+
+[project]
+name = "ifccsv"
+version = "0.0.999999"
+authors = [
+ { name="Dion Moult", email="dion@thinkmoult.com" },
+]
+description = "IFC import and export from CSV, XLSX, ODS, and Pandas Dataframes"
+readme = "README.md"
+keywords = ["IFC", "CSV", "XLS", "XLSX", "ODS", "BIM"]
+classifiers = [
+ "Programming Language :: Python :: 3",
+ "License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)",
+]
+dependencies = [
+ "ifcopenshell",
+]
+
+[project.urls]
+Homepage = "http://ifcopenshell.org"
+Documentation = "https://docs.ifcopenshell.org"
+Issues = "https://github.com/IfcOpenShell/IfcOpenShell/issues"
+
+[tool.setuptools]
+py-modules = ["ifccsv"]
diff --git a/src/ifcdiff/ifcdiff.py b/src/ifcdiff/ifcdiff.py
index e41a9667ee..2ee8392f9d 100755
--- a/src/ifcdiff/ifcdiff.py
+++ b/src/ifcdiff/ifcdiff.py
@@ -54,7 +54,7 @@ class IfcDiff:
that comparisons will take longer.
:type is_shallow: bool
:param filter_elements: An IFC filter query if you only want to compare a
- subset of elements. For example: ``.IfcWall`` to only compare walls.
+ subset of elements. For example: ``IfcWall`` to only compare walls.
:type filter_elements: string
Example::
@@ -83,9 +83,12 @@ class IfcDiff:
self.precision = self.get_precision()
if self.filter_elements:
- selector = ifcopenshell.util.selector.Selector()
- old_elements = set(e.GlobalId for e in selector.parse(self.old, self.filter_elements))
- new_elements = set(e.GlobalId for e in selector.parse(self.new, self.filter_elements))
+ old_elements = set(
+ e.GlobalId for e in ifcopenshell.util.selector.filter_elements(self.old, self.filter_elements)
+ )
+ new_elements = set(
+ e.GlobalId for e in ifcopenshell.util.selector.filter_elements(self.new, self.filter_elements)
+ )
else:
old_elements = self.old.by_type("IfcElement")
if self.old.schema == "IFC2X3":
@@ -277,7 +280,14 @@ class IfcDiff:
return
for relationship in self.relationships:
if relationship == "type":
- if ifcopenshell.util.element.get_type(old) != ifcopenshell.util.element.get_type(new):
+ old_type = ifcopenshell.util.element.get_type(old)
+ new_type = ifcopenshell.util.element.get_type(new)
+ if old_type is not None and new_type is not None:
+ if old_type.GlobalId != new_type.GlobalId:
+ self.change_register.setdefault(new.GlobalId, {}).update({"type_changed": True})
+ return True
+ elif old_type != new_type:
+ # one of the types is None while the other is not None
self.change_register.setdefault(new.GlobalId, {}).update({"type_changed": True})
return True
elif relationship == "property":
diff --git a/src/ifcgeom/IfcDerivedProfileDef.cpp b/src/ifcgeom/IfcDerivedProfileDef.cpp
index 1bf666540a..2e022d887c 100644
--- a/src/ifcgeom/IfcDerivedProfileDef.cpp
+++ b/src/ifcgeom/IfcDerivedProfileDef.cpp
@@ -28,7 +28,19 @@
bool IfcGeom::Kernel::convert(const IfcSchema::IfcDerivedProfileDef* l, TopoDS_Shape& face) {
TopoDS_Face f;
gp_Trsf2d trsf2d;
- if (convert_face(l->ParentProfile(), f) && IfcGeom::Kernel::convert(l->Operator(), trsf2d)) {
+ bool is_mirror = false;
+#ifdef SCHEMA_HAS_IfcMirroredProfileDef
+ if (l->as()) {
+ trsf2d.SetMirror(gp::Origin2d());
+ is_mirror = true;
+ }
+#endif
+ if (!is_mirror) {
+ if (!IfcGeom::Kernel::convert(l->Operator(), trsf2d)) {
+ return false;
+ }
+ }
+ if (convert_face(l->ParentProfile(), f)) {
gp_Trsf trsf = trsf2d;
face = BRepBuilderAPI_Transform(f, trsf).Shape();
return true;
diff --git a/src/ifcgeom/IfcGeom.cpp b/src/ifcgeom/IfcGeom.cpp
index 72ebf0376e..ebbf24b89a 100644
--- a/src/ifcgeom/IfcGeom.cpp
+++ b/src/ifcgeom/IfcGeom.cpp
@@ -859,7 +859,9 @@ IfcSchema::IfcRepresentation* IfcGeom::Kernel::representation_mapped_to(const If
}
}
} catch (const IfcParse::IfcException& e) {
- Logger::Error(e);
+ // @todo the remove_boundingboxes() in IfcConvert leave dangling IfcRepresentationMap
+ // causing this error to be logged.
+ Logger::Error(e, representation);
// @todo reset representation_mapped_to to zero?
}
return representation_mapped_to;
diff --git a/src/ifcgeom/IfcGeomIteratorImplementation.h b/src/ifcgeom/IfcGeomIteratorImplementation.h
index 5cf70522f9..1b5d9e8c6c 100644
--- a/src/ifcgeom/IfcGeomIteratorImplementation.h
+++ b/src/ifcgeom/IfcGeomIteratorImplementation.h
@@ -115,6 +115,7 @@ namespace IfcGeom {
private:
std::atomic finished_{ false };
+ std::atomic terminating_{ false };
std::atomic progress_{ 0 };
std::vector tasks_;
@@ -358,6 +359,10 @@ namespace IfcGeom {
std::ref(settings),
&rep);
+ if (terminating_) {
+ break;
+ }
+
threadpool.emplace_back(std::move(fu));
}
@@ -367,8 +372,10 @@ namespace IfcGeom {
finished_ = true;
- Logger::Status("\rDone creating geometry (" + boost::lexical_cast(all_processed_elements_.size()) +
- " objects) ");
+ if (!terminating_) {
+ Logger::Status("\rDone creating geometry (" + boost::lexical_cast(all_processed_elements_.size()) +
+ " objects) ");
+ }
}
/// Computes model's bounding box (bounds_min and bounds_max).
@@ -1208,6 +1215,14 @@ namespace IfcGeom {
}
~MAKE_TYPE_NAME(IteratorImplementation_)() {
+ if (num_threads_ != 1) {
+ terminating_ = true;
+
+ if (init_future_.valid()) {
+ init_future_.wait();
+ }
+ }
+
if (owns_ifc_file) {
delete ifc_file;
}
diff --git a/src/ifcgeom_schema_agnostic/IfcGeomRepresentation.cpp b/src/ifcgeom_schema_agnostic/IfcGeomRepresentation.cpp
index 7672eb381f..e30b9a3560 100644
--- a/src/ifcgeom_schema_agnostic/IfcGeomRepresentation.cpp
+++ b/src/ifcgeom_schema_agnostic/IfcGeomRepresentation.cpp
@@ -371,6 +371,11 @@ IfcGeom::Representation::Triangulation::Triangulation(const BRep& shape_model)
triangles(i).Get(n3, n2, n1);
else triangles(i).Get(n1, n2, n3);
+ if (dict[n1] == dict[n2] || dict[n2] == dict[n3] || dict[n3] == dict[n1]) {
+ Logger::Warning("Mesher generated a degenerate triangle, ignoring");
+ continue;
+ }
+
/* An alternative would be to calculate normals based
* on the coordinates of the mesh vertices */
/*
diff --git a/src/ifcgeom_schema_agnostic/IfcGeomTree.h b/src/ifcgeom_schema_agnostic/IfcGeomTree.h
index 8c7e99fde5..c72da5e59f 100644
--- a/src/ifcgeom_schema_agnostic/IfcGeomTree.h
+++ b/src/ifcgeom_schema_agnostic/IfcGeomTree.h
@@ -40,6 +40,31 @@
#include
#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "clash_utils.h"
+
+#include "H5Cpp.h"
+
+
namespace IfcGeom {
struct ray_intersection_result {
@@ -52,6 +77,15 @@ namespace IfcGeom {
double dot_product;
};
+ struct clash {
+ int clash_type; // 0 = protrusion, 1 = pierce, 2 = collision, 3 = clearance
+ IfcUtil::IfcBaseClass* a;
+ IfcUtil::IfcBaseClass* b;
+ double distance;
+ std::array p1;
+ std::array p2;
+ };
+
namespace {
// Approximates the distance `other` protrudes into `volume` by finding the
@@ -101,6 +135,740 @@ namespace IfcGeom {
namespace impl {
template
class tree {
+ bool is_shape_manifold(const TopoDS_Shape& s) {
+ TopExp_Explorer exp(s, TopAbs_SHELL);
+ bool is_closed = false;
+ while (exp.More()) {
+ is_closed = true;
+ TopoDS_Shell shell = TopoDS::Shell(exp.Current());
+ TopTools_IndexedDataMapOfShapeListOfShape edgeFaceMap;
+ TopExp::MapShapesAndAncestors(s, TopAbs_EDGE, TopAbs_FACE, edgeFaceMap);
+
+ for (int i = 1; i <= edgeFaceMap.Extent(); ++i) {
+ if (edgeFaceMap(i).Extent() < 2) {
+ // This edge is not shared by two faces, indicating a potential opening
+ return false;
+ }
+ }
+ exp.Next();
+ }
+ return is_closed;
+ }
+
+ bool is_point_in_shape(
+ const gp_Pnt& v,
+ const opencascade::handle>& bvh,
+ const std::vector>& tris,
+ const std::vector& verts,
+ // In the case of "touching" rays, let's check again!
+ bool should_check_again = false
+ ) const {
+ ray v_ray;
+ v_ray.origin[0] = v.X();
+ v_ray.origin[1] = v.Y();
+ v_ray.origin[2] = v.Z();
+
+ if (should_check_again) {
+ // The first check may be incorrect if it intersects
+ // exactly between triangles or on edges of triangles.
+ // A second check is used to "double check" the results.
+ // The second check is perpendicular because AEC objects
+ // are typically symmetrical along an axis, and goes down
+ // because there's typically less stuff down there.
+ v_ray.dir[0] = 0.0f;
+ v_ray.dir[1] = 0.0f;
+ v_ray.dir[2] = -1.0f;
+ v_ray.dir_inv[0] = INFINITY; // 1.0f/dir[0]
+ v_ray.dir_inv[1] = INFINITY; // 1.0f/dir[1]
+ v_ray.dir_inv[2] = -1.0f; // 1.0f/dir[2]
+ } else {
+ v_ray.dir[0] = 1.0f;
+ v_ray.dir[1] = 0.0f;
+ v_ray.dir[2] = 0.0f;
+ v_ray.dir_inv[0] = 1.0f; // 1.0f/dir[0]
+ v_ray.dir_inv[1] = INFINITY; // 1.0f/dir[1]
+ v_ray.dir_inv[2] = INFINITY; // 1.0f/dir[2]
+ }
+
+ gp_Vec ray_origin(v.X(), v.Y(), v.Z());
+ gp_Vec ray_vector(v_ray.dir[0], v_ray.dir[1], v_ray.dir[2]);
+
+ int total_intersections = 0;
+
+ std::stack stack;
+ stack.push(0);
+
+ while ( ! stack.empty()) {
+ int i = stack.top();
+ stack.pop();
+
+ BVH_TreeBase::BVH_VecNt min_point = bvh->MinPoint(i);
+ BVH_TreeBase::BVH_VecNt max_point = bvh->MaxPoint(i);
+
+ box box;
+ // + 1e-5 for tolerance
+ box.corners[0][0] = min_point[0] - 1e-5;
+ box.corners[0][1] = min_point[1] - 1e-5;
+ box.corners[0][2] = min_point[2] - 1e-5;
+ box.corners[1][0] = max_point[0] + 1e-5;
+ box.corners[1][1] = max_point[1] + 1e-5;
+ box.corners[1][2] = max_point[2] + 1e-5;
+ /*
+ std::cout << "Ray "
+ << v_ray.origin[0] << " "
+ << v_ray.origin[1] << " "
+ << v_ray.origin[2] << " "
+ << std::endl;
+ std::cout << "Box "
+ << min_point[0] << " "
+ << min_point[1] << " "
+ << min_point[2] << " "
+ << max_point[0] << " "
+ << max_point[1] << " "
+ << max_point[2] << " "
+ << std::endl;
+ */
+
+ if ( ! is_intersect_ray_box(&v_ray, &box)) {
+ continue;
+ }
+ //std::cout << "Ray hits box" << std::endl;
+ if (bvh->IsOuter(i)) {
+ //std::cout << "Ray hits leaf" << std::endl;
+ // Do ray triangle check.
+ for (int j=bvh->BegPrimitive(i); j<=bvh->EndPrimitive(i); ++j) {
+ const std::array& tri = tris[j];
+
+ gp_Vec ta(verts[tri[0]].XYZ());
+ gp_Vec tb(verts[tri[1]].XYZ());
+ gp_Vec tc(verts[tri[2]].XYZ());
+
+ /*
+ std::cout << "ray origin " << ray_origin.X() << " " << ray_origin.Y() << " " << ray_origin.Z() << std::endl;
+ std::cout << "inside-tri " << ta.X() << " " << ta.Y() << " " << ta.Z() << std::endl;
+ std::cout << "inside-tri " << tb.X() << " " << tb.Y() << " " << tb.Z() << std::endl;
+ std::cout << "inside-tri " << tc.X() << " " << tc.Y() << " " << tc.Z() << std::endl;
+ */
+ double at, au, av;
+ if (intersectRayTriangle(ray_origin, ray_vector, ta, tb, tc, at, au, av, false)) {
+ if (std::abs(at) < 1e-4) {
+ // The point is basically lying on a face so inside/outside is ambiguous.
+ return false;
+ }
+ // At is a signed intersection distance (positive is along +ray_vector)
+ if (at > -1e-5) {
+ total_intersections++;
+ }
+ }
+ }
+ } else {
+ stack.push(bvh->Child<0>(i));
+ stack.push(bvh->Child<1>(i));
+ }
+ }
+
+ return total_intersections % 2 != 0;
+ }
+
+ std::tuple<
+ double,
+ std::array,
+ std::array
+ > pierce_shape(
+ const gp_Vec& e1,
+ const gp_Vec& e2,
+ const opencascade::handle>& bvh,
+ const std::vector>& tris,
+ const std::vector& verts,
+ const std::vector& normals
+ ) const {
+ const gp_Vec& ray_origin = e1;
+ gp_Vec ray_vector = e2 - e1;
+ double edge_length = ray_vector.Magnitude();
+
+ std::array min_int;
+ std::array max_int;
+
+ ray_vector.Normalize();
+
+ ray v_ray;
+ v_ray.origin[0] = ray_origin.X();
+ v_ray.origin[1] = ray_origin.Y();
+ v_ray.origin[2] = ray_origin.Z();
+
+ v_ray.dir[0] = ray_vector.X();
+ v_ray.dir[1] = ray_vector.Y();
+ v_ray.dir[2] = ray_vector.Z();
+ v_ray.dir_inv[0] = 1.0f / ray_vector.X();
+ v_ray.dir_inv[1] = 1.0f / ray_vector.Y();
+ v_ray.dir_inv[2] = 1.0f / ray_vector.Z();
+
+ double min_distance = std::numeric_limits::infinity();
+ double max_distance = -std::numeric_limits::infinity();
+
+ std::stack stack;
+ stack.push(0);
+
+ while ( ! stack.empty()) {
+ int i = stack.top();
+ stack.pop();
+
+ BVH_TreeBase::BVH_VecNt min_point = bvh->MinPoint(i);
+ BVH_TreeBase::BVH_VecNt max_point = bvh->MaxPoint(i);
+
+ box box;
+ // + 1e-5 for tolerance
+ box.corners[0][0] = min_point[0] - 1e-5;
+ box.corners[0][1] = min_point[1] - 1e-5;
+ box.corners[0][2] = min_point[2] - 1e-5;
+ box.corners[1][0] = max_point[0] + 1e-5;
+ box.corners[1][1] = max_point[1] + 1e-5;
+ box.corners[1][2] = max_point[2] + 1e-5;
+
+ if ( ! is_intersect_ray_box(&v_ray, &box)) {
+ continue;
+ }
+ if (bvh->IsOuter(i)) {
+ // Do ray triangle check.
+ for (int j=bvh->BegPrimitive(i); j<=bvh->EndPrimitive(i); ++j) {
+ const std::array& tri = tris[j];
+ const gp_Vec& normal = normals[j];
+
+ if (std::abs(normal.Dot(ray_vector)) < 1e-3) {
+ continue; // This ray is coplanar to the triangle
+ }
+
+ gp_Vec ta(verts[tri[0]].XYZ());
+ gp_Vec tb(verts[tri[1]].XYZ());
+ gp_Vec tc(verts[tri[2]].XYZ());
+
+ double at, au, av;
+ // Do box check first?
+ if (intersectRayTriangle(ray_origin, ray_vector, ta, tb, tc, at, au, av, false)) {
+ // At is a signed intersection distance (positive is along +ray_vector)
+ if (at > 0 && at < edge_length) {
+ double aw = 1.0f - au - av; // Barycentric coordinate for ta
+ gp_Vec int_vec = aw * ta + au * tb + av * tc; // Intersection point
+
+ if (
+ is_point_on_line(int_vec, ta, tb)
+ || is_point_on_line(int_vec, ta, tc)
+ || is_point_on_line(int_vec, tb, tc)
+ || (ta - int_vec).Magnitude() < 1e-4
+ || (tb - int_vec).Magnitude() < 1e-4
+ || (tc - int_vec).Magnitude() < 1e-4
+ ) {
+ continue;
+ }
+
+ if (at < min_distance) {
+ min_distance = at;
+ min_int = {int_vec.X(), int_vec.Y(), int_vec.Z()};
+ }
+ if (at > max_distance) {
+ max_distance = at;
+ max_int = {int_vec.X(), int_vec.Y(), int_vec.Z()};
+ }
+ }
+ }
+ }
+ } else {
+ stack.push(bvh->Child<0>(i));
+ stack.push(bvh->Child<1>(i));
+ }
+ }
+
+ if (min_distance == std::numeric_limits::infinity()) {
+ return std::make_tuple(-1, min_int, max_int);
+ }
+ return std::make_tuple(max_distance - min_distance, min_int, max_int);
+ }
+
+ bool is_point_on_line(const gp_Pnt& point, const gp_Pnt& lineStart, const gp_Pnt& lineEnd) const {
+ // Create vectors
+ gp_Vec startToPoint(point.XYZ() - lineStart.XYZ());
+ gp_Vec startToEnd(lineEnd.XYZ() - lineStart.XYZ());
+
+ // Check if the point is on the line defined by start and end
+ // by checking if the cross product is (near) zero vector, indicating collinearity.
+ gp_Vec crossProduct = startToPoint.Crossed(startToEnd);
+ if (crossProduct.Magnitude() > 1e-5) {
+ return false; // Not collinear, hence not on the line segment
+ }
+ return true; // The point is on the line segment
+ }
+
+ // Vec variant? This _Pnt and _Vec difference is annoying.
+ bool is_point_on_line(const gp_Vec& point, const gp_Vec& lineStart, const gp_Vec& lineEnd) const {
+ // Create vectors
+ gp_Vec startToPoint = point - lineStart;
+ gp_Vec startToEnd = lineEnd - lineStart;
+
+ // Check if the point is on the line defined by start and end
+ // by checking if the cross product is (near) zero vector, indicating collinearity.
+ gp_Vec crossProduct = startToPoint.Crossed(startToEnd);
+ if (crossProduct.Magnitude() > 1e-5) {
+ return false; // Not collinear, hence not on the line segment
+ }
+ return true; // The point is on the line segment
+ }
+
+ std::unordered_map> clash_bvh(
+ opencascade::handle> bvh_a,
+ opencascade::handle> bvh_b,
+ double extend = 0.0
+ ) const {
+ std::unordered_map> bvh_clashes;
+ for (int i=0; iLength(); ++i) {
+ if ( ! bvh_a->IsOuter(i)) {
+ continue;
+ }
+
+ BVH_TreeBase::BVH_VecNt bvh_a_min = bvh_a->MinPoint(i);
+ BVH_TreeBase::BVH_VecNt bvh_a_max = bvh_a->MaxPoint(i);
+ bvh_a_min[0] -= 1e-3;
+ bvh_a_min[1] -= 1e-3;
+ bvh_a_min[2] -= 1e-3;
+ bvh_a_max[0] += 1e-3;
+ bvh_a_max[1] += 1e-3;
+ bvh_a_max[2] += 1e-3;
+
+ BVH_Box box_a(bvh_a_min, bvh_a_max);
+
+ std::stack stack;
+ stack.push(0);
+
+ while ( ! stack.empty()) {
+ int j = stack.top();
+ stack.pop();
+
+ BVH_TreeBase::BVH_VecNt bvh_b_min = bvh_b->MinPoint(j);
+ BVH_TreeBase::BVH_VecNt bvh_b_max = bvh_b->MaxPoint(j);
+ bvh_b_min[0] -= extend + 1e-3;
+ bvh_b_min[1] -= extend + 1e-3;
+ bvh_b_min[2] -= extend + 1e-3;
+ bvh_b_max[0] += extend + 1e-3;
+ bvh_b_max[1] += extend + 1e-3;
+ bvh_b_max[2] += extend + 1e-3;
+
+ if (box_a.IsOut(bvh_b_min, bvh_b_max)) {
+ continue;
+ }
+ if (bvh_b->IsOuter(j)) {
+ if (bvh_clashes.find(i) != bvh_clashes.end()) {
+ bvh_clashes[i].push_back(j);
+ } else {
+ bvh_clashes[i] = {j};
+ }
+ } else {
+ stack.push(bvh_b->Child<0>(j));
+ stack.push(bvh_b->Child<1>(j));
+ }
+ }
+ }
+ return bvh_clashes;
+ }
+
+ clash test_intersection(const T& tA, const T& tB, double tolerance, bool check_all = true) const {
+ // If there are verts of A inside shape B (protrusion):
+ // 1. For each vert, find the shortest distance to the closest face
+ // 2. Find the innermost vert (i.e. the vert that has the longest distance)
+ // Otherwise (piercing):
+ // 1. Intersect each edge with shape B
+ // 2. Find the longest distance between intersections
+
+ auto obb_b = obbs_.find(tB)->second;
+ obb_b.Enlarge(-tolerance);
+
+ // No need to search beyond the distance of the max protrusion.
+ const double max_protrusion = max_protrusions_.find(tB)->second;
+
+ // Collide BVH trees of shape A vs B
+ opencascade::handle> bvh_a = bvhs_.find(tA)->second;
+ opencascade::handle> bvh_b = bvhs_.find(tB)->second;
+
+ std::unordered_map> bvh_clashes = clash_bvh(bvh_a, bvh_b, max_protrusion);
+ if (bvh_clashes.empty()) {
+ return {-1, tA, tB, 0, {0, 0, 0}, {0, 0, 0}};
+ }
+
+ const std::vector>& tris_a = tris_.find(tA)->second;
+ const std::vector>& tris_b = tris_.find(tB)->second;
+ const std::vector& verts_a = verts_.find(tA)->second;
+ const std::vector& verts_b = verts_.find(tB)->second;
+ const std::vector& normals_a = normals_.find(tA)->second;
+ const std::vector& normals_b = normals_.find(tB)->second;
+
+ // ~10% faster?
+ std::unordered_set points_in_b_cache;
+ std::unordered_set points_not_in_b_cache;
+
+ double protrusion = -std::numeric_limits::infinity();
+ std::array protrusion_point;
+ std::array surface_point;
+
+ double pierce = -std::numeric_limits::infinity();
+ std::array pierce_point1;
+ std::array pierce_point2;
+
+ for (const auto& pair : bvh_clashes) {
+ const int bvh_a_i = pair.first;
+ const std::vector& bvh_b_is = pair.second;
+
+ for (int i=bvh_a->BegPrimitive(bvh_a_i); i<=bvh_a->EndPrimitive(bvh_a_i); ++i) {
+ const std::array& tri = tris_a[i];
+ std::vector points_in_b;
+
+ for (int v_id : tri) {
+ if (points_not_in_b_cache.find(v_id) != points_not_in_b_cache.end()) {
+ continue;
+ }
+
+ const gp_Pnt& v = verts_a[v_id];
+
+ if (points_in_b_cache.find(v_id) != points_in_b_cache.end()) {
+ points_in_b.push_back(v);
+ continue;
+ }
+
+ if (obb_b.IsOut(v)) {
+ points_not_in_b_cache.insert(v_id);
+ continue;
+ }
+
+ if (is_point_in_shape(v, bvh_b, tris_b, verts_b)
+ && is_point_in_shape(v, bvh_b, tris_b, verts_b, true)) {
+ points_in_b.push_back(v);
+ points_in_b_cache.insert(v_id);
+ } else {
+ points_not_in_b_cache.insert(v_id);
+ }
+ }
+
+ // If there are no points in b, this may be a "piercing" triangle.
+ if (points_in_b.empty()) {
+ gp_Vec v1_a_vec(verts_a[tri[0]].XYZ());
+ gp_Vec v2_a_vec(verts_a[tri[1]].XYZ());
+ gp_Vec v3_a_vec(verts_a[tri[2]].XYZ());
+
+ // Protrusions take priority over piercings. We only check for piercings if:
+ // - This is a piercing triangle (e.g. no points in b)
+ // - No protrusion was already found
+ // - We haven't yet found a piercing at the max protrusion limit
+ if (protrusion == -std::numeric_limits::infinity() && pierce != max_protrusion) {
+ std::array<
+ std::tuple, std::array>, 3
+ > pierce_results = {
+ pierce_shape(v1_a_vec, v2_a_vec, bvh_b, tris_b, verts_b, normals_b),
+ pierce_shape(v1_a_vec, v3_a_vec, bvh_b, tris_b, verts_b, normals_b),
+ pierce_shape(v2_a_vec, v3_a_vec, bvh_b, tris_b, verts_b, normals_b)
+ };
+
+ for (const auto& pr : pierce_results) {
+ auto& p_dist = std::get<0>(pr);
+ auto& p_min = std::get<1>(pr);
+ auto& p_max = std::get<2>(pr);
+ if (p_dist > tolerance && p_dist > pierce) {
+ // Piercings are capped at max_protrusion for intuitive results
+ pierce = std::min(p_dist, max_protrusion);
+ pierce_point1 = p_min;
+ pierce_point2 = p_max;
+ if ( ! check_all) {
+ return {1, tA, tB, pierce, pierce_point1, pierce_point2};
+ }
+ }
+ }
+ }
+
+ // Since there were no points in b, we don't need to check for protrusions.
+ continue;
+ }
+
+ const gp_Vec& normal_a = normals_a[i];
+ double v_protrusion = std::numeric_limits::infinity();
+ std::array v_protrusion_point;
+ std::array v_surface_point;
+
+ // Check for protrusions.
+ for (const auto& bvh_b_i : bvh_b_is) {
+ for (int j=bvh_b->BegPrimitive(bvh_b_i); j<=bvh_b->EndPrimitive(bvh_b_i); ++j) {
+ const std::array& tri = tris_b[j];
+ const gp_Vec& normal_b = normals_b[j];
+
+ tri_count_++;
+
+ // We're penetrating _into_ a shape, so don't
+ // compare distances to faces with roughly the
+ // same normal as the penetration.
+ if (normal_a.Dot(normal_b) >= 0.9f) {
+ continue;
+ }
+
+ gp_Vec ta(verts_b[tri[0]].XYZ());
+ gp_Vec tb(verts_b[tri[1]].XYZ());
+ gp_Vec tc(verts_b[tri[2]].XYZ());
+
+ for (const auto& v : points_in_b) {
+ gp_Vec ray_origin(v.XYZ());
+
+ /*
+ std::cout << "POINT IN B " << v.X() << " " << v.Y() << " " << v.Z() << std::endl;
+ std::cout << "dir-> " << normal_b.X() << " " << normal_b.Y() << " " << normal_b.Z() << std::endl;
+ std::cout << "->tri " << v1_b[0] << " " << v1_b[1] << " " << v1_b[2] << std::endl;
+ std::cout << "->tri " << v2_b[0] << " " << v2_b[1] << " " << v2_b[2] << std::endl;
+ std::cout << "->tri " << v3_b[0] << " " << v3_b[1] << " " << v3_b[2] << std::endl;
+ */
+
+ // Do (cheaper) line check.
+ double at, au, av;
+ if (intersectRayTriangle(ray_origin, normal_b, ta, tb, tc, at, au, av, false)) {
+ double current_v_protrusion = at;
+
+ // std::cout << "We got a current protrusion " << current_v_protrusion << std::endl;
+ if (current_v_protrusion < v_protrusion) {
+ double aw = 1.0f - au - av; // Barycentric coordinate for ta
+ gp_Vec point_on_b = aw * ta + au * tb + av * tc; // Intersection point
+ // std::cout << "New v_protrusion winner of " << current_v_protrusion << std::endl;
+ v_protrusion = current_v_protrusion;
+ v_protrusion_point = {v.X(), v.Y(), v.Z()};
+ v_surface_point = {point_on_b.X(), point_on_b.Y(), point_on_b.Z()};
+
+ if ( ! check_all && v_protrusion > tolerance) {
+ return {0, tA, tB, v_protrusion, v_protrusion_point, v_surface_point};
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (v_protrusion != std::numeric_limits::infinity()) {
+ if (v_protrusion > protrusion) {
+ // std::cout << "New actual protrusion winner of " << v_protrusion << std::endl;
+ protrusion = v_protrusion;
+ protrusion_point = v_protrusion_point;
+ surface_point = v_surface_point;
+ if (protrusion > (max_protrusion - 1e-3)) {
+ return {0, tA, tB, protrusion, protrusion_point, surface_point};
+ }
+ }
+ }
+ }
+ }
+
+ if (protrusion > tolerance) {
+ return {0, tA, tB, protrusion, protrusion_point, surface_point};
+ }
+
+ if (pierce > tolerance) {
+ return {1, tA, tB, pierce, pierce_point1, pierce_point2};
+ }
+
+ return {-1, tA, tB, 0, {0, 0, 0}, {0, 0, 0}};
+ }
+
+ clash test_collision(const T& tA, const T& tB, bool allow_touching) const {
+ // Collide BVH trees of shape A vs B
+ opencascade::handle> bvh_a = bvhs_.find(tA)->second;
+ opencascade::handle> bvh_b = bvhs_.find(tB)->second;
+
+ std::unordered_map> bvh_clashes = clash_bvh(bvh_a, bvh_b);
+ if (bvh_clashes.empty()) {
+ return {-1, tA, tB, 0, {0, 0, 0}, {0, 0, 0}};
+ }
+
+ const std::vector>& tris_a = tris_.find(tA)->second;
+ const std::vector>& tris_b = tris_.find(tB)->second;
+ const std::vector& verts_a = verts_.find(tA)->second;
+ const std::vector& verts_b = verts_.find(tB)->second;
+ const std::vector& normals_a = normals_.find(tA)->second;
+ const std::vector& normals_b = normals_.find(tB)->second;
+
+ for (const auto& pair : bvh_clashes) {
+ const int bvh_a_i = pair.first;
+ const std::vector& bvh_b_is = pair.second;
+
+ for (int i=bvh_a->BegPrimitive(bvh_a_i); i<=bvh_a->EndPrimitive(bvh_a_i); ++i) {
+ const std::array& tri = tris_a[i];
+ const gp_Pnt& v1_a_pnt = verts_a[tri[0]];
+ const gp_Pnt& v2_a_pnt = verts_a[tri[1]];
+ const gp_Pnt& v3_a_pnt = verts_a[tri[2]];
+ const gp_Vec& normal_a = normals_a[i];
+
+ const gp_Vec v1_a_vec(v1_a_pnt.XYZ());
+ const gp_Vec v2_a_vec(v2_a_pnt.XYZ());
+ const gp_Vec v3_a_vec(v3_a_pnt.XYZ());
+
+ for (const auto& bvh_b_i : bvh_b_is) {
+ for (int j=bvh_b->BegPrimitive(bvh_b_i); j<=bvh_b->EndPrimitive(bvh_b_i); ++j) {
+ const std::array& tri = tris_b[j];
+ const gp_Pnt& v1_b_pnt = verts_b[tri[0]];
+ const gp_Pnt& v2_b_pnt = verts_b[tri[1]];
+ const gp_Pnt& v3_b_pnt = verts_b[tri[2]];
+ const gp_Vec& normal_b = normals_b[j];
+
+ tri_count_++;
+
+ const gp_Vec v1_b_vec(v1_b_pnt.XYZ());
+ const gp_Vec v2_b_vec(v2_b_pnt.XYZ());
+ const gp_Vec v3_b_vec(v3_b_pnt.XYZ());
+
+ // Allow a deviation of 0.25 degrees in coplanarity check
+ if (std::abs(normal_a.Dot(normal_b)) >= 0.99999f) {
+ continue;
+ }
+
+ gp_Vec int1, int2;
+ if (trianglesIntersect(v1_a_vec, v2_a_vec, v3_a_vec, v1_b_vec, v2_b_vec, v3_b_vec, int1, int2, ! allow_touching)) {
+ if (allow_touching) {
+ return {2, tA, tB, 0, {int1.X(), int1.Y(), int1.Z()}, {int2.X(), int2.Y(), int2.Z()}};
+ }
+
+ // A non-touching collision is defined as two triangles that:
+ // 1. Are not coplanar
+ // 2. The point of intersection is not along the edge of triangle A.
+ // 3. The point of intersection is not a vertex of triangle B.
+
+ if (
+ ! is_point_on_line(int1, v1_a_vec, v2_a_vec)
+ && ! is_point_on_line(int1, v1_a_vec, v3_a_vec)
+ && ! is_point_on_line(int1, v2_a_vec, v3_a_vec)
+ ) {
+ if (
+ (v1_b_vec - int1).Magnitude() > 1e-4
+ && (v2_b_vec - int1).Magnitude() > 1e-4
+ && (v3_b_vec - int1).Magnitude() > 1e-4
+ ) {
+ return {2, tA, tB, 0, {int1.X(), int1.Y(), int1.Z()}, {int2.X(), int2.Y(), int2.Z()}};
+ }
+ }
+
+ if (
+ ! is_point_on_line(int1, v1_b_vec, v2_b_vec)
+ && ! is_point_on_line(int1, v1_b_vec, v3_b_vec)
+ && ! is_point_on_line(int1, v2_b_vec, v3_b_vec)
+ ) {
+ if (
+ (v1_a_vec - int1).Magnitude() > 1e-4
+ && (v2_a_vec - int1).Magnitude() > 1e-4
+ && (v3_a_vec - int1).Magnitude() > 1e-4
+ ) {
+ return {2, tA, tB, 0, {int1.X(), int1.Y(), int1.Z()}, {int2.X(), int2.Y(), int2.Z()}};
+ }
+ }
+
+ if (
+ ! is_point_on_line(int2, v1_a_vec, v2_a_vec)
+ && ! is_point_on_line(int2, v1_a_vec, v3_a_vec)
+ && ! is_point_on_line(int2, v2_a_vec, v3_a_vec)
+ ) {
+ if (
+ (v1_b_vec - int2).Magnitude() > 1e-4
+ && (v2_b_vec - int2).Magnitude() > 1e-4
+ && (v3_b_vec - int2).Magnitude() > 1e-4
+ ) {
+ return {2, tA, tB, 0, {int2.X(), int2.Y(), int2.Z()}, {int1.X(), int1.Y(), int1.Z()}};
+ }
+ }
+
+ if (
+ ! is_point_on_line(int2, v1_b_vec, v2_b_vec)
+ && ! is_point_on_line(int2, v1_b_vec, v3_b_vec)
+ && ! is_point_on_line(int2, v2_b_vec, v3_b_vec)
+ ) {
+ if (
+ (v1_a_vec - int2).Magnitude() > 1e-4
+ && (v2_a_vec - int2).Magnitude() > 1e-4
+ && (v3_a_vec - int2).Magnitude() > 1e-4
+ ) {
+ return {2, tA, tB, 0, {int2.X(), int2.Y(), int2.Z()}, {int1.X(), int1.Y(), int1.Z()}};
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return {-1, tA, tB, 0, {0, 0, 0}, {0, 0, 0}};
+ }
+
+ clash test_clearance(const T& tA, const T& tB, double clearance, bool check_all) const {
+ // Collide BVH trees of shape A vs B
+ opencascade::handle> bvh_a = bvhs_.find(tA)->second;
+ opencascade::handle> bvh_b = bvhs_.find(tB)->second;
+
+ std::unordered_map> bvh_clashes = clash_bvh(bvh_a, bvh_b, clearance);
+ if (bvh_clashes.empty()) {
+ return {-1, tA, tB, 0, {0, 0, 0}, {0, 0, 0}};
+ }
+
+ const std::vector>& tris_a = tris_.find(tA)->second;
+ const std::vector>& tris_b = tris_.find(tB)->second;
+ const std::vector& verts_a = verts_.find(tA)->second;
+ const std::vector& verts_b = verts_.find(tB)->second;
+
+ double min_clearance = std::numeric_limits::infinity();
+ std::array clearance_point1;
+ std::array clearance_point2;
+
+ for (const auto& pair : bvh_clashes) {
+ const int bvh_a_i = pair.first;
+ const std::vector& bvh_b_is = pair.second;
+
+ for (int i=bvh_a->BegPrimitive(bvh_a_i); i<=bvh_a->EndPrimitive(bvh_a_i); ++i) {
+ const std::array& tri = tris_a[i];
+ const gp_Pnt& v1_a_pnt = verts_a[tri[0]];
+ const gp_Pnt& v2_a_pnt = verts_a[tri[1]];
+ const gp_Pnt& v3_a_pnt = verts_a[tri[2]];
+
+ const gp_Vec v1_a_vec(v1_a_pnt.XYZ());
+ const gp_Vec v2_a_vec(v2_a_pnt.XYZ());
+ const gp_Vec v3_a_vec(v3_a_pnt.XYZ());
+
+ const std::array p = {v1_a_vec, v2_a_vec, v3_a_vec};
+
+ for (const auto& bvh_b_i : bvh_b_is) {
+ for (int j=bvh_b->BegPrimitive(bvh_b_i); j<=bvh_b->EndPrimitive(bvh_b_i); ++j) {
+ const std::array& tri = tris_b[j];
+ const gp_Pnt& v1_b_pnt = verts_b[tri[0]];
+ const gp_Pnt& v2_b_pnt = verts_b[tri[1]];
+ const gp_Pnt& v3_b_pnt = verts_b[tri[2]];
+
+ tri_count_++;
+
+ const gp_Vec v1_b_vec(v1_b_pnt.XYZ());
+ const gp_Vec v2_b_vec(v2_b_pnt.XYZ());
+ const gp_Vec v3_b_vec(v3_b_pnt.XYZ());
+
+ const std::array q = {v1_b_vec, v2_b_vec, v3_b_vec};
+
+ gp_Vec cp;
+ gp_Vec cq;
+
+ // https://stackoverflow.com/questions/53602907/algorithm-to-find-minimum-distance-between-two-triangles
+ distanceTriangleTriangleSquared(cp, cq, p, q);
+
+ double distance = (cq - cp).Magnitude();
+ if (distance < clearance && distance < min_clearance) {
+ min_clearance = distance;
+ clearance_point1 = {cp.X(), cp.Y(), cp.Z()};
+ clearance_point2 = {cq.X(), cq.Y(), cq.Z()};
+ if ( ! check_all || min_clearance < 1e-4) {
+ return {3, tA, tB, min_clearance, clearance_point1, clearance_point2};
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (min_clearance < clearance) {
+ return {3, tA, tB, min_clearance, clearance_point1, clearance_point2};
+
+ }
+
+ return {-1, tA, tB, 0, {0, 0, 0}, {0, 0, 0}};
+ }
bool test(const TopoDS_Shape& A, const TopoDS_Shape& B, bool completely_within, double extend) const {
if (extend > 0.) {
@@ -143,6 +911,7 @@ namespace IfcGeom {
// @todo this is ugly, embed this in the return type
mutable std::vector distances_;
mutable std::vector protrusion_distances_;
+ mutable long long tri_count_ = 0;
public:
@@ -211,6 +980,333 @@ namespace IfcGeom {
}
}
+ std::unique_ptr> build_box_set(const std::vector& elements) const {
+ double x, y, z, X, Y, Z;
+ std::unique_ptr> box_set = std::make_unique>();
+ for (int i=0; isecond;
+ aabb.Get(x, y, z, X, Y, Z);
+ const BVH_Box::BVH_VecNt min(x, y, z);
+ const BVH_Box::BVH_VecNt max(X, Y, Z);
+ BVH_Box bvh_box(min, max);
+ box_set->Add(i, bvh_box);
+ }
+ return box_set;
+ }
+
+ struct clash_task {
+ T a, b;
+ };
+
+ std::vector> allocate_tasks_to_threads(
+ std::vector& task_queue) const {
+ int num_threads = std::thread::hardware_concurrency();
+ std::vector> threaded_tasks(num_threads);
+
+ size_t tasks_per_thread = task_queue.size() / num_threads;
+ for (int i = 0; i < num_threads; ++i) {
+ auto startIter = std::next(task_queue.begin(), i * tasks_per_thread);
+ auto endIter = (i == num_threads - 1) ? task_queue.end() : std::next(startIter, tasks_per_thread);
+ threaded_tasks[i] = std::vector(startIter, endIter);
+ }
+ return threaded_tasks;
+ }
+
+ std::vector clash_intersection_many(
+ const std::vector& set_a, const std::vector& set_b,
+ double tolerance = 0.002, bool check_all = true
+ ) const {
+ std::vector task_queue;
+ std::vector results;
+
+ std::unique_ptr> box_set_a = build_box_set(set_a);
+ std::unique_ptr> box_set_b = build_box_set(set_b);
+
+ const opencascade::handle>& bvh_a = box_set_a->BVH();
+ const opencascade::handle>& bvh_b = box_set_b->BVH();
+
+ std::unordered_map> bvh_clashes = clash_bvh(bvh_a, bvh_b, 0.0);
+
+ if (bvh_clashes.empty()) {
+ return results;
+ }
+
+ std::map> tested_pairs;
+
+ for (const auto& pair : bvh_clashes) {
+ const int bvh_a_i = pair.first;
+ const std::vector& bvh_b_is = pair.second;
+ for (int i=bvh_a->BegPrimitive(bvh_a_i); i<=bvh_a->EndPrimitive(bvh_a_i); ++i) {
+ const T& t_a = set_a[box_set_a->Element(i)];
+ for (const auto& bvh_b_i : bvh_b_is) {
+ for (int j=bvh_b->BegPrimitive(bvh_b_i); j<=bvh_b->EndPrimitive(bvh_b_i); ++j) {
+ const T& t_b = set_b[box_set_b->Element(j)];
+ if (t_a == t_b) {
+ continue;
+ }
+
+ if (tested_pairs[t_a].insert(t_b).second) {
+ tested_pairs[t_b].insert(t_a).second;
+ } else {
+ continue;
+ }
+
+ task_queue.emplace_back(clash_task{t_a, t_b});
+ }
+ }
+ }
+ }
+
+ std::vector> threaded_tasks = allocate_tasks_to_threads(task_queue);
+
+ std::vector threads;
+ std::mutex results_mutex;
+
+ for (auto& tasks : threaded_tasks) {
+ threads.emplace_back([this, &tasks, &results, &results_mutex, tolerance, check_all] {
+ std::vector thread_results;
+ for (auto& task : tasks) {
+ const auto& obb_a = obbs_.find(task.a)->second;
+ auto obb_b = obbs_.find(task.b)->second;
+ obb_b.Enlarge(-tolerance);
+ if (obb_a.IsOut(obb_b)) {
+ continue;
+ }
+
+ bool has_clash = false;
+ bool is_manifold = false;
+ clash result;
+
+ if (is_manifold_.find(task.b)->second) {
+ is_manifold = true;
+ clash intersection = test_intersection(task.a, task.b, tolerance, check_all);
+ if (intersection.clash_type != -1) {
+ has_clash = true;
+ result = intersection;
+ if ( ! check_all) {
+ thread_results.push_back(result);
+ continue;
+ }
+ }
+ }
+
+ if (is_manifold_.find(task.a)->second) {
+ is_manifold = true;
+ clash intersection = test_intersection(task.b, task.a, tolerance, check_all);
+ if (intersection.clash_type != -1) {
+ // Replace the clash result if any of these criteria apply:
+ // - We don't have a clash yet
+ // - Our previous clash is piercing, and our new one is a protrusion
+ // - We have the same clash type, but our clash is more severe
+ if (
+ ! has_clash
+ || (result.clash_type == 1 && intersection.clash_type == 0)
+ || (
+ result.clash_type == intersection.clash_type
+ && intersection.distance > result.distance
+ )
+ ) {
+ has_clash = true;
+ result = intersection;
+ }
+ }
+ }
+
+ if ( ! is_manifold) {
+ clash collision = test_collision(task.a, task.b, false);
+ if (collision.clash_type != -1) {
+ has_clash = true;
+ result = collision;
+ }
+ }
+
+ if (has_clash) {
+ thread_results.push_back(result);
+ }
+ }
+ {
+ std::lock_guard lock(results_mutex);
+ results.insert(results.end(), thread_results.begin(), thread_results.end());
+ }
+ });
+ }
+
+ for (auto& thread : threads) {
+ if (thread.joinable()) {
+ thread.join();
+ }
+ }
+
+ return results;
+ }
+
+ std::vector clash_collision_many(
+ const std::vector& set_a, const std::vector& set_b, bool allow_touching = false
+ ) const {
+ std::vector task_queue;
+ std::vector results;
+
+ std::unique_ptr> box_set_a = build_box_set(set_a);
+ std::unique_ptr> box_set_b = build_box_set(set_b);
+
+ const opencascade::handle>& bvh_a = box_set_a->BVH();
+ const opencascade::handle>& bvh_b = box_set_b->BVH();
+
+ std::unordered_map> bvh_clashes = clash_bvh(bvh_a, bvh_b, 0.0);
+
+ if (bvh_clashes.empty()) {
+ return results;
+ }
+
+ std::map> tested_pairs;
+
+ for (const auto& pair : bvh_clashes) {
+ const int bvh_a_i = pair.first;
+ const std::vector& bvh_b_is = pair.second;
+ for (int i=bvh_a->BegPrimitive(bvh_a_i); i<=bvh_a->EndPrimitive(bvh_a_i); ++i) {
+ const T& t_a = set_a[box_set_a->Element(i)];
+ for (const auto& bvh_b_i : bvh_b_is) {
+ for (int j=bvh_b->BegPrimitive(bvh_b_i); j<=bvh_b->EndPrimitive(bvh_b_i); ++j) {
+ const T& t_b = set_b[box_set_b->Element(j)];
+ if (t_a == t_b) {
+ continue;
+ }
+
+ if (tested_pairs[t_a].insert(t_b).second) {
+ tested_pairs[t_b].insert(t_a).second;
+ } else {
+ continue;
+ }
+
+ task_queue.emplace_back(clash_task{t_a, t_b});
+ }
+ }
+ }
+ }
+
+ std::vector> threaded_tasks = allocate_tasks_to_threads(task_queue);
+
+ std::vector threads;
+ std::mutex results_mutex;
+
+ for (auto& tasks : threaded_tasks) {
+ threads.emplace_back([this, &tasks, &results, &results_mutex, allow_touching] {
+ std::vector thread_results;
+ for (auto& task : tasks) {
+ const auto& obb_a = obbs_.find(task.a)->second;
+ auto obb_b = obbs_.find(task.b)->second;
+ obb_b.Enlarge(-0.001);
+ if (obb_a.IsOut(obb_b)) {
+ continue;
+ }
+
+ clash result = test_collision(task.a, task.b, allow_touching);
+ if (result.clash_type != -1) {
+ thread_results.push_back(result);
+ }
+ }
+ {
+ std::lock_guard lock(results_mutex);
+ results.insert(results.end(), thread_results.begin(), thread_results.end());
+ }
+ });
+ }
+
+ for (auto& thread : threads) {
+ if (thread.joinable()) {
+ thread.join();
+ }
+ }
+
+ return results;
+ }
+
+ std::vector clash_clearance_many(
+ const std::vector& set_a, const std::vector& set_b,
+ double clearance = 0.05, bool check_all = false
+ ) const {
+ std::vector task_queue;
+ std::vector results;
+
+ std::unique_ptr> box_set_a = build_box_set(set_a);
+ std::unique_ptr> box_set_b = build_box_set(set_b);
+
+ const opencascade::handle>& bvh_a = box_set_a->BVH();
+ const opencascade::handle>& bvh_b = box_set_b->BVH();
+
+ std::unordered_map> bvh_clashes = clash_bvh(bvh_a, bvh_b, clearance);
+
+ if (bvh_clashes.empty()) {
+ return results;
+ }
+
+ std::map> tested_pairs;
+
+ for (const auto& pair : bvh_clashes) {
+ const int bvh_a_i = pair.first;
+ const std::vector& bvh_b_is = pair.second;
+ for (int i=bvh_a->BegPrimitive(bvh_a_i); i<=bvh_a->EndPrimitive(bvh_a_i); ++i) {
+ const T& t_a = set_a[box_set_a->Element(i)];
+ for (const auto& bvh_b_i : bvh_b_is) {
+ for (int j=bvh_b->BegPrimitive(bvh_b_i); j<=bvh_b->EndPrimitive(bvh_b_i); ++j) {
+ const T& t_b = set_b[box_set_b->Element(j)];
+ if (t_a == t_b) {
+ continue;
+ }
+
+ if (tested_pairs[t_a].insert(t_b).second) {
+ tested_pairs[t_b].insert(t_a).second;
+ } else {
+ continue;
+ }
+
+ task_queue.emplace_back(clash_task{t_a, t_b});
+ }
+ }
+ }
+ }
+
+ std::vector> threaded_tasks = allocate_tasks_to_threads(task_queue);
+
+ std::vector threads;
+ std::mutex results_mutex;
+
+ for (auto& tasks : threaded_tasks) {
+ threads.emplace_back([this, &tasks, &results, &results_mutex, clearance, check_all] {
+ std::vector thread_results;
+ for (auto& task : tasks) {
+ const auto& obb_a = obbs_.find(task.a)->second;
+ auto obb_b = obbs_.find(task.b)->second;
+ obb_b.Enlarge(clearance);
+ if (obb_a.IsOut(obb_b)) {
+ continue;
+ }
+
+ clash result = test_clearance(task.a, task.b, clearance, check_all);
+ if (result.clash_type != -1) {
+ thread_results.push_back(result);
+ }
+ }
+ {
+ std::lock_guard lock(results_mutex);
+ results.insert(results.end(), thread_results.begin(), thread_results.end());
+ }
+ });
+ }
+
+ for (auto& thread : threads) {
+ if (thread.joinable()) {
+ thread.join();
+ }
+ }
+
+ return results;
+ }
+
std::vector select(const T& t, bool completely_within = false, double extend = 0.0) const {
distances_.clear();
protrusion_distances_.clear();
@@ -322,6 +1418,24 @@ namespace IfcGeom {
tree_t tree_;
map_t shapes_;
+ std::map aabbs_;
+ std::map obbs_;
+ std::map max_protrusions_;
+ std::map>> bvhs_;
+ std::unordered_map is_manifold_;
+ std::unordered_map>> tris_;
+ std::unordered_map> verts_;
+ std::unordered_map> normals_;
+
+ // Temporary structures for H5
+ std::vector triangulation_elements_;
+ std::map global_ids_;
+ std::map names_;
+ std::map> placements_;
+ std::map> local_verts_;
+ std::map> local_faces_;
+ std::map> local_materials_;
+ std::map> local_material_ids_;
bool enable_face_styles_ = false;
@@ -390,14 +1504,441 @@ namespace IfcGeom {
}
}
+ void write_h5() {
+ H5::H5File file("filename.h5", H5F_ACC_TRUNC);
+ H5::Group shapes = file.createGroup("/shapes");
+
+ std::set processed_geometry_ids;
+ std::vector element_shape_ids;
+ std::unordered_map geometry_id_to_shape_id;
+ int geometry_index = 0;
+
+ std::vector> matrices;
+ std::vector> colours;
+ std::vector names;
+ std::vector global_ids;
+
+ const float tolerance = 0.01f; // Tolerance value for comparison
+
+ for (const auto& elem : triangulation_elements_) {
+ const auto geometry_id = elem->geometry().id();
+
+ const auto& placement = placements_[elem->product()];
+ matrices.emplace_back(placement.begin(), placement.end());
+
+ names.push_back(names_[elem->product()]);
+ global_ids.push_back(global_ids_[elem->product()]);
+
+ if (processed_geometry_ids.find(geometry_id) != processed_geometry_ids.end()) {
+ element_shape_ids.push_back(geometry_id_to_shape_id[geometry_id]);
+ continue;
+ }
+
+ processed_geometry_ids.insert(geometry_id);
+ H5::Group group = shapes.createGroup(std::to_string(geometry_index));
+ geometry_id_to_shape_id[geometry_id] = geometry_index;
+ element_shape_ids.push_back(geometry_index);
+
+ geometry_index++;
+
+ const auto& faces = local_faces_[geometry_id];
+ const auto& verts = local_verts_[geometry_id];
+ const auto& materials = local_materials_[geometry_id];
+ const auto& material_ids = local_material_ids_[geometry_id];
+
+ std::vector verts_float(verts.size());
+ std::transform(verts.begin(), verts.end(), verts_float.begin(),
+ [](double val) { return static_cast(val); });
+
+ // Write faces
+ size_t total_verts = verts.size() / 3;
+ hsize_t faces_dims[1] = {faces.size()};
+ H5::DataSpace faces_dataspace(1, faces_dims);
+ H5::DSetCreatPropList faces_propList;
+ faces_propList.setChunk(1, faces_dims);
+ faces_propList.setDeflate(9);
+ if (total_verts < (1 << 8)) {
+ H5::DataType dtype = H5::PredType::NATIVE_UINT8;
+ std::vector faces_dtype(faces.begin(), faces.end());
+ H5::DataSet faces_dataset = group.createDataSet("faces", dtype, faces_dataspace, faces_propList);
+ faces_dataset.write(faces_dtype.data(), dtype);
+ } else if (total_verts < (1 << 16)) {
+ H5::DataType dtype = H5::PredType::NATIVE_UINT16;
+ std::vector faces_dtype(faces.begin(), faces.end());
+ H5::DataSet faces_dataset = group.createDataSet("faces", dtype, faces_dataspace, faces_propList);
+ faces_dataset.write(faces_dtype.data(), dtype);
+ } else {
+ H5::DataType dtype = H5::PredType::NATIVE_UINT32;
+ H5::DataSet faces_dataset = group.createDataSet("faces", dtype, faces_dataspace, faces_propList);
+ faces_dataset.write(faces.data(), dtype);
+ }
+
+ // Write verts
+ H5::DataType dtype = H5::PredType::NATIVE_FLOAT;
+ hsize_t dims[1] = {verts.size()};
+ H5::DataSpace dataspace(1, dims);
+ H5::DSetCreatPropList propList;
+ propList.setChunk(1, dims);
+ propList.setDeflate(9);
+ H5::DataSet dataset = group.createDataSet("verts", dtype, dataspace, propList);
+ dataset.write(verts_float.data(), H5::PredType::NATIVE_FLOAT);
+
+ // Write materials
+ std::vector material_keys;
+ for (const auto& material : materials) {
+ float alpha = 1.0;
+ if (material.hasTransparency() && material.transparency() > 0) {
+ alpha = 1.0 - material.transparency();
+ }
+
+ int i = 0;
+ bool is_existing_colour = false;
+ for (const auto& colour : colours) {
+ if (std::abs(colour[0] - static_cast(material.diffuse()[0])) < tolerance
+ && std::abs(colour[1] - static_cast(material.diffuse()[1])) < tolerance
+ && std::abs(colour[2] - static_cast(material.diffuse()[2])) < tolerance
+ && std::abs(colour[3] - alpha) < tolerance) {
+ is_existing_colour = true;
+ break;
+ }
+ i++;
+ }
+
+ if ( ! is_existing_colour) {
+ colours.push_back({
+ static_cast(material.diffuse()[0]),
+ static_cast(material.diffuse()[1]),
+ static_cast(material.diffuse()[2]),
+ alpha});
+ }
+ material_keys.push_back(static_cast(i));
+ }
+
+ size_t total_material_keys = material_keys.size();
+ if (total_material_keys) {
+ hsize_t dims[1] = {material_keys.size()};
+ H5::DataSpace dataspace(1, dims);
+ H5::DSetCreatPropList propList;
+ propList.setChunk(1, dims);
+ propList.setDeflate(9);
+ H5::DataType dtype = H5::PredType::NATIVE_UINT8;
+ H5::DataSet dataset = group.createDataSet("materials", dtype, dataspace, propList);
+ dataset.write(material_keys.data(), dtype);
+ }
+
+ if (total_material_keys > 1) {
+ hsize_t dims[1] = {material_ids.size()};
+ H5::DataSpace dataspace(1, dims);
+ H5::DSetCreatPropList propList;
+ propList.setChunk(1, dims);
+ propList.setDeflate(9);
+ H5::DataType dtype = H5::PredType::NATIVE_UINT8;
+ H5::DataSet dataset = group.createDataSet("material_ids", dtype, dataspace, propList);
+ std::vector data_dtype(material_ids.begin(), material_ids.end());
+ dataset.write(data_dtype.data(), dtype);
+ }
+ }
+
+ // Write GlobalIds
+ std::vector uuids_array;
+ for (const auto& id_str : global_ids) {
+ for (size_t i = 0; i < id_str.length(); i += 2) {
+ uuids_array.push_back(std::stoi(id_str.substr(i, 2), 0, 16));
+ }
+ }
+
+ hsize_t global_ids_dims[2] = {global_ids.size(), 16}; // 16 bytes per UUID
+ H5::DataSpace global_ids_dataspace(2, global_ids_dims);
+ H5::DataSet global_ids_dataset = file.createDataSet("element_global_ids", H5::PredType::NATIVE_UINT8, global_ids_dataspace);
+ global_ids_dataset.write(uuids_array.data(), H5::PredType::NATIVE_UINT8);
+
+ // Write names
+ H5::StrType strType(H5::PredType::C_S1, H5T_VARIABLE);
+ hsize_t names_dims[1] = {names.size()};
+ H5::DataSpace names_dataspace(1, names_dims);
+ H5::DataSet names_dataset = file.createDataSet("element_names", strType, names_dataspace);
+ std::vector cstr_names;
+ for (const auto& name : names) {
+ cstr_names.push_back(name.c_str());
+ }
+ names_dataset.write(&cstr_names[0], strType);
+
+ // Write matrices
+ std::vector flat_matrices;
+ for (const auto& matrix : matrices) {
+ flat_matrices.insert(flat_matrices.end(), matrix.begin(), matrix.end());
+ }
+ hsize_t dims[2] = {matrices.size(), matrices[0].size()};
+ H5::DataSpace dataspace(2, dims);
+ H5::DSetCreatPropList propList;
+ propList.setChunk(2, dims);
+ propList.setDeflate(9);
+ H5::DataSet dataset = file.createDataSet("element_matrices", H5::PredType::NATIVE_FLOAT, dataspace, propList);
+ dataset.write(flat_matrices.data(), H5::PredType::NATIVE_FLOAT);
+
+ // Write element_shape_ids
+ hsize_t element_shape_ids_dims[1] = {element_shape_ids.size()};
+ H5::DataSpace element_shape_ids_dataspace(1, element_shape_ids_dims);
+ H5::DSetCreatPropList element_shape_ids_propList;
+ element_shape_ids_propList.setChunk(1, element_shape_ids_dims);
+ element_shape_ids_propList.setDeflate(9);
+ if (geometry_index < (1 << 8)) {
+ H5::DataType dtype = H5::PredType::NATIVE_UINT8;
+ std::vector element_shape_ids_dtype(element_shape_ids.begin(), element_shape_ids.end());
+ H5::DataSet element_shape_ids_dataset = file.createDataSet("element_shape_ids", dtype, element_shape_ids_dataspace, element_shape_ids_propList);
+ element_shape_ids_dataset.write(element_shape_ids_dtype.data(), dtype);
+ } else if (geometry_index < (1 << 16)) {
+ H5::DataType dtype = H5::PredType::NATIVE_UINT16;
+ std::vector element_shape_ids_dtype(element_shape_ids.begin(), element_shape_ids.end());
+ H5::DataSet element_shape_ids_dataset = file.createDataSet("element_shape_ids", dtype, element_shape_ids_dataspace, element_shape_ids_propList);
+ element_shape_ids_dataset.write(element_shape_ids_dtype.data(), dtype);
+ } else if (geometry_index < (1UL << 32)) {
+ H5::DataType dtype = H5::PredType::NATIVE_UINT32;
+ std::vector element_shape_ids_dtype(element_shape_ids.begin(), element_shape_ids.end());
+ H5::DataSet element_shape_ids_dataset = file.createDataSet("element_shape_ids", dtype, element_shape_ids_dataspace, element_shape_ids_propList);
+ element_shape_ids_dataset.write(element_shape_ids_dtype.data(), dtype);
+ }
+
+ // Write colours
+ if (colours.size()) {
+ std::vector flat_colours;
+ for (const auto& colour : colours) {
+ flat_colours.insert(flat_colours.end(), colour.begin(), colour.end());
+ }
+ hsize_t colours_dims[2] = {colours.size(), colours[0].size()};
+ H5::DataSpace colours_dataspace(2, colours_dims);
+ H5::DSetCreatPropList colours_propList;
+ colours_propList.setChunk(2, colours_dims);
+ colours_propList.setDeflate(9);
+ H5::DataSet colours_dataset = file.createDataSet("materials", H5::PredType::NATIVE_FLOAT, colours_dataspace, colours_propList);
+ colours_dataset.write(flat_colours.data(), H5::PredType::NATIVE_FLOAT);
+ }
+ }
+
+ template
+ void apply_matrix_to_flat_verts(const std::vector& flat_list, const std::vector& matrix, std::vector& result) {
+ result.clear();
+ result.reserve(flat_list.size());
+
+ for (size_t i = 0; i < flat_list.size(); i += 3) {
+ float x = flat_list[i];
+ float y = flat_list[i + 1];
+ float z = flat_list[i + 2];
+ result.push_back(x * matrix[0] + y * matrix[3] + z * matrix[6] + matrix[9]);
+ result.push_back(x * matrix[1] + y * matrix[4] + z * matrix[7] + matrix[10]);
+ result.push_back(x * matrix[2] + y * matrix[5] + z * matrix[8] + matrix[11]);
+ }
+ }
+
+ std::string uint8_to_b64(const std::vector& uuids_array) {
+ std::string hex_str;
+ for (auto byte : uuids_array) {
+ // Convert each byte to a two-digit hexadecimal string and append it to the result
+ char hex[3]; // Two characters for the hex value and one for the null terminator
+ snprintf(hex, sizeof(hex), "%02x", byte);
+ hex_str.append(hex);
+ }
+ return hex_str;
+ }
+
+ static bool is_manifold(const std::vector& fs) {
+ // @nb this assumes geometry is processed with the WELD_VERTICES setting
+ std::unordered_set, boost::hash>> dict;
+ for (size_t i = 0; i < fs.size(); i += 3) {
+ for (size_t j = 0; j < 3; ++j) {
+ auto k = (j + 1) % 3;
+ auto it = dict.find({ fs[i + j], fs[i + k] });
+ if (it != dict.end()) {
+ dict.erase(it);
+ } else {
+ dict.insert({ fs[i + k], fs[i + j] });
+ }
+ }
+ }
+ return dict.empty();
+ }
+
+ void add_element(IfcGeom::TriangulationElement* elem) {
+
+ Bnd_Box aabb;
+ Bnd_OBB obb;
+
+ {
+ auto& trsf = elem->transformation().data();
+ auto& vs = elem->geometry().verts();
+ auto& fs = elem->geometry().faces();
+
+ std::vector vs_transformed;
+ vs_transformed.reserve(vs.size() / 3);
+ for (size_t i = 0; i < vs.size(); i += 3) {
+ gp_Pnt p(vs[i + 0], vs[i + 1], vs[i + 2]);
+ vs_transformed.push_back(p.Transformed(trsf));
+ aabb.Add(vs_transformed.back());
+ }
+
+ std::cout << "aabb: ";
+ aabb.DumpJson(std::cout);
+ std::cout << std::endl;
+
+ std::unordered_map, std::vector, boost::hash