mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-31 08:56:34 +00:00
Merge branch 'v0.7.0' into interface-to-interact-with-Brickschema
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
# This CITATION.cff file was generated with cffinit.
|
||||
# Visit https://bit.ly/cffinit to generate yours today!
|
||||
|
||||
cff-version: 1.2.0
|
||||
title: BlenderBIM Add-on
|
||||
message: >-
|
||||
If you use this software, please cite it using the
|
||||
metadata from this file.
|
||||
type: software
|
||||
authors:
|
||||
- name: "IfcOpenShell contributors"
|
||||
repository-code: >-
|
||||
https://github.com/IfcOpenShell/IfcOpenShell/tree/v0.7.0/src/blenderbim
|
||||
url: 'https://blenderbim.org/'
|
||||
repository-artifact: 'https://blenderbim.org/download.html'
|
||||
abstract: >-
|
||||
Add-on to Blender providing a graphical native IFC
|
||||
authoring platform
|
||||
keywords:
|
||||
- IFC
|
||||
- Blender
|
||||
- NativeIFC
|
||||
license: GPL-3.0-or-later
|
||||
commit: 1fb6dac
|
||||
version: v0.0.230506
|
||||
date-released: '2023-05-23'
|
||||
+46
-3
@@ -56,6 +56,7 @@ ifeq ($(PYVERSION), py37)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/linux-64/hpp-fcl-1.7.5-py37h5f1835d_0.tar.bz2
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/linux-64/eigenpy-2.6.5-py37h95e2c48_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/linux-64/boost-1.74.0-py37h0379df6_3.tar.bz2
|
||||
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/linux-64/qhull-2020.2-h4bd325d_2.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/30/c0/d0526314971fc661b083ab135747dc68446a3022686da8c16d25fcf6ef07/lxml-4.6.3-cp37-cp37m-manylinux2014_x86_64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/1d/a4/931d0780f31f3ea8c4f9ef6464a2825137c5241e6707a5fb03bef760a7eb/shapely-2.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/ed/d5/c2e84e1e36ab8ebea033921d5886a056c77e18bab5ab1051fcc22de2e8a2/Pillow-9.2.0-cp37-cp37m-manylinux_2_28_x86_64.whl
|
||||
@@ -64,6 +65,7 @@ ifeq ($(PYVERSION), py39)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/linux-64/hpp-fcl-1.7.5-py39hbcdfc36_0.tar.bz2
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/linux-64/eigenpy-2.6.5-py39h5aed9d1_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/linux-64/boost-1.74.0-py39h5472131_3.tar.bz2
|
||||
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/linux-64/qhull-2020.2-h4bd325d_2.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/19/d9/a69c6aff5673554df48120565a14a50eaa41d29ae03b02faa0b023666318/lxml-4.6.3-cp39-cp39-manylinux2014_x86_64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/2d/f2/8ec281d357e8bb7d08dc8d727f0e4c8ef3dae7d3fa75c69c8e452bb82d50/shapely-2.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/01/61/3ff85fb4bb596ce3d223c8fcf93c8df5c12bc8899dfb4fb3cb1c5b20dd5f/Pillow-9.2.0-cp39-cp39-manylinux_2_28_x86_64.whl
|
||||
@@ -72,6 +74,7 @@ ifeq ($(PYVERSION), py310)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.8.0/download/linux-64/hpp-fcl-1.8.0-py310hdaf7e41_1.tar.bz2
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.11/download/linux-64/eigenpy-2.6.11-py310hf3e5c9c_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/linux-64/boost-1.74.0-py310h7c3ba0c_5.tar.bz2
|
||||
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/linux-64/qhull-2020.2-h4bd325d_2.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/25/1e/19b46d8e8881fe0df2e20945d51919eeb1817836d62a90efa8506530e45c/lxml-4.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/a8/a5/403728b5614b28083f6424dfdefec5fcf58068495fb03bb08532671c642f/shapely-2.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/f6/51/320986ebd6d46a0e95c2240468ced73153b691ce07617078bcdf30c609ec/Pillow-9.2.0-cp310-cp310-manylinux_2_28_x86_64.whl
|
||||
@@ -86,6 +89,7 @@ ifeq ($(PYVERSION), py37)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/osx-64/hpp-fcl-1.7.5-py37h2d7f23a_0.tar.bz2
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/osx-64/eigenpy-2.6.5-py37h0695097_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/osx-64/boost-1.74.0-py37hd79e0ac_3.tar.bz2
|
||||
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/osx-64/qhull-2020.2-h940c156_2.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/1e/3e/f0abc15d5dac50939bccc589aae336d5ead4c72e7ad1039a2e0f3630ea92/lxml-4.6.3-cp37-cp37m-macosx_10_9_x86_64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/e6/7d/4923f27c340339e1c896c77cafc8ed672c8d381a025bbab6c6ddcba27e8f/shapely-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/88/49/c26fc3b5b0e82bdc9d8751d6b939da29327b0d98f7c3b95a575cbfed2743/Pillow-9.2.0-cp37-cp37m-macosx_10_10_x86_64.whl
|
||||
@@ -94,6 +98,7 @@ ifeq ($(PYVERSION), py39)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/osx-64/hpp-fcl-1.7.5-py39h1e32b98_0.tar.bz2
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/osx-64/eigenpy-2.6.5-py39h5405915_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/osx-64/boost-1.74.0-py39ha641261_3.tar.bz2
|
||||
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/osx-64/qhull-2020.2-h940c156_2.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/b8/74/a71f7ad72e8db54ce899efab84507b801660750cbbfa6a39e6717557d36a/lxml-4.6.3-cp39-cp39-macosx_10_9_x86_64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/36/a4/7e542a209f862f967d7cb8e939eff155f4294a27d17e16441fb8bdd51a2c/shapely-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/88/7a/ddfe28b485b623361457d4783007c1f9ba83a87f93e7fec32f64793efb6c/Pillow-9.2.0-cp39-cp39-macosx_10_10_x86_64.whl
|
||||
@@ -102,6 +107,7 @@ ifeq ($(PYVERSION), py310)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.8.0/download/osx-64/hpp-fcl-1.8.0-py310h651ac30_1.tar.bz2
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.11/download/osx-64/eigenpy-2.6.11-py310hc03097c_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/osx-64/boost-1.74.0-py310h509978a_5.tar.bz2
|
||||
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/osx-64/qhull-2020.2-h940c156_2.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/a1/44/17b7dac7a18807d30e2fe10c3328c152808f5464565e230bfd0e77f178c6/lxml-4.8.0-cp310-cp310-macosx_10_15_x86_64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/1f/2a/dc3353c2431cf53e8d04bb8fba27e584410ca3435c9c85f76d71bf0c0e80/shapely-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/d8/60/b13c00d403f34110e96c1b5c0afa73ce461efe3fe960c3a7e3e7fe190d82/Pillow-9.2.0-cp310-cp310-macosx_10_10_x86_64.whl
|
||||
@@ -117,6 +123,7 @@ ifeq ($(PYVERSION), py37)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/osx-64/hpp-fcl-1.7.5-py37h2d7f23a_0.tar.bz2
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/osx-64/eigenpy-2.6.5-py37h0695097_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/osx-64/boost-1.74.0-py37hd79e0ac_3.tar.bz2
|
||||
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/osx-arm64/qhull-2020.2-hc021e02_2.tar.bz2
|
||||
LXML_URL:=https://anaconda.org/conda-forge/lxml/4.9.1/download/osx-64/lxml-4.9.1-py37h994c40b_0.tar.bz2
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/e6/7d/4923f27c340339e1c896c77cafc8ed672c8d381a025bbab6c6ddcba27e8f/shapely-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/aa/bc/21097cd891dd2fa02f2b3d767e02e883e026482e59d29975d1bc30024aa3/Pillow-9.2.0-cp39-cp39-macosx_11_0_arm64.whl
|
||||
@@ -125,6 +132,7 @@ ifeq ($(PYVERSION), py39)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/osx-arm64/hpp-fcl-1.7.5-py39ha69f3c1_2.tar.bz2
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.7/download/osx-arm64/eigenpy-2.6.7-py39ha69f3c1_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-arm64/boost-1.78.0-py39h2550fe3_0.tar.bz2
|
||||
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/osx-arm64/qhull-2020.2-hc021e02_2.tar.bz2
|
||||
LXML_URL:=https://anaconda.org/conda-forge/lxml/4.9.1/download/osx-arm64/lxml-4.9.1-py39h9eb174b_0.tar.bz2
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/ea/aa/45fbd031edf3149cb767d8b9f9db45d5faf0324d743c6b8fb0298cc022d0/shapely-2.0.1-cp39-cp39-macosx_11_0_arm64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/aa/bc/21097cd891dd2fa02f2b3d767e02e883e026482e59d29975d1bc30024aa3/Pillow-9.2.0-cp39-cp39-macosx_11_0_arm64.whl
|
||||
@@ -133,6 +141,7 @@ ifeq ($(PYVERSION), py310)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.8.0/download/osx-arm64/hpp-fcl-1.8.0-py310h5699539_2.tar.bz2
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.9/download/osx-arm64/eigenpy-2.6.9-py310habfc766_1.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-arm64/boost-1.78.0-py310h4566fd1_0.tar.bz2
|
||||
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/osx-arm64/qhull-2020.2-hc021e02_2.tar.bz2
|
||||
LXML_URL:=https://anaconda.org/conda-forge/lxml/4.9.1/download/osx-arm64/lxml-4.9.1-py310h02f21da_0.tar.bz2
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/ec/41/d59208743e737184e1b403e95a937aebb022b8459e99efbcd5208fc8be46/shapely-2.0.1-cp310-cp310-macosx_11_0_arm64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/0c/5f/117b653cad585f3aedfe0de996c292e67d4b020ed77f652e5a6c8c24f908/Pillow-9.2.0-cp310-cp310-macosx_11_0_arm64.whl
|
||||
@@ -206,9 +215,7 @@ endif
|
||||
# IfcOpenBot sometimes lags behind, so we hotfix the Python utilities
|
||||
cp -r dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/util/* dist/blenderbim/libs/site/packages/ifcopenshell/util/
|
||||
cp -r dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/api/* dist/blenderbim/libs/site/packages/ifcopenshell/api/
|
||||
cp dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/entity_instance.py dist/blenderbim/libs/site/packages/ifcopenshell/
|
||||
cp dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/file.py dist/blenderbim/libs/site/packages/ifcopenshell/
|
||||
cp dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/validate.py dist/blenderbim/libs/site/packages/ifcopenshell/
|
||||
cp dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/*.py dist/blenderbim/libs/site/packages/ifcopenshell/
|
||||
cp dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/express/rule_executor.py dist/blenderbim/libs/site/packages/ifcopenshell/express/
|
||||
# Provides bcf functionality
|
||||
cp -r dist/working/IfcOpenShell-0.7.0/src/bcf/src/bcf dist/blenderbim/libs/site/packages/
|
||||
@@ -425,6 +432,10 @@ ifeq ($(PLATFORM), macos)
|
||||
cp -r dist/working/lib/$(PYLIBDIR)/site-packages/hppfcl dist/blenderbim/libs/site/packages/
|
||||
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), macosm1)
|
||||
cp -r dist/working/lib/$(PYLIBDIR)/site-packages/hppfcl dist/blenderbim/libs/site/packages/
|
||||
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), win)
|
||||
cp -r dist/working/Lib/site-packages/hppfcl dist/blenderbim/libs/site/packages/
|
||||
cp -r dist/working/Library/bin/*.dll dist/blenderbim/libs/site/packages/hppfcl/
|
||||
@@ -441,11 +452,31 @@ endif
|
||||
ifeq ($(PLATFORM), macos)
|
||||
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), macosm1)
|
||||
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), win)
|
||||
cp -r dist/working/Library/bin/*.dll dist/blenderbim/libs/site/packages/hppfcl/
|
||||
endif
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by hpp-fcl except on Windows
|
||||
ifneq ($(PLATFORM), win)
|
||||
mkdir dist/working
|
||||
cd dist/working && wget $(QHULL_URL)
|
||||
cd dist/working && tar -xf qhull*
|
||||
ifeq ($(PLATFORM), linux)
|
||||
cp -r dist/working/lib/*.so* dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), macos)
|
||||
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), macosm1)
|
||||
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
rm -rf dist/working
|
||||
endif
|
||||
|
||||
# Required by hpp-fcl
|
||||
mkdir dist/working
|
||||
cd dist/working && wget $(EIGENPY_URL)
|
||||
@@ -456,6 +487,9 @@ endif
|
||||
ifeq ($(PLATFORM), macos)
|
||||
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), macosm1)
|
||||
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), win)
|
||||
cp -r dist/working/Library/bin/*.dll dist/blenderbim/libs/site/packages/hppfcl/
|
||||
endif
|
||||
@@ -471,6 +505,9 @@ endif
|
||||
ifeq ($(PLATFORM), macos)
|
||||
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), macosm1)
|
||||
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), win)
|
||||
# Uh, do nothing, apparently? No DLLs are shipped.
|
||||
endif
|
||||
@@ -486,6 +523,9 @@ endif
|
||||
ifeq ($(PLATFORM), macos)
|
||||
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), macosm1)
|
||||
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), win)
|
||||
cp -r dist/working/Library/bin/*.dll dist/blenderbim/libs/site/packages/hppfcl/
|
||||
endif
|
||||
@@ -501,6 +541,9 @@ endif
|
||||
ifeq ($(PLATFORM), macos)
|
||||
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), macosm1)
|
||||
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), win)
|
||||
cp -r dist/working/Library/bin/*.dll dist/blenderbim/libs/site/packages/hppfcl/
|
||||
endif
|
||||
|
||||
@@ -98,8 +98,8 @@ def update_bim_tool_props():
|
||||
if not obj:
|
||||
return
|
||||
mode = bpy.context.mode
|
||||
current_tool = bpy.context.workspace.tools.from_space_view3d_mode(mode).idname
|
||||
if current_tool != "bim.bim_tool":
|
||||
current_tool = bpy.context.workspace.tools.from_space_view3d_mode(mode)
|
||||
if not current_tool or current_tool.idname != "bim.bim_tool":
|
||||
return
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
@@ -168,6 +168,9 @@ def refresh_ui_data():
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
if isinstance(tool.Ifc.get(), ifcopenshell.sqlite):
|
||||
tool.Ifc.get().clear_cache()
|
||||
|
||||
|
||||
def purge_module_data():
|
||||
from blenderbim.bim import modules
|
||||
@@ -259,6 +262,12 @@ def get_application_version():
|
||||
)
|
||||
|
||||
|
||||
def viewport_shading_changed_callback(area):
|
||||
shading = area.spaces.active.shading.type
|
||||
if shading == "RENDERED":
|
||||
bpy.context.scene.BIMStylesProperties.active_style_type = "External"
|
||||
|
||||
|
||||
@persistent
|
||||
def setDefaultProperties(scene):
|
||||
global global_subscription_owner
|
||||
@@ -266,6 +275,21 @@ def setDefaultProperties(scene):
|
||||
bpy.msgbus.subscribe_rna(
|
||||
key=active_object_key, owner=global_subscription_owner, args=(), notify=active_object_callback
|
||||
)
|
||||
|
||||
# subscribe to changes in viewport shading mode
|
||||
# NOTE: couldn't find a way to make it work for new areas too
|
||||
# it starts working for them after blender restart though
|
||||
for screen in bpy.data.screens:
|
||||
for area in screen.areas:
|
||||
if area.type != "VIEW_3D":
|
||||
continue
|
||||
shading = area.spaces.active.shading
|
||||
key = shading.path_resolve("type", False)
|
||||
|
||||
bpy.msgbus.subscribe_rna(
|
||||
key=key, owner=global_subscription_owner, args=(area,), notify=viewport_shading_changed_callback
|
||||
)
|
||||
|
||||
ifcopenshell.api.owner.settings.get_user = lambda ifc: core_owner.get_user(tool.Owner)
|
||||
ifcopenshell.api.owner.settings.get_application = get_application
|
||||
AuthoringData.type_thumbnails = {}
|
||||
|
||||
@@ -273,6 +273,9 @@ def convert_property_group_from_si(property_group, skip_props=()):
|
||||
setattr(property_group, prop_name, prop_value)
|
||||
|
||||
|
||||
# TODO this should move into ifcopenshell.util
|
||||
|
||||
|
||||
class IfcHeaderExtractor:
|
||||
def __init__(self, filepath: str):
|
||||
self.filepath = filepath
|
||||
@@ -284,6 +287,8 @@ class IfcHeaderExtractor:
|
||||
return self.extract_ifc_spf(ifc_file)
|
||||
elif extension.lower() == "ifczip":
|
||||
return self.extract_ifc_zip()
|
||||
elif extension.lower() == "ifcsqlite":
|
||||
return {} # TODO
|
||||
|
||||
def extract_ifc_spf(self, ifc_file):
|
||||
# https://www.steptools.com/stds/step/IS_final_p21e3.html#clause-8
|
||||
|
||||
@@ -120,6 +120,8 @@ class IfcStore:
|
||||
|
||||
@staticmethod
|
||||
def load_file(path):
|
||||
if not os.path.isfile(path):
|
||||
return
|
||||
extension = path.split(".")[-1]
|
||||
if extension.lower() == "ifczip":
|
||||
with tempfile.TemporaryDirectory() as unzipped_path:
|
||||
@@ -130,7 +132,9 @@ class IfcStore:
|
||||
return
|
||||
elif extension.lower() == "ifcxml":
|
||||
IfcStore.file = ifcopenshell.file(ifcopenshell.ifcopenshell_wrapper.parse_ifcxml(path))
|
||||
elif extension.lower() == "ifc":
|
||||
elif bpy.context.scene.BIMProjectProperties.should_stream:
|
||||
IfcStore.file = ifcopenshell.open(path, should_stream=True)
|
||||
else:
|
||||
IfcStore.file = ifcopenshell.open(path)
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -187,7 +187,7 @@ class IfcImporter:
|
||||
self.settings_native.set(self.settings_native.INCLUDE_CURVES, True)
|
||||
self.settings_2d = ifcopenshell.geom.settings()
|
||||
self.settings_2d.set(self.settings_2d.INCLUDE_CURVES, True)
|
||||
self.settings_2d.set(self.settings.STRICT_TOLERANCE, True)
|
||||
self.settings_2d.set(self.settings_2d.STRICT_TOLERANCE, True)
|
||||
self.project = None
|
||||
self.has_existing_project = False
|
||||
self.collections = {}
|
||||
@@ -347,7 +347,8 @@ class IfcImporter:
|
||||
self.element_types = set(self.file.by_type("IfcTypeProduct"))
|
||||
|
||||
if self.ifc_import_settings.has_filter and self.ifc_import_settings.should_filter_spatial_elements:
|
||||
self.spatial_elements = self.get_spatial_elements_filtered_by_elements(self.elements)
|
||||
filtered_elements = self.elements | set(self.file.by_type("IfcGrid"))
|
||||
self.spatial_elements = self.get_spatial_elements_filtered_by_elements(filtered_elements)
|
||||
else:
|
||||
if self.file.schema == "IFC2X3":
|
||||
self.spatial_elements = set(self.file.by_type("IfcSpatialStructureElement"))
|
||||
@@ -361,11 +362,13 @@ class IfcImporter:
|
||||
while True:
|
||||
results.add(spatial_element)
|
||||
spatial_element = ifcopenshell.util.element.get_aggregate(spatial_element)
|
||||
if not spatial_element or spatial_element.is_a("IfcContext"):
|
||||
if not spatial_element or spatial_element.is_a() in ("IfcProject", "IfcProjectLibrary"):
|
||||
break
|
||||
return results
|
||||
|
||||
def parse_native_elements(self):
|
||||
if not self.ifc_import_settings.should_load_geometry:
|
||||
return
|
||||
for element in self.elements:
|
||||
if self.is_native(element):
|
||||
self.native_elements.add(element)
|
||||
@@ -410,19 +413,19 @@ class IfcImporter:
|
||||
return True
|
||||
|
||||
def is_native_swept_disk_solid(self, element, representations):
|
||||
# detect BBIM Railings to represent them with meshes and not curves
|
||||
if tool.Pset.get_element_pset(element, "BBIM_Railing"):
|
||||
return False
|
||||
|
||||
for representation in representations:
|
||||
items = representation["raw"].Items or [] # Be forgiving of invalid IFCs because Revit :(
|
||||
if len(items) == 1 and items[0].is_a("IfcSweptDiskSolid"):
|
||||
if tool.Pset.get_element_pset(element, "BBIM_Railing"):
|
||||
return False
|
||||
return True
|
||||
elif len(items) and ( # See #2508 why we accommodate for invalid IFCs here
|
||||
items[0].is_a("IfcSweptDiskSolid")
|
||||
and len({i.is_a() for i in items}) == 1
|
||||
and len({i.Radius for i in items}) == 1
|
||||
):
|
||||
if tool.Pset.get_element_pset(element, "BBIM_Railing"):
|
||||
return False
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -577,6 +580,8 @@ class IfcImporter:
|
||||
return result
|
||||
|
||||
def create_grids(self):
|
||||
if not self.ifc_import_settings.should_load_geometry:
|
||||
return
|
||||
grids = self.file.by_type("IfcGrid")
|
||||
for grid in grids:
|
||||
shape = None
|
||||
@@ -622,34 +627,37 @@ class IfcImporter:
|
||||
|
||||
def create_element_type(self, element):
|
||||
self.ifc_import_settings.logger.info("Creating object %s", element)
|
||||
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||
if not representation:
|
||||
representation = ifcopenshell.util.representation.get_representation(element, "Plan", "Annotation")
|
||||
if not representation:
|
||||
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Annotation")
|
||||
mesh = None
|
||||
if representation:
|
||||
mesh_name = "{}/{}".format(representation.ContextOfItems.id(), representation.id())
|
||||
mesh = self.meshes.get(mesh_name)
|
||||
if mesh is None:
|
||||
shape = None
|
||||
try:
|
||||
shape = ifcopenshell.geom.create_shape(self.settings, representation)
|
||||
except:
|
||||
if self.ifc_import_settings.should_load_geometry:
|
||||
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||
if not representation:
|
||||
representation = ifcopenshell.util.representation.get_representation(element, "Plan", "Annotation")
|
||||
if not representation:
|
||||
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Annotation")
|
||||
if representation:
|
||||
mesh_name = "{}/{}".format(representation.ContextOfItems.id(), representation.id())
|
||||
mesh = self.meshes.get(mesh_name)
|
||||
if mesh is None:
|
||||
shape = None
|
||||
try:
|
||||
shape = ifcopenshell.geom.create_shape(self.settings_2d, representation)
|
||||
shape = ifcopenshell.geom.create_shape(self.settings, representation)
|
||||
except:
|
||||
self.ifc_import_settings.logger.error("Failed to generate shape for %s", element)
|
||||
if shape:
|
||||
mesh = self.create_mesh(element, shape)
|
||||
tool.Loader.link_mesh(shape, mesh)
|
||||
self.meshes[mesh_name] = mesh
|
||||
try:
|
||||
shape = ifcopenshell.geom.create_shape(self.settings_2d, representation)
|
||||
except:
|
||||
self.ifc_import_settings.logger.error("Failed to generate shape for %s", element)
|
||||
if shape:
|
||||
mesh = self.create_mesh(element, shape)
|
||||
tool.Loader.link_mesh(shape, mesh)
|
||||
self.meshes[mesh_name] = mesh
|
||||
obj = bpy.data.objects.new(tool.Loader.get_name(element), mesh)
|
||||
self.link_element(element, obj)
|
||||
self.material_creator.create(element, obj, mesh)
|
||||
self.type_products[element.GlobalId] = obj
|
||||
|
||||
def create_native_elements(self):
|
||||
if not self.ifc_import_settings.should_load_geometry:
|
||||
return
|
||||
progress = 0
|
||||
checkpoint = time.time()
|
||||
total = len(self.native_elements)
|
||||
@@ -691,21 +699,76 @@ class IfcImporter:
|
||||
self.create_generic_elements(self.elements)
|
||||
|
||||
def create_generic_elements(self, elements):
|
||||
if isinstance(self.file, ifcopenshell.sqlite):
|
||||
return self.create_generic_sqlite_elements(elements)
|
||||
|
||||
# Based on my experience in viewing BIM models, representations are prioritised as follows:
|
||||
# 1. 3D Body, 2. 2D Body, 3. 2D Plans / annotations, 4. Point clouds, 5. No representation
|
||||
# If an element has a representation that doesn't follow 1, 2, 3, or 4, it will not show by default.
|
||||
# The user can load them later if they want to view them.
|
||||
products = self.create_products(elements)
|
||||
elements -= products
|
||||
products = self.create_products(elements, settings=self.settings_curve)
|
||||
elements -= products
|
||||
products = self.create_products(elements, settings=self.settings_2d)
|
||||
elements -= products
|
||||
products = self.create_pointclouds(elements)
|
||||
elements -= products
|
||||
for element in elements:
|
||||
if self.ifc_import_settings.should_load_geometry:
|
||||
products = self.create_products(elements)
|
||||
elements -= products
|
||||
products = self.create_products(elements, settings=self.settings_curve)
|
||||
elements -= products
|
||||
products = self.create_products(elements, settings=self.settings_2d)
|
||||
elements -= products
|
||||
products = self.create_pointclouds(elements)
|
||||
elements -= products
|
||||
|
||||
total = len(elements)
|
||||
for i, element in enumerate(elements):
|
||||
if i % 250 == 0:
|
||||
print("{} / {} elements processed ...".format(i, total))
|
||||
self.create_product(element)
|
||||
|
||||
def create_generic_sqlite_elements(self, elements):
|
||||
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}))
|
||||
mesh = bpy.data.meshes.new(mesh_name)
|
||||
|
||||
verts = geometry["verts"]
|
||||
mesh["has_cartesian_point_offset"] = False
|
||||
|
||||
if geometry["faces"]:
|
||||
num_vertices = len(verts) // 3
|
||||
total_faces = len(geometry["faces"])
|
||||
loop_start = range(0, total_faces, 3)
|
||||
num_loops = total_faces // 3
|
||||
loop_total = [3] * num_loops
|
||||
num_vertex_indices = len(geometry["faces"])
|
||||
|
||||
mesh.vertices.add(num_vertices)
|
||||
mesh.vertices.foreach_set("co", verts)
|
||||
mesh.loops.add(num_vertex_indices)
|
||||
mesh.loops.foreach_set("vertex_index", geometry["faces"])
|
||||
mesh.polygons.add(num_loops)
|
||||
mesh.polygons.foreach_set("loop_start", loop_start)
|
||||
mesh.polygons.foreach_set("loop_total", loop_total)
|
||||
mesh.update()
|
||||
else:
|
||||
e = geometry["edges"]
|
||||
v = verts
|
||||
vertices = [[v[i], v[i + 1], v[i + 2]] for i in range(0, len(v), 3)]
|
||||
edges = [[e[i], e[i + 1]] for i in range(0, len(e), 2)]
|
||||
mesh.from_pydata(vertices, edges, [])
|
||||
|
||||
mesh["ios_materials"] = geometry["materials"]
|
||||
mesh["ios_material_ids"] = geometry["material_ids"]
|
||||
self.meshes[mesh_name] = mesh
|
||||
|
||||
total = len(elements)
|
||||
for i, element in enumerate(elements):
|
||||
if i % 250 == 0:
|
||||
print("{} / {} elements processed ...".format(i, total))
|
||||
mesh = None
|
||||
geometry_id = self.geometry_cache["shapes"][element.id()]["geometry"]
|
||||
if geometry_id:
|
||||
mesh_name = tool.Loader.get_mesh_name(type("Geometry", (), {"id": geometry_id}))
|
||||
mesh = self.meshes.get(mesh_name)
|
||||
self.create_product(element, mesh=mesh)
|
||||
|
||||
def create_products(self, products, settings=None):
|
||||
if settings is None:
|
||||
settings = self.settings
|
||||
@@ -1342,14 +1405,11 @@ class IfcImporter:
|
||||
obj.BIMObjectProperties.collection = self.project["blender"]
|
||||
|
||||
def create_collections(self):
|
||||
if self.ifc_import_settings.collection_mode == "DECOMPOSITION":
|
||||
self.create_decomposition_collections()
|
||||
elif self.ifc_import_settings.collection_mode == "SPATIAL_DECOMPOSITION":
|
||||
self.create_spatial_decomposition_collections()
|
||||
|
||||
def create_decomposition_collections(self):
|
||||
self.create_spatial_decomposition_collections()
|
||||
self.create_aggregate_collections()
|
||||
if self.ifc_import_settings.collection_mode == "DECOMPOSITION":
|
||||
self.create_aggregate_collections()
|
||||
elif self.ifc_import_settings.collection_mode == "SPATIAL_DECOMPOSITION":
|
||||
pass
|
||||
|
||||
def create_spatial_decomposition_collections(self):
|
||||
for rel_aggregate in self.project["ifc"].IsDecomposedBy or []:
|
||||
@@ -1478,183 +1538,18 @@ class IfcImporter:
|
||||
if not blender_material:
|
||||
name = style.Name or str(style.id())
|
||||
blender_material = bpy.data.materials.new(name)
|
||||
blender_material.use_fake_user = True
|
||||
|
||||
self.link_element(style, blender_material)
|
||||
|
||||
blender_material.BIMMaterialProperties.ifc_style_id = style.id()
|
||||
self.material_creator.styles[style.id()] = blender_material
|
||||
|
||||
rendering_style = None
|
||||
texture_style = None
|
||||
|
||||
for surface_style in style.Styles:
|
||||
if surface_style.is_a() == "IfcSurfaceStyleShading":
|
||||
self.create_surface_style_shading(blender_material, surface_style)
|
||||
elif surface_style.is_a("IfcSurfaceStyleRendering"):
|
||||
rendering_style = surface_style
|
||||
self.create_surface_style_rendering(blender_material, surface_style)
|
||||
elif surface_style.is_a("IfcSurfaceStyleWithTextures"):
|
||||
texture_style = surface_style
|
||||
|
||||
if rendering_style and texture_style:
|
||||
self.create_surface_style_with_textures(blender_material, rendering_style, texture_style)
|
||||
|
||||
tool.Style.record_shading(blender_material)
|
||||
|
||||
def create_surface_style_shading(self, blender_material, surface_style):
|
||||
alpha = 1.0
|
||||
# Transparency was added in IFC4
|
||||
if hasattr(surface_style, "Transparency") and surface_style.Transparency:
|
||||
alpha = 1 - surface_style.Transparency
|
||||
blender_material.diffuse_color = (
|
||||
surface_style.SurfaceColour.Red,
|
||||
surface_style.SurfaceColour.Green,
|
||||
surface_style.SurfaceColour.Blue,
|
||||
alpha,
|
||||
)
|
||||
|
||||
def create_surface_style_rendering(self, blender_material, surface_style):
|
||||
self.create_surface_style_shading(blender_material, surface_style)
|
||||
if surface_style.ReflectanceMethod in ["PHYSICAL", "NOTDEFINED"]:
|
||||
blender_material.use_nodes = True
|
||||
bsdf = blender_material.node_tree.nodes["Principled BSDF"]
|
||||
if surface_style.DiffuseColour:
|
||||
if surface_style.DiffuseColour.is_a("IfcColourRgb"):
|
||||
bsdf.inputs["Base Color"].default_value = (
|
||||
surface_style.DiffuseColour.Red,
|
||||
surface_style.DiffuseColour.Green,
|
||||
surface_style.DiffuseColour.Blue,
|
||||
1,
|
||||
)
|
||||
elif surface_style.DiffuseColour.is_a("IfcNormalisedRatioMeasure"):
|
||||
bsdf.inputs["Base Color"].default_value = (
|
||||
surface_style.SurfaceColour.Red * surface_style.DiffuseColour.wrappedValue,
|
||||
surface_style.SurfaceColour.Green * surface_style.DiffuseColour.wrappedValue,
|
||||
surface_style.SurfaceColour.Blue * surface_style.DiffuseColour.wrappedValue,
|
||||
1,
|
||||
)
|
||||
if surface_style.SpecularColour and surface_style.SpecularColour.is_a("IfcNormalisedRatioMeasure"):
|
||||
bsdf.inputs["Metallic"].default_value = surface_style.SpecularColour.wrappedValue
|
||||
if surface_style.SpecularHighlight and surface_style.SpecularHighlight.is_a("IfcSpecularRoughness"):
|
||||
bsdf.inputs["Roughness"].default_value = surface_style.SpecularHighlight.wrappedValue
|
||||
if hasattr(surface_style, "Transparency") and surface_style.Transparency:
|
||||
bsdf.inputs["Alpha"].default_value = 1 - surface_style.Transparency
|
||||
blender_material.blend_method = "BLEND"
|
||||
elif surface_style.ReflectanceMethod == "FLAT":
|
||||
blender_material.use_nodes = True
|
||||
|
||||
output = {n.type: n for n in blender_material.node_tree.nodes}.get("OUTPUT_MATERIAL", None)
|
||||
bsdf = blender_material.node_tree.nodes["Principled BSDF"]
|
||||
|
||||
mix = blender_material.node_tree.nodes.new(type="ShaderNodeMixShader")
|
||||
mix.location = bsdf.location
|
||||
blender_material.node_tree.links.new(mix.outputs[0], output.inputs["Surface"])
|
||||
|
||||
blender_material.node_tree.nodes.remove(bsdf)
|
||||
|
||||
lightpath = blender_material.node_tree.nodes.new(type="ShaderNodeLightPath")
|
||||
lightpath.location = mix.location - mathutils.Vector((200, -200))
|
||||
blender_material.node_tree.links.new(lightpath.outputs[0], mix.inputs[0])
|
||||
|
||||
bsdf = blender_material.node_tree.nodes.new(type="ShaderNodeBsdfTransparent")
|
||||
bsdf.location = mix.location - mathutils.Vector((200, 0))
|
||||
blender_material.node_tree.links.new(bsdf.outputs[0], mix.inputs[1])
|
||||
|
||||
rgb = blender_material.node_tree.nodes.new(type="ShaderNodeRGB")
|
||||
rgb.location = mix.location - mathutils.Vector((200, 200))
|
||||
blender_material.node_tree.links.new(rgb.outputs[0], mix.inputs[2])
|
||||
|
||||
if surface_style.DiffuseColour and surface_style.DiffuseColour.is_a("IfcColourRgb"):
|
||||
rgb.outputs[0].default_value = (
|
||||
surface_style.DiffuseColour.Red,
|
||||
surface_style.DiffuseColour.Green,
|
||||
surface_style.DiffuseColour.Blue,
|
||||
1,
|
||||
)
|
||||
|
||||
def create_surface_style_with_textures(self, blender_material, rendering_style, texture_style):
|
||||
for texture in texture_style.Textures:
|
||||
mode = getattr(texture, "Mode", None)
|
||||
node = None
|
||||
|
||||
if texture.is_a("IfcImageTexture"):
|
||||
image_url = texture.URLReference
|
||||
if not os.path.abspath(texture.URLReference) and tool.Ifc.get_path():
|
||||
image_url = os.path.join(os.path.dirname(tool.Ifc.get_path()), texture.URLReference)
|
||||
|
||||
if rendering_style.ReflectanceMethod in ["PHYSICAL", "NOTDEFINED"]:
|
||||
bsdf = blender_material.node_tree.nodes["Principled BSDF"]
|
||||
if mode == "NORMAL":
|
||||
normalmap = blender_material.node_tree.nodes.new(type="ShaderNodeNormalMap")
|
||||
normalmap.location = bsdf.location - mathutils.Vector((200, 0))
|
||||
blender_material.node_tree.links.new(normalmap.outputs[0], bsdf.inputs["Normal"])
|
||||
|
||||
node = blender_material.node_tree.nodes.new(type="ShaderNodeTexImage")
|
||||
node.location = normalmap.location - mathutils.Vector((200, 0))
|
||||
image = bpy.data.images.load(image_url)
|
||||
image.colorspace_settings.name = "Non-Color"
|
||||
node.image = image
|
||||
blender_material.node_tree.links.new(node.outputs[0], normalmap.inputs["Color"])
|
||||
elif mode == "EMISSIVE":
|
||||
output = {n.type: n for n in blender_material.node_tree.nodes}.get("OUTPUT_MATERIAL", None)
|
||||
|
||||
add = blender_material.node_tree.nodes.new(type="ShaderNodeAddShader")
|
||||
add.location = bsdf.location + mathutils.Vector((200, 0))
|
||||
blender_material.node_tree.links.new(bsdf.outputs[0], add.inputs[1])
|
||||
blender_material.node_tree.links.new(add.outputs[0], output.inputs[0])
|
||||
|
||||
emission = blender_material.node_tree.nodes.new(type="ShaderNodeEmission")
|
||||
emission.location = add.location - mathutils.Vector((200, 0))
|
||||
blender_material.node_tree.links.new(emission.outputs[0], add.inputs[0])
|
||||
|
||||
node = blender_material.node_tree.nodes.new(type="ShaderNodeTexImage")
|
||||
node.location = emission.location - mathutils.Vector((200, 0))
|
||||
image = bpy.data.images.load(image_url)
|
||||
node.image = image
|
||||
blender_material.node_tree.links.new(node.outputs[0], emission.inputs[0])
|
||||
elif mode == "METALLICROUGHNESS":
|
||||
separate = blender_material.node_tree.nodes.new(type="ShaderNodeSeparateRGB")
|
||||
separate.location = bsdf.location - mathutils.Vector((200, 0))
|
||||
blender_material.node_tree.links.new(separate.outputs[1], bsdf.inputs["Roughness"])
|
||||
blender_material.node_tree.links.new(separate.outputs[2], bsdf.inputs["Metallic"])
|
||||
|
||||
node = blender_material.node_tree.nodes.new(type="ShaderNodeTexImage")
|
||||
node.location = separate.location - mathutils.Vector((200, 0))
|
||||
image = bpy.data.images.load(image_url)
|
||||
image.colorspace_settings.name = "Non-Color"
|
||||
node.image = image
|
||||
blender_material.node_tree.links.new(node.outputs[0], separate.inputs[0])
|
||||
elif mode == "OCCLUSION":
|
||||
# TODO work out how to implement glTF settings here
|
||||
# https://docs.blender.org/manual/en/dev/addons/import_export/scene_gltf2.html
|
||||
pass
|
||||
elif mode == "DIFFUSE":
|
||||
node = blender_material.node_tree.nodes.new(type="ShaderNodeTexImage")
|
||||
node.location = bsdf.location - mathutils.Vector((400, 0))
|
||||
image = bpy.data.images.load(image_url)
|
||||
node.image = image
|
||||
blender_material.node_tree.links.new(node.outputs[0], bsdf.inputs["Base Color"])
|
||||
blender_material.node_tree.links.new(node.outputs[1], bsdf.inputs["Alpha"])
|
||||
blender_material.blend_method = "BLEND"
|
||||
elif rendering_style.ReflectanceMethod == "FLAT":
|
||||
bsdf = blender_material.node_tree.nodes["Mix Shader"]
|
||||
if mode == "EMISSIVE":
|
||||
node = blender_material.node_tree.nodes.new(type="ShaderNodeTexImage")
|
||||
node.location = bsdf.location - mathutils.Vector((200, 0))
|
||||
image = bpy.data.images.load(image_url)
|
||||
node.image = image
|
||||
blender_material.node_tree.links.new(node.outputs[0], bsdf.inputs[2])
|
||||
|
||||
if node and getattr(texture, "IsMappedBy", None):
|
||||
coordinates = texture.IsMappedBy[0]
|
||||
coord = blender_material.node_tree.nodes.new(type="ShaderNodeTexCoord")
|
||||
coord.location = node.location - mathutils.Vector((200, 0))
|
||||
if coordinates.is_a("IfcTextureCoordinateGenerator") and coordinates.Mode == "COORD":
|
||||
blender_material.node_tree.links.new(coord.outputs["Generated"], node.inputs["Vector"])
|
||||
elif coordinates.is_a("IfcTextureCoordinateGenerator") and coordinates.Mode == "COORD-EYE":
|
||||
blender_material.node_tree.links.new(coord.outputs["Camera"], node.inputs["Vector"])
|
||||
else:
|
||||
blender_material.node_tree.links.new(coord.outputs["UV"], node.inputs["Vector"])
|
||||
style_elements = tool.Style.get_style_elements(blender_material)
|
||||
if tool.Style.has_blender_external_style(style_elements):
|
||||
blender_material.BIMStyleProperties.active_style_type = "External"
|
||||
else:
|
||||
blender_material.BIMStyleProperties.active_style_type = "Shading"
|
||||
|
||||
def place_objects_in_collections(self):
|
||||
for ifc_definition_id, obj in self.added_data.items():
|
||||
@@ -1729,7 +1624,10 @@ class IfcImporter:
|
||||
return rel.RelatingGroup
|
||||
|
||||
def get_element_matrix(self, element):
|
||||
result = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
|
||||
if isinstance(element, ifcopenshell.sqlite_entity):
|
||||
result = self.geometry_cache["shapes"][element.id()]["matrix"]
|
||||
else:
|
||||
result = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
|
||||
result[0][3] *= self.unit_scale
|
||||
result[1][3] *= self.unit_scale
|
||||
result[2][3] *= self.unit_scale
|
||||
@@ -1886,6 +1784,7 @@ class IfcImporter:
|
||||
mesh.polygons.add(num_loops)
|
||||
mesh.polygons.foreach_set("loop_start", loop_start)
|
||||
mesh.polygons.foreach_set("loop_total", loop_total)
|
||||
mesh.polygons.foreach_set("use_smooth", [0] * total_faces)
|
||||
mesh.update()
|
||||
else:
|
||||
e = geometry.edges
|
||||
@@ -1962,6 +1861,7 @@ class IfcImportSettings:
|
||||
self.should_use_cpu_multiprocessing = True
|
||||
self.merge_mode = None
|
||||
self.should_merge_materials_by_colour = False
|
||||
self.should_load_geometry = True
|
||||
self.should_use_native_meshes = False
|
||||
self.should_clean_mesh = True
|
||||
self.should_cache = True
|
||||
@@ -1991,6 +1891,7 @@ class IfcImportSettings:
|
||||
settings.should_use_cpu_multiprocessing = props.should_use_cpu_multiprocessing
|
||||
settings.merge_mode = props.merge_mode
|
||||
settings.should_merge_materials_by_colour = props.should_merge_materials_by_colour
|
||||
settings.should_load_geometry = props.should_load_geometry
|
||||
settings.should_use_native_meshes = props.should_use_native_meshes
|
||||
settings.should_clean_mesh = props.should_clean_mesh
|
||||
settings.should_cache = props.should_cache
|
||||
|
||||
@@ -35,6 +35,7 @@ from mathutils import Vector, Matrix
|
||||
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
|
||||
|
||||
|
||||
def get_boundaries_collection(blender_space):
|
||||
|
||||
@@ -163,7 +163,7 @@ class RemoveCostItem(bpy.types.Operator, tool.Ifc.Operator):
|
||||
cost_item: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.remove_cost_item(tool.Ifc, tool.Cost, cost_item=tool.Ifc.get().by_id(self.cost_item))
|
||||
core.remove_cost_item(tool.Ifc, tool.Cost, cost_item_id=self.cost_item)
|
||||
|
||||
|
||||
class EnableEditingCostItem(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -631,17 +631,19 @@ class ExportCostSchedules(bpy.types.Operator):
|
||||
bl_description = "Export a cost schedule to a CSV, XSLX OR ODS file"
|
||||
cost_schedule: bpy.props.IntProperty()
|
||||
format: bpy.props.EnumProperty("Format", items=(("CSV", "CSV", ""), ("XLSX", "XLSX", ""), ("ODS", "ODS", "")))
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def execute(self, context):
|
||||
cost_schedule = tool.Ifc.get().by_id(self.cost_schedule) if self.cost_schedule else None
|
||||
r = core.export_cost_schedules(tool.Cost, format=self.format, cost_schedule=cost_schedule)
|
||||
r = core.export_cost_schedules(tool.Cost, filepath=self.filepath, format=self.format, cost_schedule=cost_schedule)
|
||||
if isinstance(r, str):
|
||||
self.report({"ERROR"}, r)
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
wm = context.window_manager
|
||||
return wm.invoke_props_dialog(self)
|
||||
wm.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
def draw(self, context):
|
||||
self.layout.label(text="Choose a format")
|
||||
|
||||
@@ -150,14 +150,9 @@ class ExportIfcCsv(bpy.types.Operator):
|
||||
selector = ifcopenshell.util.selector.Selector()
|
||||
results = selector.parse(ifc_file, props.ifc_selector)
|
||||
ifc_csv = ifccsv.IfcCsv()
|
||||
ifc_csv.output = self.filepath
|
||||
ifc_csv.attributes = [a.name for a in props.csv_attributes]
|
||||
ifc_csv.selector = selector
|
||||
if props.csv_delimiter == "CUSTOM":
|
||||
ifc_csv.delimiter = props.csv_custom_delimiter
|
||||
else:
|
||||
ifc_csv.delimiter = props.csv_delimiter
|
||||
ifc_csv.export(ifc_file, results)
|
||||
attributes = [a.name for a in props.csv_attributes]
|
||||
sep = props.csv_custom_delimiter if props.csv_delimiter == "CUSTOM" else props.csv_delimiter
|
||||
ifc_csv.export(ifc_file, results, attributes, output=self.filepath, format=args.format, delimiter=sep)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -182,12 +177,8 @@ class ImportIfcCsv(bpy.types.Operator):
|
||||
else:
|
||||
ifc_file = ifcopenshell.open(props.csv_ifc_file)
|
||||
ifc_csv = ifccsv.IfcCsv()
|
||||
ifc_csv.output = self.filepath
|
||||
if props.csv_delimiter == "CUSTOM":
|
||||
ifc_csv.delimiter = props.csv_custom_delimiter
|
||||
else:
|
||||
ifc_csv.delimiter = props.csv_delimiter
|
||||
ifc_csv.Import(ifc_file)
|
||||
sep = props.csv_custom_delimiter if props.csv_delimiter == "CUSTOM" else props.csv_delimiter
|
||||
ifc_csv.Import(ifc_file, self.filepath, delimiter=sep)
|
||||
if not props.should_load_from_memory:
|
||||
ifc_file.write(props.csv_ifc_file)
|
||||
purge_module_data()
|
||||
|
||||
@@ -45,7 +45,7 @@ class Annotator:
|
||||
font.name = "OpenGost Type B TT"
|
||||
obj.data.font = font
|
||||
obj.data.BIMTextProperties.font_size = "2.5"
|
||||
collection = bpy.context.scene.camera.users_collection[0]
|
||||
collection = bpy.context.scene.camera.BIMObjectProperties.collection
|
||||
collection.objects.link(obj)
|
||||
Annotator.resize_text(obj)
|
||||
return obj
|
||||
@@ -53,9 +53,10 @@ class Annotator:
|
||||
@staticmethod
|
||||
def resize_text(text_obj):
|
||||
camera = None
|
||||
for obj in text_obj.users_collection[0].objects:
|
||||
if isinstance(obj.data, bpy.types.Camera):
|
||||
camera = obj
|
||||
group = tool.Drawing.get_drawing_group(tool.Ifc.get_entity(text_obj))
|
||||
for element in tool.Drawing.get_drawing_elements(group):
|
||||
if element.is_a("IfcAnnotation") and element.ObjectType == "DRAWING":
|
||||
camera = tool.Ifc.get_object(element)
|
||||
break
|
||||
if not camera:
|
||||
return
|
||||
@@ -112,7 +113,7 @@ class Annotator:
|
||||
co1, _, _, _ = Annotator.get_placeholder_coords(camera)
|
||||
matrix_world = camera.matrix_world.copy()
|
||||
matrix_world.translation = co1
|
||||
collection = camera.users_collection[0]
|
||||
collection = camera.BIMObjectProperties.collection
|
||||
|
||||
if object_type == "TEXT":
|
||||
obj = bpy.data.objects.new(object_type, None)
|
||||
|
||||
@@ -269,7 +269,7 @@ def get_active_drawing(scene):
|
||||
props = scene.DocProperties
|
||||
try:
|
||||
camera = tool.Ifc.get_object(tool.Ifc.get().by_id(props.active_drawing_id))
|
||||
return camera.users_collection[0], camera
|
||||
return camera.BIMObjectProperties.collection, camera
|
||||
except:
|
||||
return None, None
|
||||
|
||||
|
||||
@@ -150,7 +150,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
"Creates/refreshes a .svg drawing based on currently active camera.\n\n"
|
||||
+ "SHIFT+CLICK to print all selected drawings"
|
||||
)
|
||||
print_all: bpy.props.BoolProperty(name="Print All", default=False)
|
||||
print_all: bpy.props.BoolProperty(name="Print All", default=False, options={"SKIP_SAVE"})
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -158,12 +158,9 @@ class CreateDrawing(bpy.types.Operator):
|
||||
|
||||
def invoke(self, context, event):
|
||||
# printing 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.print_all = True
|
||||
else:
|
||||
# can't rely on default value since the line above
|
||||
# will set the value to `True` for all future operator calls
|
||||
self.print_all = False
|
||||
return self.execute(context)
|
||||
|
||||
def execute(self, context):
|
||||
@@ -177,7 +174,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
|
||||
for drawing_id in drawings_to_print:
|
||||
if self.print_all:
|
||||
bpy.ops.bim.activate_drawing(drawing=drawing_id)
|
||||
bpy.ops.bim.activate_drawing(drawing=drawing_id, camera_view_point=False)
|
||||
|
||||
self.camera = context.scene.camera
|
||||
self.camera_element = tool.Ifc.get_entity(self.camera)
|
||||
@@ -220,7 +217,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
open_with_user_command(context.preferences.addons["blenderbim"].preferences.svg_command, svg_path)
|
||||
|
||||
if self.print_all:
|
||||
bpy.ops.bim.activate_drawing(drawing=original_drawing_id)
|
||||
bpy.ops.bim.activate_drawing(drawing=original_drawing_id, camera_view_point=False)
|
||||
return {"FINISHED"}
|
||||
|
||||
def get_camera_dimensions(self):
|
||||
@@ -288,7 +285,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
bpy.ops.render.render(write_still=True)
|
||||
else:
|
||||
previous_visibility = {}
|
||||
for obj in self.camera.users_collection[0].objects:
|
||||
for obj in self.camera.BIMObjectProperties.collection.objects:
|
||||
if bpy.context.view_layer.objects.get(obj.name):
|
||||
previous_visibility[obj.name] = obj.hide_get()
|
||||
obj.hide_set(True)
|
||||
@@ -1191,16 +1188,13 @@ 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)
|
||||
select_all: bpy.props.BoolProperty(name="Open All", default=False, options={"SKIP_SAVE"})
|
||||
|
||||
def invoke(self, context, event):
|
||||
# deselect 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.select_all = False
|
||||
else:
|
||||
# can't rely on default value since the line above
|
||||
# will set the value to `True` for all future operator calls
|
||||
self.select_all = True
|
||||
return self.execute(context)
|
||||
|
||||
def execute(self, context):
|
||||
@@ -1219,16 +1213,13 @@ class OpenDrawing(bpy.types.Operator):
|
||||
+ 'or using "svg_command" from the BlenderBIM preferences (if provided).\n\n'
|
||||
+ "SHIFT+CLICK to open all selected drawings"
|
||||
)
|
||||
open_all: bpy.props.BoolProperty(name="Open All", default=False)
|
||||
open_all: bpy.props.BoolProperty(name="Open All", default=False, options={"SKIP_SAVE"})
|
||||
|
||||
def invoke(self, context, event):
|
||||
# opening 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
|
||||
else:
|
||||
# can't rely on default value since the line above
|
||||
# will set the value to `True` for all future operator calls
|
||||
self.open_all = False
|
||||
return self.execute(context)
|
||||
|
||||
def execute(self, context):
|
||||
@@ -1306,13 +1297,30 @@ class ActivateDrawing(bpy.types.Operator):
|
||||
bl_idname = "bim.activate_drawing"
|
||||
bl_label = "Activate Drawing"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Activates the selected drawing view"
|
||||
bl_description = "Activates the selected drawing view.\n\n" + "ALT+CLICK to keep the viewport position"
|
||||
|
||||
drawing: bpy.props.IntProperty()
|
||||
camera_view_point: bpy.props.BoolProperty(name="Camera View Point", default=True, options={"SKIP_SAVE"})
|
||||
|
||||
def invoke(self, context, event):
|
||||
# keep the viewport position on alt+click
|
||||
# make sure to use SKIP_SAVE on property, otherwise it might get stuck
|
||||
if event.type == "LEFTMOUSE" and event.alt:
|
||||
self.camera_view_point = False
|
||||
return self.execute(context)
|
||||
|
||||
def execute(self, context):
|
||||
drawing = tool.Ifc.get().by_id(self.drawing)
|
||||
dprops = bpy.context.scene.DocProperties
|
||||
|
||||
if not self.camera_view_point:
|
||||
viewport_position = tool.Blender.get_viewport_position()
|
||||
|
||||
core.activate_drawing_view(tool.Ifc, tool.Drawing, drawing=drawing)
|
||||
|
||||
if not self.camera_view_point:
|
||||
tool.Blender.set_viewport_position(viewport_position)
|
||||
|
||||
dprops.active_drawing_id = self.drawing
|
||||
# reset DrawingsData to reload_drawing_styles work correctly
|
||||
DrawingsData.is_loaded = False
|
||||
@@ -1350,7 +1358,7 @@ class ResizeText(bpy.types.Operator):
|
||||
# TODO: check undo redo
|
||||
|
||||
def execute(self, context):
|
||||
for obj in context.scene.camera.users_collection[0].objects:
|
||||
for obj in context.scene.camera.BIMObjectProperties.collection.objects:
|
||||
if isinstance(obj.data, bpy.types.TextCurve):
|
||||
annotation.Annotator.resize_text(obj)
|
||||
return {"FINISHED"}
|
||||
@@ -1363,16 +1371,13 @@ class RemoveDrawing(bpy.types.Operator, Operator):
|
||||
bl_description = "Remove currently selected drawing.\n\n" + "SHIFT+CLICK to remove all selected drawings"
|
||||
|
||||
drawing: bpy.props.IntProperty()
|
||||
remove_all: bpy.props.BoolProperty(name="Remove All", default=False)
|
||||
remove_all: bpy.props.BoolProperty(name="Remove All", default=False, options={"SKIP_SAVE"})
|
||||
|
||||
def invoke(self, context, event):
|
||||
# removing all selected 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.remove_all = True
|
||||
else:
|
||||
# can't rely on default value since the line above
|
||||
# will set the value to `True` for all future operator calls
|
||||
self.remove_all = False
|
||||
return self.execute(context)
|
||||
|
||||
def _execute(self, context):
|
||||
|
||||
@@ -231,7 +231,7 @@ def toggleDecorations(self, context):
|
||||
toggle = self.should_draw_decorations
|
||||
if toggle:
|
||||
# TODO: design a proper text variable templating renderer
|
||||
collection = context.scene.camera.users_collection[0]
|
||||
collection = context.scene.camera.BIMObjectProperties.collection
|
||||
for obj in collection.objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element or not tool.Drawing.is_annotation_object_type(element, ["TEXT", "TEXT_LEADER"]):
|
||||
|
||||
@@ -28,9 +28,12 @@ import blenderbim.tool as tool
|
||||
import ifcopenshell.util.geolocation
|
||||
from xml.dom import minidom
|
||||
from mathutils import Vector
|
||||
import re
|
||||
|
||||
VIEW_TITLE_OFFSET_Y = 5
|
||||
DEFAULT_POSITION = Vector((30, 30))
|
||||
SVG = "{http://www.w3.org/2000/svg}"
|
||||
XLINK = "{http://www.w3.org/1999/xlink}"
|
||||
|
||||
|
||||
class SheetBuilder:
|
||||
@@ -130,7 +133,6 @@ class SheetBuilder:
|
||||
|
||||
def update_sheet_drawing_sizes(self, sheet):
|
||||
ET.register_namespace("", "http://www.w3.org/2000/svg")
|
||||
SVG = "{http://www.w3.org/2000/svg}"
|
||||
|
||||
layout_path = tool.Drawing.get_document_uri(sheet, "LAYOUT")
|
||||
layout_tree = ET.parse(layout_path)
|
||||
@@ -280,8 +282,61 @@ class SheetBuilder:
|
||||
titleblock.append(g)
|
||||
titleblock.remove(image)
|
||||
|
||||
def ensure_drawing_unique_styles(self, svg, drawing_id):
|
||||
"""ensures all drawing's classes and ids will be unique for the whole sheet
|
||||
by adding `drawing_id` based prefix
|
||||
"""
|
||||
prefix = f"d{drawing_id}" # just number doesn't work
|
||||
|
||||
# add .prefix class to all css selectors
|
||||
style = svg.find(f"{SVG}defs/{SVG}style")
|
||||
style_data = style.text
|
||||
text = ""
|
||||
brackets_level = 0
|
||||
for l in style_data:
|
||||
if l == "{":
|
||||
if brackets_level == 0:
|
||||
cur_line = text.splitlines()[-1]
|
||||
text = text[: -len(cur_line)]
|
||||
css_selectors = []
|
||||
# making sure cases like "text, tspan" will be
|
||||
# converted to "text.prefix, tspan.prefix"
|
||||
for css_selector in cur_line.split(","):
|
||||
css_selector = f"{css_selector.strip()}.{prefix}"
|
||||
css_selectors.append(css_selector)
|
||||
text += ", ".join(css_selectors) + " "
|
||||
brackets_level += 1
|
||||
elif l == "}":
|
||||
brackets_level -= 1
|
||||
text += l
|
||||
|
||||
# replace urls url(#marker) with url(#prefix-marker)
|
||||
# since url(#marker.prefix) doesn't seem to work
|
||||
text = re.sub(r"url\(#([^\)]+)\)", rf"url(#{prefix}-\1)", text)
|
||||
style.text = text
|
||||
|
||||
for svg_element in svg.findall(f".//*"):
|
||||
if svg_element.tag in (f"{SVG}style", f"{SVG}svg"):
|
||||
continue
|
||||
attrib = svg_element.attrib
|
||||
# add "prefix-" to all ids
|
||||
if "id" in attrib:
|
||||
attrib["id"] = f"{prefix}-{attrib['id']}"
|
||||
# add class "prefix" to all classes
|
||||
if "class" in attrib:
|
||||
attrib["class"] += f" {prefix}"
|
||||
if svg_element.tag == f"{SVG}use":
|
||||
href_attrib = f"{XLINK}href"
|
||||
if href_attrib in attrib:
|
||||
href = attrib[href_attrib]
|
||||
if href.startswith("#"):
|
||||
attrib[href_attrib] = f"#{prefix}-{href[1:]}"
|
||||
|
||||
return svg
|
||||
|
||||
def build_drawings(self, root, sheet):
|
||||
for view in root.findall('{http://www.w3.org/2000/svg}g[@data-type="drawing"]'):
|
||||
drawing_id = int(view.attrib["data-id"])
|
||||
reference = tool.Ifc.get().by_id(int(view.attrib["data-id"]))
|
||||
drawing = tool.Ifc.get().by_id(view.attrib["data-drawing"])
|
||||
|
||||
@@ -300,7 +355,9 @@ class SheetBuilder:
|
||||
view_title = image
|
||||
|
||||
if foreground is not None:
|
||||
view.append(self.parse_embedded_svg(foreground, {}))
|
||||
svg = self.parse_embedded_svg(foreground, {})
|
||||
svg = self.ensure_drawing_unique_styles(svg, drawing_id)
|
||||
view.append(svg)
|
||||
|
||||
if background is not None:
|
||||
background_path = os.path.join(self.layout_dir, self.get_href(background))
|
||||
|
||||
@@ -100,6 +100,9 @@ class SvgWriter:
|
||||
if not os.path.exists(resource_path):
|
||||
resource_basename = os.path.basename(resource_path)
|
||||
ootb_resource = os.path.join(bpy.context.scene.BIMProperties.data_dir, "assets", resource_basename)
|
||||
print(
|
||||
f"WARNING. Couldn't find {resource} for the drawing by the path: {resource_path}. Default BBIM resource will be copied from {ootb_resource}"
|
||||
)
|
||||
if os.path.exists(ootb_resource):
|
||||
shutil.copy(ootb_resource, resource_path)
|
||||
self.resource_paths[resource] = resource_path
|
||||
@@ -121,23 +124,35 @@ class SvgWriter:
|
||||
self.height = self.raw_height * self.svg_scale
|
||||
|
||||
def add_stylesheet(self):
|
||||
if not self.resource_paths["Stylesheet"] or not os.path.exists(self.resource_paths["Stylesheet"]):
|
||||
path = self.resource_paths["Stylesheet"]
|
||||
if not path:
|
||||
return
|
||||
with open(self.resource_paths["Stylesheet"], "r") as stylesheet:
|
||||
if not os.path.exists(path):
|
||||
print(f"WARNING. Couldn't find stylesheet for the drawing by the path: {path}")
|
||||
return
|
||||
with open(path, "r") as stylesheet:
|
||||
self.svg.defs.add(self.svg.style(stylesheet.read()))
|
||||
|
||||
def add_markers(self):
|
||||
if not self.resource_paths["Markers"] or not os.path.exists(self.resource_paths["Markers"]):
|
||||
path = self.resource_paths["Markers"]
|
||||
if not path:
|
||||
return
|
||||
tree = ET.parse(self.resource_paths["Markers"])
|
||||
if not os.path.exists(path):
|
||||
print(f"WARNING. Couldn't find markers for the drawing by the path: {path}")
|
||||
return
|
||||
tree = ET.parse(path)
|
||||
root = tree.getroot()
|
||||
for child in root:
|
||||
self.svg.defs.add(External(child))
|
||||
|
||||
def add_symbols(self):
|
||||
if not self.resource_paths["Symbols"] or not os.path.exists(self.resource_paths["Symbols"]):
|
||||
path = self.resource_paths["Symbols"]
|
||||
if not path:
|
||||
return
|
||||
tree = ET.parse(self.resource_paths["Symbols"])
|
||||
if not os.path.exists(path):
|
||||
print(f"WARNING. Couldn't find symbols for the drawing by the path: {path}")
|
||||
return
|
||||
tree = ET.parse(path)
|
||||
root = tree.getroot()
|
||||
for child in root:
|
||||
self.svg.defs.add(External(child))
|
||||
@@ -148,9 +163,15 @@ class SvgWriter:
|
||||
return External(xml_symbol) if xml_symbol else None
|
||||
|
||||
def add_patterns(self):
|
||||
if not self.resource_paths["Patterns"] or not os.path.exists(self.resource_paths["Patterns"]):
|
||||
path = self.resource_paths["Patterns"]
|
||||
if not path:
|
||||
return
|
||||
tree = ET.parse(self.resource_paths["Patterns"])
|
||||
if not os.path.exists(path):
|
||||
print(f"WARNING. Couldn't find patterns for the drawing by the path: {path}")
|
||||
return
|
||||
if not path or not os.path.exists(path):
|
||||
return
|
||||
tree = ET.parse(path)
|
||||
root = tree.getroot()
|
||||
for child in root:
|
||||
self.svg.defs.add(External(child))
|
||||
@@ -839,6 +860,10 @@ class SvgWriter:
|
||||
text_tag.add(tspan)
|
||||
line_number += 1
|
||||
|
||||
if add_fill_bg:
|
||||
# return line_number back to the original value
|
||||
line_number -= len(text_lines)
|
||||
|
||||
if "fill-bg" in classes:
|
||||
add_text_tag(True)
|
||||
add_text_tag(False)
|
||||
|
||||
@@ -63,6 +63,8 @@ def register():
|
||||
bpy.types.Scene.BIMGeometryProperties = bpy.props.PointerProperty(type=prop.BIMGeometryProperties)
|
||||
bpy.types.OBJECT_PT_transform.append(ui.BIM_PT_transform)
|
||||
bpy.types.VIEW3D_MT_object.append(ui.object_menu)
|
||||
bpy.types.OUTLINER_MT_object.append(ui.outliner_menu)
|
||||
bpy.types.VIEW3D_MT_object_context_menu.append(ui.object_menu)
|
||||
wm = bpy.context.window_manager
|
||||
if wm.keyconfigs.addon:
|
||||
km = wm.keyconfigs.addon.keymaps.new(name="Object Mode", space_type="EMPTY")
|
||||
@@ -90,6 +92,8 @@ def register():
|
||||
def unregister():
|
||||
bpy.types.VIEW3D_MT_object.remove(ui.object_menu)
|
||||
bpy.types.OBJECT_PT_transform.remove(ui.BIM_PT_transform)
|
||||
bpy.types.OUTLINER_MT_object.remove(ui.outliner_menu)
|
||||
bpy.types.VIEW3D_MT_object_context_menu.remove(ui.outliner_menu)
|
||||
del bpy.types.Scene.BIMGeometryProperties
|
||||
del bpy.types.Object.BIMGeometryProperties
|
||||
wm = bpy.context.window_manager
|
||||
|
||||
@@ -104,24 +104,44 @@ class ConnectionsData:
|
||||
def connections(cls):
|
||||
results = []
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
for rel in getattr(element, "ConnectedTo", []):
|
||||
|
||||
connected_to = getattr(element, "ConnectedTo", [])
|
||||
connected_from = getattr(element, "ConnectedFrom", [])
|
||||
|
||||
for rel in connected_to:
|
||||
if element.is_a("IfcDistributionPort"):
|
||||
related_element = rel.RelatedPort
|
||||
related_element_connection_type = ""
|
||||
else:
|
||||
related_element = rel.RelatedElement
|
||||
related_element_connection_type = rel.RelatedConnectionType
|
||||
|
||||
results.append(
|
||||
{
|
||||
"id": rel.id(),
|
||||
"is_relating": True,
|
||||
"Name": rel.RelatedElement.Name or "Unnamed",
|
||||
"ConnectionType": rel.RelatingConnectionType,
|
||||
"Name": related_element.Name or "Unnamed",
|
||||
"ConnectionType": related_element_connection_type,
|
||||
}
|
||||
)
|
||||
for rel in getattr(element, "ConnectedFrom", []):
|
||||
|
||||
for rel in connected_from:
|
||||
if element.is_a("IfcDistributionPort"):
|
||||
relating_element = rel.RelatingPort
|
||||
relating_element_connection_type = ""
|
||||
else:
|
||||
relating_element = rel.RelatingElement
|
||||
relating_element_connection_type = rel.RelatingConnectionType
|
||||
|
||||
results.append(
|
||||
{
|
||||
"id": rel.id(),
|
||||
"is_relating": False,
|
||||
"Name": rel.RelatingElement.Name or "Unnamed",
|
||||
"ConnectionType": rel.RelatedConnectionType,
|
||||
"Name": relating_element.Name or "Unnamed",
|
||||
"ConnectionType": relating_element_connection_type,
|
||||
}
|
||||
)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
|
||||
@@ -373,6 +373,7 @@ class OverrideDelete(bpy.types.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
use_global: bpy.props.BoolProperty(default=False)
|
||||
confirm: bpy.props.BoolProperty(default=True)
|
||||
is_batch: bpy.props.BoolProperty(name="Is Batch", default=False)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -380,7 +381,7 @@ class OverrideDelete(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
# Deep magick from the dawn of time
|
||||
if IfcStore.get_file():
|
||||
if tool.Ifc.get():
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
for obj in context.selected_objects:
|
||||
bpy.data.objects.remove(obj)
|
||||
@@ -389,21 +390,55 @@ class OverrideDelete(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
if self.confirm:
|
||||
if tool.Ifc.get():
|
||||
total_elements = len(tool.Ifc.get().wrapped_data.entity_names())
|
||||
total_polygons = sum([len(o.data.polygons) for o in context.selected_objects if o.type == "MESH"])
|
||||
# These numbers are a bit arbitrary, but basically batching is only
|
||||
# really necessary on large models and large geometry removals.
|
||||
self.is_batch = total_elements > 500000 and total_polygons > 2000
|
||||
if self.is_batch:
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
elif self.confirm:
|
||||
return context.window_manager.invoke_confirm(self, event)
|
||||
elif self.confirm:
|
||||
return context.window_manager.invoke_confirm(self, event)
|
||||
self.confirm = True
|
||||
return self.execute(context)
|
||||
|
||||
def draw(self, context):
|
||||
row = self.layout.row()
|
||||
row.label(text="Warning: Faster deletion will use more memory.", icon="ERROR")
|
||||
row = self.layout.row()
|
||||
row.prop(self, "is_batch", text="Enable Faster Deletion")
|
||||
|
||||
def _execute(self, context):
|
||||
if self.is_batch:
|
||||
ifcopenshell.util.element.batch_remove_deep2(tool.Ifc.get())
|
||||
for obj in context.selected_objects:
|
||||
if tool.Ifc.get_entity(obj):
|
||||
tool.Geometry.delete_ifc_object(obj)
|
||||
else:
|
||||
bpy.data.objects.remove(obj)
|
||||
if self.is_batch:
|
||||
old_file = tool.Ifc.get()
|
||||
old_file.end_transaction()
|
||||
new_file = ifcopenshell.util.element.unbatch_remove_deep2(tool.Ifc.get())
|
||||
new_file.begin_transaction()
|
||||
tool.Ifc.set(new_file)
|
||||
self.transaction_data = {"old_file": old_file, "new_file": new_file}
|
||||
IfcStore.add_transaction_operation(self)
|
||||
# Required otherwise gizmos are still visible
|
||||
context.view_layer.objects.active = None
|
||||
return {"FINISHED"}
|
||||
|
||||
def rollback(self, data):
|
||||
tool.Ifc.set(data["old_file"])
|
||||
data["old_file"].undo()
|
||||
|
||||
def commit(self, data):
|
||||
data["old_file"].redo()
|
||||
tool.Ifc.set(data["new_file"])
|
||||
|
||||
|
||||
class OverrideOutlinerDelete(bpy.types.Operator):
|
||||
bl_idname = "bim.override_outliner_delete"
|
||||
@@ -526,6 +561,8 @@ class OverrideDuplicateMove(bpy.types.Operator):
|
||||
collection.objects.link(new_obj)
|
||||
obj.select_set(False)
|
||||
new_obj.select_set(True)
|
||||
# clear object's collection so it will be able to have it's own
|
||||
new_obj.BIMObjectProperties.collection = None
|
||||
# Copy the actual class
|
||||
new = blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=new_obj)
|
||||
if new:
|
||||
@@ -738,13 +775,17 @@ class OverrideModeSetEdit(bpy.types.Operator):
|
||||
if not obj:
|
||||
continue
|
||||
|
||||
if not obj.data:
|
||||
obj.select_set(False)
|
||||
continue
|
||||
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
continue
|
||||
|
||||
# We are switching from OBJECT to EDIT mode.
|
||||
usage_type = tool.Model.get_usage_type(element)
|
||||
if usage_type:
|
||||
if usage_type is not None and usage_type not in ("PROFILE", "LAYER3"):
|
||||
# Parametric objects shall not be edited as meshes as they
|
||||
# can be modified to be incompatible with the parametric
|
||||
# constraints.
|
||||
@@ -755,6 +796,36 @@ class OverrideModeSetEdit(bpy.types.Operator):
|
||||
if not representation:
|
||||
continue
|
||||
|
||||
if (
|
||||
tool.Pset.get_element_pset(element, "BBIM_Door")
|
||||
or tool.Pset.get_element_pset(element, "BBIM_Window")
|
||||
or tool.Pset.get_element_pset(element, "BBIM_Stair")
|
||||
):
|
||||
obj.select_set(False)
|
||||
continue
|
||||
if usage_type == "PROFILE":
|
||||
if len(context.selected_objects) == 1:
|
||||
bpy.ops.bim.hotkey(hotkey="A_E", description="")
|
||||
return {"FINISHED"}
|
||||
else:
|
||||
self.report({"INFO"}, "Only a single profile-based representation can be edited at a time.")
|
||||
obj.select_set(False)
|
||||
continue
|
||||
if (
|
||||
tool.Geometry.is_profile_based(obj.data)
|
||||
or usage_type == "LAYER3"
|
||||
or tool.Geometry.is_swept_profile(representation)
|
||||
or tool.Pset.get_element_pset(element, "BBIM_Roof")
|
||||
or tool.Pset.get_element_pset(element, "BBIM_Railing")
|
||||
):
|
||||
if len(context.selected_objects) == 1:
|
||||
bpy.ops.bim.hotkey(hotkey="S_E", description="")
|
||||
return {"FINISHED"}
|
||||
else:
|
||||
self.report({"INFO"}, "Only a single profile-based representation can be edited at a time.")
|
||||
obj.select_set(False)
|
||||
continue
|
||||
|
||||
if tool.Geometry.is_meshlike(representation):
|
||||
if getattr(element, "HasOpenings", None):
|
||||
# Mesh elements with openings must disable openings
|
||||
@@ -866,7 +937,22 @@ class OverrideModeSetObject(bpy.types.Operator):
|
||||
if not element:
|
||||
continue
|
||||
|
||||
if obj.data.BIMMeshProperties.ifc_definition_id:
|
||||
if tool.Profile.is_editing_profile():
|
||||
profile_id = context.scene.BIMProfileProperties.active_profile_id
|
||||
if profile_id:
|
||||
profile = tool.Ifc.get().by_id(profile_id)
|
||||
if tool.Ifc.get_object(profile): # We are editing an arbitrary profile
|
||||
bpy.ops.bim.edit_arbitrary_profile()
|
||||
elif tool.Pset.get_element_pset(element, "BBIM_Railing"):
|
||||
bpy.ops.bim.cad_hotkey(hotkey="S_Q")
|
||||
elif tool.Pset.get_element_pset(element, "BBIM_Roof"):
|
||||
bpy.ops.bim.cad_hotkey(hotkey="S_Q")
|
||||
elif tool.Model.get_usage_type(element) == "PROFILE":
|
||||
bpy.ops.bim.edit_extrusion_axis()
|
||||
else:
|
||||
bpy.ops.bim.edit_extrusion_profile()
|
||||
return self.execute(context)
|
||||
elif obj.data.BIMMeshProperties.ifc_definition_id:
|
||||
if not tool.Geometry.has_geometric_data(obj):
|
||||
self.is_valid = False
|
||||
self.should_save = False
|
||||
|
||||
@@ -24,10 +24,14 @@ from blenderbim.bim.module.geometry.data import RepresentationsData, Connections
|
||||
|
||||
|
||||
def object_menu(self, context):
|
||||
self.layout.separator()
|
||||
self.layout.operator("bim.override_object_duplicate_move", icon="PLUGIN")
|
||||
self.layout.operator("bim.override_object_delete", icon="PLUGIN")
|
||||
self.layout.operator("bim.override_paste_buffer", icon="PLUGIN")
|
||||
|
||||
def outliner_menu(self, context):
|
||||
self.layout.separator()
|
||||
self.layout.operator("bim.override_outliner_delete", icon='X')
|
||||
|
||||
class BIM_PT_representations(Panel):
|
||||
bl_label = "IFC Representations"
|
||||
|
||||
@@ -33,6 +33,7 @@ classes = (
|
||||
operator.ToggleGroup,
|
||||
operator.UnassignGroup,
|
||||
operator.UpdateGroup,
|
||||
operator.SelectGroupElements,
|
||||
prop.ExpandedGroups,
|
||||
prop.Group,
|
||||
prop.BIMGroupProperties,
|
||||
|
||||
@@ -275,3 +275,19 @@ class UpdateGroup(bpy.types.Operator, tool.Ifc.Operator):
|
||||
)
|
||||
bpy.ops.bim.load_groups()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SelectGroupElements(bpy.types.Operator):
|
||||
bl_idname = "bim.select_group_elements"
|
||||
bl_label = "Select Group elements"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
group: bpy.props.IntProperty()
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return bool(tool.Ifc.get() and context.active_object)
|
||||
|
||||
def execute(self, context):
|
||||
elements = tool.Drawing.get_group_elements(tool.Ifc.get().by_id(self.group))
|
||||
tool.Spatial.select_products(elements)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -107,6 +107,7 @@ class BIM_PT_object_groups(Panel):
|
||||
for group in ObjectGroupsData.data["groups"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=group["name"])
|
||||
row.operator("bim.select_group_elements", text="", icon="RESTRICT_SELECT_OFF").group = group["id"]
|
||||
op = row.operator("bim.unassign_group", text="", icon="X")
|
||||
op.group = group["id"]
|
||||
|
||||
|
||||
@@ -21,10 +21,12 @@ from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.AddFileToRepo,
|
||||
operator.AddRemote,
|
||||
operator.AddTag,
|
||||
operator.CloneRepo,
|
||||
operator.CommitChanges,
|
||||
operator.CreateRepo,
|
||||
operator.DeleteRemote,
|
||||
operator.DeleteTag,
|
||||
operator.DiscardUncommitted,
|
||||
operator.DisplayRevision,
|
||||
|
||||
@@ -15,6 +15,11 @@ class IfcGitData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def make_sure_is_loaded(cls):
|
||||
if not cls.is_loaded:
|
||||
cls.load()
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {
|
||||
@@ -29,6 +34,10 @@ class IfcGitData:
|
||||
"name_ifc": cls.name_ifc(),
|
||||
"dir_name": cls.dir_name(),
|
||||
"base_name": cls.base_name(),
|
||||
"working_dir": cls.working_dir(),
|
||||
"untracked_files": cls.untracked_files(),
|
||||
"is_detached": cls.is_detached(),
|
||||
"active_branch_name": cls.active_branch_name(),
|
||||
"is_dirty": cls.is_dirty(),
|
||||
"commit": cls.commit(),
|
||||
"current_revision": cls.current_revision(),
|
||||
@@ -113,6 +122,27 @@ class IfcGitData:
|
||||
return os.path.basename(path_ifc)
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def working_dir(cls):
|
||||
if cls.repo():
|
||||
return cls.repo().working_dir
|
||||
|
||||
@classmethod
|
||||
def untracked_files(cls):
|
||||
if cls.repo():
|
||||
return cls.repo().untracked_files
|
||||
return []
|
||||
|
||||
@classmethod
|
||||
def is_detached(cls):
|
||||
if cls.repo():
|
||||
return cls.repo().head.is_detached
|
||||
|
||||
@classmethod
|
||||
def active_branch_name(cls):
|
||||
if cls.repo() and not cls.is_detached():
|
||||
return cls.repo().active_branch.name
|
||||
|
||||
@classmethod
|
||||
def is_dirty(cls):
|
||||
if cls.repo() and cls.git_exe():
|
||||
|
||||
@@ -15,6 +15,7 @@ class CreateRepo(bpy.types.Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
IfcGitData.make_sure_is_loaded()
|
||||
path_ifc = IfcGitData.data["path_ifc"]
|
||||
if not os.path.isfile(path_ifc):
|
||||
return False
|
||||
@@ -42,6 +43,7 @@ class AddFileToRepo(bpy.types.Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
IfcGitData.make_sure_is_loaded()
|
||||
path_ifc = IfcGitData.data["path_ifc"]
|
||||
if not os.path.isfile(path_ifc):
|
||||
return False
|
||||
@@ -80,6 +82,7 @@ class CloneRepo(bpy.types.Operator):
|
||||
|
||||
props = context.scene.IfcGitProperties
|
||||
core.clone_repo(tool.IfcGit, props.remote_url, props.local_folder, self)
|
||||
props.remote_url = ""
|
||||
refresh()
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -107,6 +110,7 @@ class CommitChanges(bpy.types.Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
IfcGitData.make_sure_is_loaded()
|
||||
props = context.scene.IfcGitProperties
|
||||
repo = IfcGitData.data["repo"]
|
||||
if props.commit_message == "":
|
||||
@@ -128,7 +132,7 @@ class CommitChanges(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
|
||||
repo = IfcGitData.data["repo"]
|
||||
core.commit_changes(tool.IfcGit, tool.Ifc, repo, context)
|
||||
core.commit_changes(tool.IfcGit, tool.Ifc, repo)
|
||||
bpy.ops.ifcgit.refresh()
|
||||
refresh()
|
||||
return {"FINISHED"}
|
||||
@@ -143,7 +147,10 @@ class AddTag(bpy.types.Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
IfcGitData.make_sure_is_loaded()
|
||||
props = context.scene.IfcGitProperties
|
||||
if props.new_tag_name == "":
|
||||
return False
|
||||
repo = IfcGitData.data["repo"]
|
||||
if repo and (
|
||||
not tool.IfcGit.is_valid_ref_format(props.new_tag_name)
|
||||
@@ -187,8 +194,9 @@ class RefreshGit(bpy.types.Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
IfcGitData.make_sure_is_loaded()
|
||||
repo = IfcGitData.data["repo"]
|
||||
if repo != None and repo.heads:
|
||||
if repo:
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -207,9 +215,15 @@ class DisplayRevision(bpy.types.Operator):
|
||||
bl_idname = "ifcgit.display_revision"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
props = context.scene.IfcGitProperties
|
||||
if props.ifcgit_commits:
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
core.colourise_revision(tool.IfcGit, context)
|
||||
core.colourise_revision(tool.IfcGit)
|
||||
refresh()
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -236,6 +250,12 @@ class SwitchRevision(bpy.types.Operator):
|
||||
bl_idname = "ifcgit.switch_revision"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
props = context.scene.IfcGitProperties
|
||||
if props.ifcgit_commits:
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
core.switch_revision(tool.IfcGit, tool.Ifc)
|
||||
@@ -252,7 +272,9 @@ class Merge(bpy.types.Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if IfcGitData.data["ifcmerge_exe"]:
|
||||
IfcGitData.make_sure_is_loaded()
|
||||
props = context.scene.IfcGitProperties
|
||||
if IfcGitData.data["ifcmerge_exe"] and props.ifcgit_commits and not IfcGitData.data["is_detached"]:
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -276,8 +298,7 @@ class Push(bpy.types.Operator):
|
||||
|
||||
props = context.scene.IfcGitProperties
|
||||
repo = IfcGitData.data["repo"]
|
||||
remote = repo.remotes[props.select_remote]
|
||||
remote.push(refspec=IfcGitData.data["repo"].active_branch.name)
|
||||
core.push(tool.IfcGit, repo, props.select_remote, self)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -297,6 +318,52 @@ class Fetch(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddRemote(bpy.types.Operator):
|
||||
"""Add a remote repository"""
|
||||
|
||||
bl_label = "Add Remote"
|
||||
bl_idname = "ifcgit.add_remote"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
IfcGitData.make_sure_is_loaded()
|
||||
props = context.scene.IfcGitProperties
|
||||
repo = IfcGitData.data["repo"]
|
||||
if (
|
||||
not repo
|
||||
or not tool.IfcGit.is_valid_ref_format(props.remote_name)
|
||||
or not props.remote_url
|
||||
or props.remote_name in [remote.name for remote in repo.remotes]
|
||||
):
|
||||
return False
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
repo = IfcGitData.data["repo"]
|
||||
core.add_remote(tool.IfcGit, repo)
|
||||
bpy.ops.ifcgit.refresh()
|
||||
refresh()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DeleteRemote(bpy.types.Operator):
|
||||
"""Delete the selected remote"""
|
||||
|
||||
bl_label = "Delete Remote"
|
||||
bl_idname = "ifcgit.delete_remote"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
repo = IfcGitData.data["repo"]
|
||||
core.delete_remote(tool.IfcGit, repo)
|
||||
bpy.ops.ifcgit.refresh()
|
||||
refresh()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ObjectLog(bpy.types.Operator):
|
||||
"""Displays Git log of selected object"""
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ from bpy.props import (
|
||||
IntProperty,
|
||||
EnumProperty,
|
||||
)
|
||||
from blenderbim.bim.module.ifcgit.data import IfcGitData, refresh
|
||||
from blenderbim.bim.module.ifcgit.data import IfcGitData
|
||||
|
||||
|
||||
def git_branches(self, context):
|
||||
@@ -22,8 +22,9 @@ def git_branches(self, context):
|
||||
IfcGitData.data["branch_names"] = ["main"] + IfcGitData.data["branch_names"]
|
||||
|
||||
if IfcGitData.data["remotes"]:
|
||||
props = context.scene.IfcGitProperties
|
||||
IfcGitData.data["branch_names"] += [r.name for r in IfcGitData.data["remotes"][props.select_remote].refs]
|
||||
for remote in IfcGitData.data["remotes"]:
|
||||
for remote_branch in remote.refs:
|
||||
IfcGitData.data["branch_names"].append(remote_branch.name)
|
||||
|
||||
return [(myname, myname, myname) for myname in IfcGitData.data["branch_names"]]
|
||||
|
||||
@@ -113,6 +114,11 @@ class IfcGitProperties(PropertyGroup):
|
||||
description="An optional human readable description of this tag",
|
||||
default="",
|
||||
)
|
||||
remote_name: StringProperty(
|
||||
name="New remote name",
|
||||
description="A local name for a remote Git repository",
|
||||
default="",
|
||||
)
|
||||
remote_url: StringProperty(
|
||||
name="Git URL",
|
||||
description="A URL pointing to a Git repository",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import bpy
|
||||
import time
|
||||
from blenderbim.bim.module.ifcgit.data import IfcGitData, refresh
|
||||
from blenderbim.bim.module.ifcgit.data import IfcGitData
|
||||
|
||||
|
||||
class IFCGIT_PT_panel(bpy.types.Panel):
|
||||
@@ -34,8 +34,8 @@ class IFCGIT_PT_panel(bpy.types.Panel):
|
||||
if path_ifc:
|
||||
if IfcGitData.data["repo"]:
|
||||
name_ifc = IfcGitData.data["name_ifc"]
|
||||
row.label(text=IfcGitData.data["repo"].working_dir, icon="SYSTEM")
|
||||
if name_ifc in IfcGitData.data["repo"].untracked_files:
|
||||
row.label(text=IfcGitData.data["working_dir"], icon="SYSTEM")
|
||||
if name_ifc in IfcGitData.data["untracked_files"]:
|
||||
row.operator(
|
||||
"ifcgit.addfile",
|
||||
text="Add '" + name_ifc + "' to repository",
|
||||
@@ -79,7 +79,7 @@ class IFCGIT_PT_panel(bpy.types.Panel):
|
||||
row = layout.row()
|
||||
row.prop(props, "commit_message")
|
||||
|
||||
if IfcGitData.data["repo"].head.is_detached:
|
||||
if IfcGitData.data["is_detached"]:
|
||||
row = layout.row()
|
||||
row.label(text="HEAD is detached, commit will create a branch", icon="ERROR")
|
||||
row.prop(props, "new_branch_name")
|
||||
@@ -88,10 +88,10 @@ class IFCGIT_PT_panel(bpy.types.Panel):
|
||||
row.operator("ifcgit.commit_changes", icon="GREASEPENCIL")
|
||||
|
||||
row = layout.row()
|
||||
if IfcGitData.data["repo"].head.is_detached:
|
||||
if IfcGitData.data["is_detached"]:
|
||||
row.label(text="Working branch: Detached HEAD")
|
||||
else:
|
||||
row.label(text="Working branch: " + IfcGitData.data["repo"].active_branch.name)
|
||||
row.label(text="Working branch: " + IfcGitData.data["active_branch_name"])
|
||||
|
||||
grouped = layout.row()
|
||||
column = grouped.column()
|
||||
@@ -120,8 +120,6 @@ class IFCGIT_PT_panel(bpy.types.Panel):
|
||||
row = column.row()
|
||||
row.operator("ifcgit.switch_revision", icon="CURRENT_FILE")
|
||||
|
||||
# TODO operator to tag selected
|
||||
|
||||
row = column.row()
|
||||
row.operator("ifcgit.merge", icon="EXPERIMENTAL", text="")
|
||||
|
||||
@@ -168,10 +166,19 @@ class IFCGIT_PT_panel(bpy.types.Panel):
|
||||
row.prop(props, "select_remote", text="Select remote")
|
||||
urls = IfcGitData.data["remote_urls"]
|
||||
row.label(text=urls[props.select_remote])
|
||||
row.operator("ifcgit.delete_remote", text="", icon="PANEL_CLOSE")
|
||||
row = layout.row()
|
||||
row.operator("ifcgit.push", icon="EXPERIMENTAL")
|
||||
row.operator("ifcgit.push", icon="EXPORT")
|
||||
row.operator("ifcgit.fetch", icon="IMPORT")
|
||||
|
||||
box = layout.box()
|
||||
row = box.row()
|
||||
row.prop(props, "remote_name")
|
||||
row = box.row()
|
||||
row.prop(props, "remote_url")
|
||||
row = box.row()
|
||||
row.operator("ifcgit.add_remote", icon="ADD")
|
||||
|
||||
|
||||
class COMMIT_UL_List(bpy.types.UIList):
|
||||
"""List of Git commits"""
|
||||
@@ -199,9 +206,9 @@ class COMMIT_UL_List(bpy.types.UIList):
|
||||
refs += "{" + tag.name + "} "
|
||||
|
||||
if commit == current_revision:
|
||||
layout.label(text="[HEAD] " + refs + commit.message, icon="DECORATE_KEYFRAME")
|
||||
layout.label(text="[HEAD] " + refs + commit.message.split("\n")[0], icon="DECORATE_KEYFRAME")
|
||||
else:
|
||||
layout.label(text=refs + commit.message, icon="DECORATE_ANIMATE")
|
||||
layout.label(text=refs + commit.message.split("\n")[0], icon="DECORATE_ANIMATE")
|
||||
layout.label(text=time.strftime("%c", time.localtime(commit.committed_date)))
|
||||
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ class SelectByMaterial(bpy.types.Operator, tool.Ifc.Operator):
|
||||
material: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.select_by_material(tool.Material, material=tool.Ifc.get().by_id(self.material))
|
||||
core.select_by_material(tool.Material, tool.Spatial, material=tool.Ifc.get().by_id(self.material))
|
||||
|
||||
|
||||
class EnableEditingMaterial(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -174,67 +174,7 @@ class AssignMaterial(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
def _execute(self, context):
|
||||
objects = [bpy.data.objects.get(self.obj)] if self.obj else tool.Blender.get_selected_objects()
|
||||
active_obj = context.active_object
|
||||
active_object_material_type = self.material_type or active_obj.BIMObjectMaterialProperties.material_type
|
||||
material = tool.Ifc.get().by_id(int(active_obj.BIMObjectMaterialProperties.material))
|
||||
for obj in objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
continue
|
||||
ifcopenshell.api.run(
|
||||
"material.assign_material",
|
||||
tool.Ifc.get(),
|
||||
product=element,
|
||||
type=active_object_material_type,
|
||||
material=material,
|
||||
)
|
||||
assigned_material = ifcopenshell.util.element.get_material(element)
|
||||
if assigned_material.is_a("IfcMaterialConstituentSet"):
|
||||
if not assigned_material.MaterialConstituents:
|
||||
ifcopenshell.api.run(
|
||||
"material.add_constituent",
|
||||
tool.Ifc.get(),
|
||||
constituent_set=assigned_material,
|
||||
material=material,
|
||||
)
|
||||
elif assigned_material.is_a() == "IfcMaterialLayerSet":
|
||||
if not assigned_material.MaterialLayers:
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
layer = ifcopenshell.api.run(
|
||||
"material.add_layer",
|
||||
tool.Ifc.get(),
|
||||
layer_set=assigned_material,
|
||||
material=material,
|
||||
)
|
||||
thickness = 0.1 # Arbitrary metric thickness for now
|
||||
layer.LayerThickness = thickness / unit_scale
|
||||
elif assigned_material.is_a("IfcMaterialProfileSet"):
|
||||
if not assigned_material.MaterialProfiles:
|
||||
named_profiles = [p for p in tool.Ifc.get().by_type("IfcProfileDef") if p.ProfileName]
|
||||
if named_profiles:
|
||||
profile = named_profiles[0]
|
||||
else:
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
size = 0.5 / unit_scale
|
||||
profile = tool.Ifc.get().create_entity(
|
||||
"IfcRectangleProfileDef",
|
||||
ProfileName="New Profile",
|
||||
ProfileType="AREA",
|
||||
XDim=size,
|
||||
YDim=size,
|
||||
)
|
||||
material_profile = ifcopenshell.api.run(
|
||||
"material.add_profile",
|
||||
tool.Ifc.get(),
|
||||
profile_set=assigned_material,
|
||||
material=tool.Ifc.get().by_type("IfcMaterial")[0],
|
||||
)
|
||||
ifcopenshell.api.run(
|
||||
"material.assign_profile",
|
||||
tool.Ifc.get(),
|
||||
material_profile=material_profile,
|
||||
profile=profile,
|
||||
)
|
||||
core.assign_material(tool.Ifc, tool.Material, material_type= self.material_type , objects=objects )
|
||||
|
||||
|
||||
class UnassignMaterial(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -245,12 +185,7 @@ class UnassignMaterial(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
def _execute(self, context):
|
||||
objects = [bpy.data.objects.get(self.obj)] if self.obj else tool.Blender.get_selected_objects()
|
||||
for obj in objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element:
|
||||
material = ifcopenshell.util.element.get_material(element, should_inherit=False)
|
||||
if "Usage" not in material.is_a():
|
||||
ifcopenshell.api.run("material.unassign_material", tool.Ifc.get(), product=element)
|
||||
core.unassign_material(tool.Ifc, tool.Material, objects=objects )
|
||||
|
||||
|
||||
class AddConstituent(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
@@ -30,7 +30,6 @@ class BIM_PT_misc_utilities(bpy.types.Panel):
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
props = context.scene.BIMMiscProperties
|
||||
|
||||
row = layout.split(factor=0.2, align=True)
|
||||
row.prop(props, "override_colour", text="")
|
||||
row.operator("bim.set_override_colour")
|
||||
@@ -49,6 +48,8 @@ class BIM_PT_misc_utilities(bpy.types.Panel):
|
||||
row.operator("bim.draw_system_arrows")
|
||||
row = layout.row()
|
||||
row.operator("bim.clean_wireframes")
|
||||
row = layout.row()
|
||||
row.operator("bim.patch_non_parametric_mep_segment")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.enable_editing_sketch_extrusion_profile", text="Start Sketching")
|
||||
|
||||
@@ -86,6 +86,7 @@ classes = (
|
||||
profile.ExtendProfile,
|
||||
profile.RecalculateProfile,
|
||||
profile.Rotate90,
|
||||
profile.PatchNonParametricMepSegment,
|
||||
roof.GenerateHippedRoof,
|
||||
slab.DisableEditingExtrusionProfile,
|
||||
slab.DisableEditingSketchExtrusionProfile,
|
||||
@@ -97,6 +98,7 @@ classes = (
|
||||
slab.SetArcIndex,
|
||||
space.GenerateSpace,
|
||||
space.GenerateSpacesFromWalls,
|
||||
space.ToggleSpaceVisibility,
|
||||
prop.BIMModelProperties,
|
||||
prop.BIMArrayProperties,
|
||||
prop.BIMStairProperties,
|
||||
@@ -105,7 +107,6 @@ classes = (
|
||||
prop.BIMDoorProperties,
|
||||
prop.BIMRailingProperties,
|
||||
prop.BIMRoofProperties,
|
||||
ui.BIM_PT_authoring,
|
||||
ui.BIM_PT_array,
|
||||
ui.BIM_PT_stair,
|
||||
ui.BIM_PT_sverchok,
|
||||
@@ -115,7 +116,6 @@ classes = (
|
||||
ui.BIM_PT_roof,
|
||||
ui.LaunchTypeManager,
|
||||
ui.BIM_MT_model,
|
||||
ui.BIM_PT_GridsSpatialManager,
|
||||
ui.BIM_PT_Grids,
|
||||
grid.BIM_OT_add_object,
|
||||
stair.BIM_OT_add_object,
|
||||
|
||||
@@ -128,15 +128,16 @@ def update_door_modifier_representation(context):
|
||||
)
|
||||
|
||||
# type attributes
|
||||
element.OperationType = props.door_type
|
||||
if tool.Ifc.get_schema() != "IFC2X3":
|
||||
element.OperationType = props.door_type
|
||||
|
||||
# occurences attributes
|
||||
occurences = tool.Ifc.get_all_element_occurences(element)
|
||||
for occurence in occurences:
|
||||
occurence.OverallWidth = props.overall_width
|
||||
occurence.OverallHeight = props.overall_height
|
||||
occurence.OverallWidth = props.overall_width / si_conversion
|
||||
occurence.OverallHeight = props.overall_height / si_conversion
|
||||
|
||||
update_simple_openings(element, props.overall_width, props.overall_height)
|
||||
update_simple_openings(element, props.overall_width / si_conversion, props.overall_height / si_conversion)
|
||||
|
||||
|
||||
# TODO: move it out to tools
|
||||
@@ -486,7 +487,8 @@ class BIM_OT_add_door(bpy.types.Operator, tool.Ifc.Operator):
|
||||
element = blenderbim.core.root.assign_class(
|
||||
tool.Ifc, tool.Collector, tool.Root, obj=obj, ifc_class="IfcDoor", should_add_representation=False
|
||||
)
|
||||
element.PredefinedType = "DOOR"
|
||||
if tool.Ifc.get_schema() != "IFC2X3":
|
||||
element.PredefinedType = "DOOR"
|
||||
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
bpy.context.view_layer.objects.active = None
|
||||
@@ -507,8 +509,8 @@ class AddDoor(bpy.types.Operator, tool.Ifc.Operator):
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
props = obj.BIMDoorProperties
|
||||
|
||||
if element.is_a() not in ("IfcDoor", "IfcDoorType"):
|
||||
self.report({"ERROR"}, "Object has to be IfcDoor/IfcDoorType type to add a door.")
|
||||
if element.is_a() not in ("IfcDoor", "IfcDoorType", "IfcDoorStyle"):
|
||||
self.report({"ERROR"}, "Object has to be IfcDoor/IfcDoorType/IfcDoorStyle type to add a door.")
|
||||
return {"CANCELLED"}
|
||||
|
||||
door_data = props.get_general_kwargs(convert_to_project_units=True)
|
||||
|
||||
@@ -232,9 +232,16 @@ class FilledOpeningGenerator:
|
||||
curve_3d = ifcopenshell.util.representation.resolve_representation(profile).Items[0]
|
||||
|
||||
def get_curve_2d_from_3d(curve_3d):
|
||||
ifc_segments = [shape_builder.deep_copy(s) for s in curve_3d.Segments]
|
||||
ifc_points = ifc_file.createIfcCartesianPointList2D([Vector(p).xz for p in curve_3d.Points.CoordList])
|
||||
ifc_curve = ifc_file.createIfcIndexedPolyCurve(Points=ifc_points, Segments=ifc_segments)
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
coords = [Vector(p).xz for p in shape_builder.get_polyline_coords(curve_3d)]
|
||||
ifc_curve = shape_builder.polyline(coords, closed=True)
|
||||
else:
|
||||
# using different algorithm to keep arc segments possible in the future
|
||||
ifc_segments = [shape_builder.deep_copy(s) for s in curve_3d.Segments]
|
||||
ifc_points = ifc_file.createIfcCartesianPointList2D(
|
||||
[Vector(p).xz for p in curve_3d.Points.CoordList]
|
||||
)
|
||||
ifc_curve = ifc_file.createIfcIndexedPolyCurve(Points=ifc_points, Segments=ifc_segments)
|
||||
return ifc_curve
|
||||
|
||||
extrusion = shape_builder.extrude(
|
||||
|
||||
@@ -68,7 +68,7 @@ class PieUpdateContainer(bpy.types.Operator):
|
||||
if not obj.BIMObjectProperties.ifc_definition_id:
|
||||
continue
|
||||
for collection in obj.users_collection:
|
||||
spatial_obj = bpy.data.objects.get(collection.name)
|
||||
spatial_obj = collection.BIMCollectionProperties.obj
|
||||
if spatial_obj and spatial_obj.BIMObjectProperties.ifc_definition_id:
|
||||
blenderbim.core.spatial.assign_container(
|
||||
tool.Ifc, tool.Collector, tool.Spatial, structure_obj=spatial_obj, element_obj=obj
|
||||
|
||||
@@ -128,20 +128,9 @@ class AddConstrTypeInstance(bpy.types.Operator):
|
||||
|
||||
obj.location = context.scene.cursor.location
|
||||
|
||||
collection = None
|
||||
if (
|
||||
building_obj
|
||||
and building_element
|
||||
and building_element.is_a() in ["IfcWall", "IfcWallStandardCase", "IfcCovering"]
|
||||
and instance_class in ["IfcWindow", "IfcDoor"]
|
||||
):
|
||||
# Fills should be a sibling to the building element
|
||||
collection = building_obj.users_collection[0]
|
||||
if not collection:
|
||||
collection = context.view_layer.active_layer_collection.collection
|
||||
|
||||
collection = context.view_layer.active_layer_collection.collection
|
||||
collection.objects.link(obj)
|
||||
collection_obj = bpy.data.objects.get(collection.name)
|
||||
collection_obj = collection.BIMCollectionProperties.obj
|
||||
|
||||
bpy.ops.bim.assign_class(obj=obj.name, ifc_class=instance_class)
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
@@ -150,6 +139,26 @@ class AddConstrTypeInstance(bpy.types.Operator):
|
||||
# Update required as core.type.assign_type may change obj.data
|
||||
context.view_layer.update()
|
||||
|
||||
if (
|
||||
building_obj
|
||||
and building_element
|
||||
and building_element.is_a() in ["IfcWall", "IfcWallStandardCase", "IfcCovering"]
|
||||
and instance_class in ["IfcWindow", "IfcDoor"]
|
||||
):
|
||||
# Fills should be a sibling to the building element
|
||||
parent = ifcopenshell.util.element.get_aggregate(building_element)
|
||||
if parent:
|
||||
parent_obj = tool.Ifc.get_object(parent)
|
||||
blenderbim.core.aggregate.assign_object(
|
||||
tool.Ifc, tool.Aggregate, tool.Collector, relating_obj=parent_obj, related_obj=obj
|
||||
)
|
||||
else:
|
||||
parent = ifcopenshell.util.element.get_container(building_element)
|
||||
parent_obj = tool.Ifc.get_object(parent)
|
||||
blenderbim.core.spatial.assign_container(
|
||||
tool.Ifc, tool.Collector, tool.Spatial, structure_obj=parent_obj, element_obj=obj
|
||||
)
|
||||
|
||||
# set occurences properties for the types defined with modifiers
|
||||
if instance_class in ["IfcWindow", "IfcDoor"]:
|
||||
pset_name = f"BBIM_{instance_class[3:]}"
|
||||
@@ -462,11 +471,6 @@ class MirrorElements(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
obj.matrix_world = newmat
|
||||
|
||||
def copy_obj(self, obj):
|
||||
new = obj.copy()
|
||||
new.data = wall2.data.copy()
|
||||
wall1.users_collection[0].objects.link(wall2)
|
||||
blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=wall2)
|
||||
|
||||
|
||||
def generate_box(usecase_path, ifc_file, settings):
|
||||
|
||||
@@ -28,6 +28,7 @@ import blenderbim.bim.handler
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.type
|
||||
import blenderbim.core.geometry
|
||||
import blenderbim.core.material
|
||||
from math import pi, degrees, inf
|
||||
from mathutils import Vector, Matrix, Quaternion
|
||||
from blenderbim.bim.module.geometry.helper import Helper
|
||||
@@ -53,7 +54,7 @@ class DumbProfileGenerator:
|
||||
self.axis_context = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Axis", "GRAPH_VIEW")
|
||||
props = bpy.context.scene.BIMModelProperties
|
||||
self.collection = bpy.context.view_layer.active_layer_collection.collection
|
||||
self.collection_obj = bpy.data.objects.get(self.collection.name)
|
||||
self.collection_obj = self.collection.BIMCollectionProperties.obj
|
||||
self.depth = props.extrusion_depth
|
||||
self.rotation = 0
|
||||
self.location = Vector((0, 0, 0))
|
||||
@@ -857,6 +858,25 @@ class Rotate90(bpy.types.Operator, tool.Ifc.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class PatchNonParametricMepSegment(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.patch_non_parametric_mep_segment"
|
||||
bl_label = "Set MEP segment Material Profile"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.active_object
|
||||
|
||||
def _execute(self, context):
|
||||
styles = tool.Geometry.get_styles(context.active_object)
|
||||
blenderbim.core.material.patch_non_parametric_mep_segment(
|
||||
tool.Ifc, tool.Material, tool.Profile, obj=context.active_object
|
||||
)
|
||||
bpy.ops.bim.enable_editing_extrusion_axis()
|
||||
bpy.ops.bim.edit_extrusion_axis()
|
||||
styles = tool.Geometry.get_styles(context.active_object)
|
||||
|
||||
|
||||
class EnableEditingExtrusionAxis(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.enable_editing_extrusion_axis"
|
||||
bl_label = "Enable Editing Extrusion Axis"
|
||||
@@ -927,7 +947,7 @@ class DisableEditingExtrusionAxis(bpy.types.Operator, tool.Ifc.Operator):
|
||||
return context.selected_objects
|
||||
|
||||
def _execute(self, context):
|
||||
return disable_editing_extrusion_axis()
|
||||
return disable_editing_extrusion_axis(context)
|
||||
|
||||
|
||||
class EditExtrusionAxis(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -948,6 +968,10 @@ class EditExtrusionAxis(bpy.types.Operator, tool.Ifc.Operator):
|
||||
depth = (end - start).length
|
||||
z_axis = (end - start).normalized()
|
||||
y_axis = Vector((0, 0, 1))
|
||||
# making sure z_axis != y_axis
|
||||
if z_axis == y_axis:
|
||||
y_axis = Vector((0,1,0))
|
||||
|
||||
x_axis = y_axis.cross(z_axis).normalized()
|
||||
y_axis = z_axis.cross(x_axis).normalized()
|
||||
|
||||
|
||||
@@ -133,7 +133,7 @@ class DumbSlabGenerator:
|
||||
|
||||
props = bpy.context.scene.BIMModelProperties
|
||||
self.collection = bpy.context.view_layer.active_layer_collection.collection
|
||||
self.collection_obj = bpy.data.objects.get(self.collection.name)
|
||||
self.collection_obj = self.collection.BIMCollectionProperties.obj
|
||||
self.depth = sum(thicknesses) * unit_scale
|
||||
self.width = 3
|
||||
self.length = 3
|
||||
|
||||
@@ -26,7 +26,7 @@ import blenderbim.tool as tool
|
||||
import blenderbim.core.type
|
||||
from math import pi
|
||||
from mathutils import Vector, Matrix
|
||||
from shapely import Polygon
|
||||
from shapely import Polygon, MultiPolygon
|
||||
|
||||
|
||||
class GenerateSpace(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -38,7 +38,7 @@ class GenerateSpace(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
collection = context.view_layer.active_layer_collection.collection
|
||||
collection_obj = bpy.data.objects.get(collection.name)
|
||||
collection_obj = collection.BIMCollectionProperties.obj
|
||||
return tool.Ifc.get_entity(collection_obj)
|
||||
|
||||
def _execute(self, context):
|
||||
@@ -60,7 +60,7 @@ class GenerateSpace(bpy.types.Operator, tool.Ifc.Operator):
|
||||
relating_type = None
|
||||
|
||||
collection = context.view_layer.active_layer_collection.collection
|
||||
collection_obj = bpy.data.objects.get(collection.name)
|
||||
collection_obj = collection.BIMCollectionProperties.obj
|
||||
if not collection_obj:
|
||||
bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
|
||||
return
|
||||
@@ -185,7 +185,7 @@ class GenerateSpacesFromWalls(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.generate_spaces_from_walls"
|
||||
bl_label = "Generate Spaces From Walls"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Generate spaces from selected walls. The active object must be a wall."
|
||||
bl_description = "Generate spaces from selected walls. The active object must be a wall"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -203,29 +203,19 @@ class GenerateSpacesFromWalls(bpy.types.Operator, tool.Ifc.Operator):
|
||||
active_obj = bpy.context.active_object
|
||||
|
||||
if not active_obj:
|
||||
self.report({'ERROR'}, "No active object. Please select a wall")
|
||||
self.report({"ERROR"}, "No active object. Please select a wall")
|
||||
return
|
||||
|
||||
element = None
|
||||
element = tool.Ifc.get_entity(active_obj)
|
||||
if element:
|
||||
if not element.is_a("IfcWall"):
|
||||
self.report({'ERROR'}, "The active object is not a wall. Please select a wall.")
|
||||
return
|
||||
if element and not element.is_a("IfcWall"):
|
||||
return self.report({"ERROR"}, "The active object is not a wall. Please select a wall.")
|
||||
|
||||
collection = active_obj.users_collection[0]
|
||||
collection_obj = bpy.data.objects.get(collection.name)
|
||||
if not collection_obj:
|
||||
self.report({'ERROR'}, "No collection found. Please insert one.")
|
||||
return
|
||||
|
||||
spatial_element = tool.Ifc.get_entity(collection_obj)
|
||||
if not spatial_element:
|
||||
self.report({'ERROR'}, "The collection hasn't an ifc space entity. Please provide one.")
|
||||
return
|
||||
container = ifcopenshell.util.element.get_container(element)
|
||||
if not container:
|
||||
self.report({"ERROR"}, "The wall is not contained.")
|
||||
|
||||
if not bpy.context.selected_objects:
|
||||
self.report({'ERROR'}, "No selected objects found. Please select walls.")
|
||||
self.report({"ERROR"}, "No selected objects found. Please select walls.")
|
||||
return
|
||||
|
||||
x, y, z = active_obj.matrix_world.translation.xyz
|
||||
@@ -237,19 +227,20 @@ class GenerateSpacesFromWalls(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
polys = self.get_polygons(boundary_elements)
|
||||
|
||||
converted_tolerance = self.get_converted_tolerance(tolerance = 0.03)
|
||||
converted_tolerance = self.get_converted_tolerance(tolerance=0.03)
|
||||
|
||||
union = shapely.ops.unary_union(polys).buffer(converted_tolerance, cap_style = 2, join_style = 2)
|
||||
union = shapely.ops.unary_union(polys).buffer(converted_tolerance, cap_style=2, join_style=2)
|
||||
|
||||
i=0
|
||||
for linear_ring in union.interiors:
|
||||
union = self.get_purged_inner_holes_poly(union_geom = union, min_area = self.get_converted_tolerance(tolerance = 3))
|
||||
|
||||
for i, linear_ring in enumerate(union.interiors):
|
||||
poly = Polygon(linear_ring)
|
||||
poly = poly.buffer(converted_tolerance, single_sided=True, cap_style = 2, join_style = 2)
|
||||
poly = poly.buffer(converted_tolerance, single_sided=True, cap_style=2, join_style=2)
|
||||
|
||||
bm = self.get_bmesh_from_polygon(poly, mat, h)
|
||||
|
||||
name = "Space" + str(i)
|
||||
mesh = bpy.data.meshes.new(name = name)
|
||||
mesh = bpy.data.meshes.new(name=name)
|
||||
bm.to_mesh(mesh)
|
||||
bm.free()
|
||||
|
||||
@@ -258,19 +249,21 @@ class GenerateSpacesFromWalls(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
self.set_obj_origin_to_bboxcenter(obj)
|
||||
|
||||
collection.objects.link(obj)
|
||||
if z != 0:
|
||||
obj.location = obj.location + Vector((0,0,z))
|
||||
|
||||
context.view_layer.active_layer_collection.collection.objects.link(obj)
|
||||
bpy.ops.bim.assign_class(obj=obj.name, ifc_class="IfcSpace")
|
||||
i+=1
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
container_obj = tool.Ifc.get_object(container)
|
||||
blenderbim.core.spatial.assign_container(
|
||||
tool.Ifc, tool.Collector, tool.Spatial, structure_obj=container_obj, element_obj=obj
|
||||
)
|
||||
|
||||
def get_boundary_elements(self, selected_objects):
|
||||
boundary_elements = []
|
||||
for obj in selected_objects:
|
||||
subelement = tool.Ifc.get_entity(obj)
|
||||
if subelement.is_a("IfcWall"):
|
||||
if subelement.is_a("IfcWall") or subelement.is_a("IfcColumn"):
|
||||
boundary_elements.append(subelement)
|
||||
return boundary_elements
|
||||
|
||||
@@ -302,17 +295,40 @@ class GenerateSpacesFromWalls(bpy.types.Operator, tool.Ifc.Operator):
|
||||
def get_converted_tolerance(self, tolerance):
|
||||
model = tool.Ifc.get()
|
||||
project_unit = ifcopenshell.util.unit.get_project_unit(model, "LENGTHUNIT")
|
||||
prefix=getattr(project_unit, "Prefix", None)
|
||||
prefix = getattr(project_unit, "Prefix", None)
|
||||
|
||||
converted_tolerance = ifcopenshell.util.unit.convert(
|
||||
value = tolerance,
|
||||
from_prefix = None,
|
||||
from_unit = "METRE",
|
||||
to_prefix = prefix,
|
||||
to_unit = project_unit.Name,
|
||||
)
|
||||
value=tolerance,
|
||||
from_prefix=None,
|
||||
from_unit="METRE",
|
||||
to_prefix=prefix,
|
||||
to_unit=project_unit.Name,
|
||||
)
|
||||
return tolerance
|
||||
|
||||
def get_purged_inner_holes_poly(self, union_geom, min_area):
|
||||
interiors_list = []
|
||||
|
||||
if union_geom.geom_type == "MultiPolygon":
|
||||
for poly in union_geom.geoms:
|
||||
interiors_list = self.get_poly_valid_interior_list(poly = poly, min_area = min_area, interiors_list = interiors_list)
|
||||
|
||||
new_poly = Polygon(poly.exterior.coords, holes = interiors_list)
|
||||
|
||||
if union_geom.geom_type == "Polygon":
|
||||
interiors_list = self.get_poly_valid_interior_list(poly = union_geom, min_area = min_area, interiors_list = interiors_list)
|
||||
new_poly = Polygon(union_geom.exterior.coords, holes = interiors_list)
|
||||
|
||||
return new_poly
|
||||
|
||||
def get_poly_valid_interior_list(self, poly, min_area, interiors_list):
|
||||
for interior in poly.interiors:
|
||||
p = Polygon(interior)
|
||||
if p.area >= min_area:
|
||||
interiors_list.append(interior)
|
||||
return interiors_list
|
||||
|
||||
|
||||
def get_bmesh_from_polygon(self, poly, mat, h):
|
||||
bm = bmesh.new()
|
||||
bm.verts.index_update()
|
||||
@@ -335,7 +351,7 @@ class GenerateSpacesFromWalls(bpy.types.Operator, tool.Ifc.Operator):
|
||||
extruded_verts = [g for g in extrusion["geom"] if isinstance(g, bmesh.types.BMVert)]
|
||||
bmesh.ops.translate(bm, vec=[0.0, 0.0, h], verts=extruded_verts)
|
||||
|
||||
bmesh.ops.recalc_face_normals(bm, faces = bm.faces)
|
||||
bmesh.ops.recalc_face_normals(bm, faces=bm.faces)
|
||||
|
||||
return bm
|
||||
|
||||
@@ -353,3 +369,35 @@ class GenerateSpacesFromWalls(bpy.types.Operator, tool.Ifc.Operator):
|
||||
aux_vector = aux_vector - diff
|
||||
vert.co = inverted @ aux_vector
|
||||
obj.location = newLoc
|
||||
|
||||
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):
|
||||
model = tool.Ifc.get()
|
||||
|
||||
spaces = model.by_type('IfcSpace')
|
||||
|
||||
if not spaces:
|
||||
print(spaces)
|
||||
return {"FINISHED"}
|
||||
|
||||
first_obj = tool.Ifc.get_object(spaces[0])
|
||||
|
||||
if bpy.data.objects[first_obj.name].display_type == 'TEXTURED':
|
||||
for space in spaces:
|
||||
obj = tool.Ifc.get_object(space)
|
||||
bpy.data.objects[obj.name].show_wire = True
|
||||
bpy.data.objects[obj.name].display_type = 'WIRE'
|
||||
return {"FINISHED"}
|
||||
|
||||
elif bpy.data.objects[first_obj.name].display_type == 'WIRE':
|
||||
for space in spaces:
|
||||
obj = tool.Ifc.get_object(space)
|
||||
bpy.data.objects[obj.name].show_wire = False
|
||||
bpy.data.objects[obj.name].display_type = 'TEXTURED'
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -250,7 +250,8 @@ def update_ifc_stair_props(obj):
|
||||
props = obj.BIMStairProperties
|
||||
ifc_file = tool.Ifc.get()
|
||||
|
||||
element.PredefinedType = "STRAIGHT"
|
||||
if tool.Ifc.get_schema() != "IFC2X3":
|
||||
element.PredefinedType = "STRAIGHT"
|
||||
number_of_risers = props.number_of_treads + 1
|
||||
# update IfcStairFlight properties (seems already deprecated but keep it for now)
|
||||
# http://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcStairFlight.htm
|
||||
@@ -260,7 +261,11 @@ def update_ifc_stair_props(obj):
|
||||
tread_length = props.tread_depth / si_conversion
|
||||
|
||||
if element.is_a("IfcStairFlight"):
|
||||
element.NumberOfRisers = number_of_risers
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
element.NumberOfRiser = number_of_risers
|
||||
else:
|
||||
element.NumberOfRisers = number_of_risers
|
||||
|
||||
element.NumberOfTreads = props.number_of_treads
|
||||
element.RiserHeight = riser_height
|
||||
element.TreadLength = tread_length
|
||||
@@ -330,7 +335,8 @@ class BIM_OT_add_clever_stair(bpy.types.Operator, tool.Ifc.Operator):
|
||||
should_add_representation=True,
|
||||
context=body_context,
|
||||
)
|
||||
element.PredefinedType = "STRAIGHT"
|
||||
if tool.Ifc.get_schema() != "IFC2X3":
|
||||
element.PredefinedType = "STRAIGHT"
|
||||
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
bpy.context.view_layer.objects.active = None
|
||||
|
||||
@@ -135,32 +135,24 @@ class BIM_PT_authoring(Panel):
|
||||
row.operator("bim.generate_space")
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.generate_spaces_from_walls")
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.toggle_space_visibility")
|
||||
|
||||
|
||||
class BIM_PT_GridsSpatialManager(Panel):
|
||||
bl_label = "Grids and Containers"
|
||||
bl_idname = "BIM_PT_GridsSpatialManager"
|
||||
class BIM_PT_Grids(Panel):
|
||||
bl_label = "Grids"
|
||||
bl_idname = "BIM_PT_Grids"
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "UI"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_category = "BlenderBIM"
|
||||
|
||||
def draw(self, context):
|
||||
pass
|
||||
|
||||
class BIM_PT_Grids(Panel):
|
||||
bl_label = "Grid Creator"
|
||||
bl_idname = "BIM_PT_Grids"
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "UI"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_parent_id = "BIM_PT_GridsSpatialManager"
|
||||
|
||||
def draw(self, context):
|
||||
self.animation_props = context.scene.BIMAnimationProperties
|
||||
row = self.layout.row()
|
||||
row.operator("mesh.add_grid", icon="ADD", text="Add Grids")
|
||||
|
||||
|
||||
class BIM_PT_array(bpy.types.Panel):
|
||||
bl_label = "IFC Array"
|
||||
bl_idname = "BIM_PT_array"
|
||||
|
||||
@@ -434,7 +434,7 @@ class DumbWallGenerator:
|
||||
|
||||
props = bpy.context.scene.BIMModelProperties
|
||||
self.collection = bpy.context.view_layer.active_layer_collection.collection
|
||||
self.collection_obj = bpy.data.objects.get(self.collection.name)
|
||||
self.collection_obj = self.collection.BIMCollectionProperties.obj
|
||||
self.width = self.layers["thickness"]
|
||||
self.height = props.extrusion_depth
|
||||
self.length = props.length
|
||||
@@ -934,7 +934,8 @@ class DumbWallJoiner:
|
||||
def duplicate_wall(self, wall1):
|
||||
wall2 = wall1.copy()
|
||||
wall2.data = wall2.data.copy()
|
||||
wall1.users_collection[0].objects.link(wall2)
|
||||
for collection in wall1.users_collection:
|
||||
collection.objects.link(wall2)
|
||||
blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=wall2)
|
||||
return wall2
|
||||
|
||||
|
||||
@@ -167,7 +167,8 @@ def update_window_modifier_representation(context):
|
||||
)
|
||||
|
||||
# type attributes
|
||||
element.PartitioningType = props.window_type
|
||||
if tool.Ifc.get_schema() != "IFC2X3":
|
||||
element.PartitioningType = props.window_type
|
||||
|
||||
# occurences attributes
|
||||
occurences = tool.Ifc.get_all_element_occurences(element)
|
||||
@@ -262,14 +263,19 @@ def create_bm_window(
|
||||
frame_thickness,
|
||||
glass_thickness,
|
||||
position: Vector,
|
||||
x_offsets: list = None,
|
||||
):
|
||||
"""`lining_thickness` expected to be defined as a list,
|
||||
"""`lining_thickness` and `x_offsets` are expected to be defined as a list,
|
||||
similarly to `create_bm_window_frame` `thickness` argument"""
|
||||
|
||||
if x_offsets is None:
|
||||
x_offsets = [lining_to_panel_offset_x] * 4
|
||||
|
||||
# window lining
|
||||
window_lining_verts = create_bm_window_frame(bm, lining_size, lining_thickness)
|
||||
|
||||
# window frame
|
||||
frame_position = V(lining_to_panel_offset_x, lining_to_panel_offset_y_full, lining_to_panel_offset_x)
|
||||
frame_position = V(x_offsets[0], lining_to_panel_offset_y_full, x_offsets[3])
|
||||
frame_verts = create_bm_window_frame(bm, frame_size, frame_thickness, frame_position)
|
||||
|
||||
# window glass
|
||||
@@ -319,21 +325,35 @@ def update_window_modifier_bmesh(context):
|
||||
unique_cols = len(set(panel_row))
|
||||
|
||||
for column_i, panel_i in enumerate(panel_row):
|
||||
# detect mullion
|
||||
has_mullion = unique_cols > 1
|
||||
first_column = column_i == 0
|
||||
last_column = column_i == unique_cols - 1
|
||||
left_to_mullion = has_mullion and not last_column
|
||||
right_to_mullion = has_mullion and not first_column
|
||||
|
||||
# detect transom
|
||||
has_transom = unique_rows_in_col[column_i] > 1
|
||||
first_row = row_i == 0
|
||||
last_row = row_i == unique_rows_in_col[column_i] - 1
|
||||
top_to_transom = has_transom and not first_row
|
||||
bottom_to_transom = has_transom and not last_row
|
||||
|
||||
# calculate current panel dimensions
|
||||
if unique_cols > 1:
|
||||
if column_i == 0:
|
||||
if has_mullion:
|
||||
if first_column:
|
||||
panel_width = first_mullion_offset
|
||||
elif column_i == unique_cols - 1:
|
||||
elif last_column:
|
||||
panel_width = overall_width - accumulated_width
|
||||
else:
|
||||
panel_width = second_mullion_offset - accumulated_width
|
||||
else:
|
||||
panel_width = overall_width
|
||||
|
||||
if unique_rows_in_col[column_i] > 1:
|
||||
if row_i == 0:
|
||||
if has_transom:
|
||||
if first_row:
|
||||
panel_height = first_transom_offset
|
||||
elif row_i == unique_rows_in_col[column_i] - 1:
|
||||
elif last_row:
|
||||
panel_height = overall_height - accumulated_height[column_i]
|
||||
else:
|
||||
panel_height = second_transom_offset - accumulated_height[column_i]
|
||||
@@ -347,7 +367,7 @@ def update_window_modifier_bmesh(context):
|
||||
|
||||
frame_depth = props.frame_depth[panel_i]
|
||||
frame_thickness = props.frame_thickness[panel_i]
|
||||
|
||||
lining_to_panel_offset_y_full = (lining_depth - frame_depth) + lining_to_panel_offset_y
|
||||
# add window
|
||||
window_lining_size = V(
|
||||
panel_width,
|
||||
@@ -355,25 +375,33 @@ def update_window_modifier_bmesh(context):
|
||||
panel_height,
|
||||
)
|
||||
|
||||
# calculate lining thickness
|
||||
# calculate lining thickness and frame size / offset
|
||||
# taking into account mullions and transoms
|
||||
window_lining_thickness = [lining_thickness] * 4
|
||||
# mullion thickness
|
||||
if unique_cols > 1:
|
||||
if column_i != 0:
|
||||
window_lining_thickness[0] = mullion_thickness # left column
|
||||
if column_i != unique_cols - 1:
|
||||
window_lining_thickness[2] = mullion_thickness # right column
|
||||
# transom thickness
|
||||
if unique_rows_in_col[column_i] > 1:
|
||||
if row_i != 0:
|
||||
window_lining_thickness[3] = transom_thickness # bottom row
|
||||
if row_i != unique_rows_in_col[column_i] - 1:
|
||||
window_lining_thickness[1] = transom_thickness # top row
|
||||
# fmt: off
|
||||
window_lining_thickness = [
|
||||
mullion_thickness if right_to_mullion else lining_thickness,
|
||||
transom_thickness if bottom_to_transom else lining_thickness,
|
||||
mullion_thickness if left_to_mullion else lining_thickness,
|
||||
transom_thickness if top_to_transom else lining_thickness,
|
||||
]
|
||||
|
||||
# x offsets can differ if there are mullions or transoms because we're trying to maintain symmetry
|
||||
base_frame_clear = lining_to_panel_offset_x + frame_thickness - lining_thickness
|
||||
current_offset_x = base_frame_clear - frame_thickness + mullion_thickness
|
||||
current_offset_z = base_frame_clear - frame_thickness + transom_thickness
|
||||
# fmt: off
|
||||
x_offsets = [
|
||||
current_offset_x if right_to_mullion else lining_to_panel_offset_x, # LEFT
|
||||
current_offset_z if bottom_to_transom else lining_to_panel_offset_x, # TOP
|
||||
current_offset_x if left_to_mullion else lining_to_panel_offset_x, # RIGHT
|
||||
current_offset_z if top_to_transom else lining_to_panel_offset_x, # BOTTOM
|
||||
]
|
||||
# fmt: on
|
||||
|
||||
frame_size = window_lining_size.copy()
|
||||
frame_size.y = frame_depth
|
||||
frame_size = frame_size - V(lining_to_panel_offset_x * 2, 0, lining_to_panel_offset_x * 2)
|
||||
frame_size.x -= x_offsets[0] + x_offsets[2]
|
||||
frame_size.z -= x_offsets[1] + x_offsets[3]
|
||||
|
||||
window_position = V(accumulated_width, 0, accumulated_height[column_i])
|
||||
lining_verts, panel_verts, glass_verts = create_bm_window(
|
||||
@@ -381,11 +409,12 @@ def update_window_modifier_bmesh(context):
|
||||
window_lining_size,
|
||||
window_lining_thickness,
|
||||
lining_to_panel_offset_x,
|
||||
(lining_depth - frame_depth) + lining_to_panel_offset_y,
|
||||
lining_to_panel_offset_y_full,
|
||||
frame_size,
|
||||
frame_thickness,
|
||||
glass_thickness,
|
||||
window_position,
|
||||
x_offsets,
|
||||
)
|
||||
|
||||
built_panels.append(panel_i)
|
||||
@@ -427,7 +456,8 @@ class BIM_OT_add_window(bpy.types.Operator, tool.Ifc.Operator):
|
||||
element = blenderbim.core.root.assign_class(
|
||||
tool.Ifc, tool.Collector, tool.Root, obj=obj, ifc_class="IfcWindow", should_add_representation=False
|
||||
)
|
||||
element.PredefinedType = "WINDOW"
|
||||
if tool.Ifc.get_schema() != "IFC2X3":
|
||||
element.PredefinedType = "WINDOW"
|
||||
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
bpy.context.view_layer.objects.active = None
|
||||
@@ -448,8 +478,8 @@ class AddWindow(bpy.types.Operator, tool.Ifc.Operator):
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
props = obj.BIMWindowProperties
|
||||
|
||||
if element.is_a() not in ("IfcWindow", "IfcWindowType"):
|
||||
self.report({"ERROR"}, "Object has to be IfcWindow/IfcWindowType type to add a window.")
|
||||
if element.is_a() not in ("IfcWindow", "IfcWindowType", "IfcWindowStyle"):
|
||||
self.report({"ERROR"}, "Object has to be IfcWindow/IfcWindowType/IfcWindowStyle type to add a window.")
|
||||
return {"CANCELLED"}
|
||||
|
||||
window_data = props.get_general_kwargs(convert_to_project_units=True)
|
||||
@@ -481,6 +511,8 @@ class CancelEditingWindow(bpy.types.Operator, tool.Ifc.Operator):
|
||||
obj = context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Window", "Data"))
|
||||
data.update(data.pop("lining_properties"))
|
||||
data.update(data.pop("panel_properties"))
|
||||
props = obj.BIMWindowProperties
|
||||
props.set_props_kwargs_from_ifc_data(data)
|
||||
|
||||
|
||||
@@ -57,7 +57,6 @@ class BimTool(WorkSpaceTool):
|
||||
("bim.hotkey", {"type": "V", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_V")]}),
|
||||
("bim.hotkey", {"type": "X", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_X")]}),
|
||||
("bim.hotkey", {"type": "Y", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_Y")]}),
|
||||
("bim.hotkey", {"type": "B", "value": "PRESS", "alt": True}, {"properties": [("hotkey", "A_B")]}),
|
||||
("bim.hotkey", {"type": "D", "value": "PRESS", "alt": True}, {"properties": [("hotkey", "A_D")]}),
|
||||
("bim.hotkey", {"type": "E", "value": "PRESS", "alt": True}, {"properties": [("hotkey", "A_E")]}),
|
||||
("bim.hotkey", {"type": "O", "value": "PRESS", "alt": True}, {"properties": [("hotkey", "A_O")]}),
|
||||
@@ -271,12 +270,6 @@ class BimToolUI:
|
||||
else:
|
||||
row.operator("bim.show_openings", icon="HIDE_OFF", text="")
|
||||
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_B")
|
||||
row.prop(cls.props, "boundary_class", text="")
|
||||
row.operator("bim.add_boundary", text="Add Boundary")
|
||||
|
||||
cls.layout.row(align=True).label(text="Align")
|
||||
add_layout_hotkey_operator(cls.layout, "Align Exterior", "S_X", "")
|
||||
add_layout_hotkey_operator(cls.layout, "Align Centerline", "S_C", "")
|
||||
@@ -286,7 +279,6 @@ class BimToolUI:
|
||||
cls.layout.row(align=True).label(text="Mode")
|
||||
add_layout_hotkey_operator(cls.layout, "Void", "A_O", "Toggle openings")
|
||||
add_layout_hotkey_operator(cls.layout, "Decomposition", "A_D", "Select decomposition")
|
||||
add_layout_hotkey_operator(cls.layout, "Boundaries", "A_B", "Toggle boundaries")
|
||||
|
||||
@classmethod
|
||||
def draw_header_interface(cls):
|
||||
@@ -602,14 +594,6 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
|
||||
self.props.y = self.y
|
||||
self.props.z = self.z
|
||||
|
||||
def hotkey_A_B(self):
|
||||
if not bpy.context.selected_objects:
|
||||
return
|
||||
if AuthoringData.data["has_visible_boundaries"]:
|
||||
bpy.ops.bim.hide_boundaries()
|
||||
else:
|
||||
bpy.ops.bim.show_boundaries()
|
||||
|
||||
def hotkey_A_D(self):
|
||||
if not bpy.context.selected_objects:
|
||||
return
|
||||
|
||||
@@ -74,19 +74,21 @@ class SelectLibraryFile(bpy.types.Operator, IFCFileSelector):
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"})
|
||||
append_all: bpy.props.BoolProperty(default=False)
|
||||
use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=False)
|
||||
|
||||
def execute(self, context):
|
||||
IfcStore.begin_transaction(self)
|
||||
old_filepath = IfcStore.library_path
|
||||
result = self._execute(context)
|
||||
self.transaction_data = {"old_filepath": old_filepath, "filepath": self.filepath}
|
||||
self.transaction_data = {"old_filepath": old_filepath, "filepath": self.get_filepath()}
|
||||
IfcStore.add_transaction_operation(self)
|
||||
IfcStore.end_transaction(self)
|
||||
return result
|
||||
|
||||
def _execute(self, context):
|
||||
IfcStore.library_path = self.filepath
|
||||
IfcStore.library_file = ifcopenshell.open(self.filepath)
|
||||
filepath = self.get_filepath()
|
||||
IfcStore.library_path = filepath
|
||||
IfcStore.library_file = ifcopenshell.open(filepath)
|
||||
bpy.ops.bim.refresh_library()
|
||||
if context.area:
|
||||
context.area.tag_redraw()
|
||||
@@ -371,7 +373,8 @@ class AppendLibraryElement(bpy.types.Operator):
|
||||
if not type_collection:
|
||||
type_collection = bpy.data.collections.new("Types")
|
||||
for collection in bpy.context.view_layer.layer_collection.children:
|
||||
if "IfcProject/" in collection.name:
|
||||
collection_obj = collection.collection.BIMCollectionProperties.obj
|
||||
if collection_obj and tool.Ifc.get_entity(collection_obj).is_a("IfcProject"):
|
||||
collection.collection.children.link(type_collection)
|
||||
collection.children["Types"].hide_viewport = True
|
||||
break
|
||||
@@ -522,13 +525,14 @@ class LoadProject(bpy.types.Operator, IFCFileSelector):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Load an existing IFC project"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"})
|
||||
filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml;*.ifcsqlite", options={"HIDDEN"})
|
||||
is_advanced: bpy.props.BoolProperty(name="Enable Advanced Mode", default=False)
|
||||
use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=False)
|
||||
|
||||
def execute(self, context):
|
||||
if not self.is_existing_ifc_file():
|
||||
return {"FINISHED"}
|
||||
context.scene.BIMProperties.ifc_file = self.filepath
|
||||
context.scene.BIMProperties.ifc_file = self.get_filepath()
|
||||
context.scene.BIMProjectProperties.is_loading = True
|
||||
context.scene.BIMProjectProperties.total_elements = len(tool.Ifc.get().by_type("IfcElement"))
|
||||
if not self.is_advanced:
|
||||
@@ -817,10 +821,22 @@ class ExportIFC(bpy.types.Operator):
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
json_version: bpy.props.EnumProperty(items=[("4", "4", ""), ("5a", "5a", "")], name="IFC JSON Version")
|
||||
json_compact: bpy.props.BoolProperty(name="Export Compact IFCJSON", default=False)
|
||||
should_save_as: bpy.props.BoolProperty(name="Should Save As", default=False)
|
||||
should_save_as: bpy.props.BoolProperty(name="Should Save As", default=False, options={"HIDDEN"})
|
||||
use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=False)
|
||||
save_as_invoked: bpy.props.BoolProperty(name="Save As Dialog Was Invoked", default=False, options={"HIDDEN"})
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.prop(self, "json_version")
|
||||
layout.prop(self, "json_compact")
|
||||
if bpy.data.is_saved:
|
||||
layout.prop(self, "use_relative_path")
|
||||
else:
|
||||
layout.label(text="Save the .blend file first ")
|
||||
layout.label(text="to use relative paths for .ifc.")
|
||||
|
||||
def invoke(self, context, event):
|
||||
self.save_as_invoked = False
|
||||
if not IfcStore.get_file():
|
||||
self.report({"ERROR"}, "No IFC project is available for export - create or import a project first.")
|
||||
return {"FINISHED"}
|
||||
@@ -834,18 +850,23 @@ class ExportIFC(bpy.types.Operator):
|
||||
self.filepath = Path(bpy.data.filepath).with_suffix(".ifc").__str__()
|
||||
else:
|
||||
self.filepath = "untitled.ifc"
|
||||
|
||||
self.save_as_invoked = True
|
||||
WindowManager = context.window_manager
|
||||
WindowManager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
def execute(self, context):
|
||||
if context.scene.BIMProjectProperties.should_disable_undo_on_save:
|
||||
project_props = context.scene.BIMProjectProperties
|
||||
if self.save_as_invoked:
|
||||
project_props.use_relative_project_path = self.use_relative_path
|
||||
if project_props.should_disable_undo_on_save:
|
||||
old_history_size = tool.Ifc.get().history_size
|
||||
old_undo_steps = context.preferences.edit.undo_steps
|
||||
tool.Ifc.get().history_size = 0
|
||||
context.preferences.edit.undo_steps = 0
|
||||
IfcStore.execute_ifc_operator(self, context)
|
||||
if context.scene.BIMProjectProperties.should_disable_undo_on_save:
|
||||
if project_props.should_disable_undo_on_save:
|
||||
tool.Ifc.get().history_size = old_history_size
|
||||
context.preferences.edit.undo_steps = old_undo_steps
|
||||
return {"FINISHED"}
|
||||
@@ -882,7 +903,7 @@ class ExportIFC(bpy.types.Operator):
|
||||
if not scene.DocProperties.ifc_files:
|
||||
new = scene.DocProperties.ifc_files.add()
|
||||
new.name = output_file
|
||||
if self.use_relative_path and bpy.data.is_saved:
|
||||
if context.scene.BIMProjectProperties.use_relative_project_path and bpy.data.is_saved:
|
||||
output_file = os.path.relpath(output_file, bpy.path.abspath("//"))
|
||||
if scene.BIMProperties.ifc_file != output_file and extension not in ["ifczip", "ifcjson"]:
|
||||
scene.BIMProperties.ifc_file = output_file
|
||||
|
||||
@@ -148,6 +148,8 @@ class BIMProjectProperties(PropertyGroup):
|
||||
default="NONE",
|
||||
)
|
||||
should_merge_materials_by_colour: BoolProperty(name="Merge Materials by Colour", default=False)
|
||||
should_stream: BoolProperty(name="Stream Data From IFC-SPF (Only for advanced users)", default=False)
|
||||
should_load_geometry: BoolProperty(name="Load Geometry", default=True)
|
||||
should_use_native_meshes: BoolProperty(name="Native Meshes", default=False)
|
||||
should_clean_mesh: BoolProperty(name="Clean Meshes", default=True)
|
||||
should_cache: BoolProperty(name="Cache", default=False)
|
||||
@@ -165,6 +167,7 @@ class BIMProjectProperties(PropertyGroup):
|
||||
active_link_index: IntProperty(name="Active Link Index")
|
||||
export_schema: EnumProperty(items=get_export_schema, name="IFC Schema")
|
||||
template_file: EnumProperty(items=get_template_file, name="Template File")
|
||||
use_relative_project_path: BoolProperty(name="Use Relative Project Path", default=False)
|
||||
|
||||
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))
|
||||
|
||||
@@ -77,6 +77,8 @@ class BIM_PT_project(Panel):
|
||||
row = self.layout.row()
|
||||
row.prop(pprops, "should_cache")
|
||||
row = self.layout.row()
|
||||
row.prop(pprops, "should_load_geometry")
|
||||
row = self.layout.row()
|
||||
row.prop(pprops, "should_use_native_meshes")
|
||||
row = self.layout.row()
|
||||
row.prop(pprops, "should_merge_materials_by_colour")
|
||||
|
||||
@@ -18,7 +18,9 @@
|
||||
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.doc
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.bim.schema
|
||||
|
||||
|
||||
# TODO: Should this cache belong here? Dunno. Maybe.
|
||||
@@ -67,6 +69,8 @@ class ObjectPsetsData(Data):
|
||||
cls.data = {
|
||||
"psets": cls.psetqtos(tool.Ifc.get_entity(bpy.context.active_object), psets_only=True),
|
||||
"inherited_psets": cls.inherited_psets(),
|
||||
"pset_name": cls.pset_name(),
|
||||
"qto_name": cls.qto_name(),
|
||||
}
|
||||
cls.is_loaded = True
|
||||
|
||||
@@ -79,6 +83,35 @@ class ObjectPsetsData(Data):
|
||||
if element_type:
|
||||
return cls.psetqtos(element_type)
|
||||
|
||||
@classmethod
|
||||
def pset_name(cls):
|
||||
obj = bpy.context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
return []
|
||||
psets = blenderbim.bim.schema.ifc.psetqto.get_applicable(element.is_a(), pset_only=True)
|
||||
psetnames = cls.format_pset_enum(psets)
|
||||
assigned_names = ifcopenshell.util.element.get_psets(element, psets_only=True).keys()
|
||||
return [p for p in psetnames if p[0] not in assigned_names]
|
||||
|
||||
@classmethod
|
||||
def qto_name(cls):
|
||||
obj = bpy.context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
return []
|
||||
qtos = blenderbim.bim.schema.ifc.psetqto.get_applicable(element.is_a(), qto_only=True)
|
||||
return cls.format_pset_enum(qtos)
|
||||
|
||||
@classmethod
|
||||
def format_pset_enum(cls, psets):
|
||||
enum_items = []
|
||||
version = tool.Ifc.get_schema()
|
||||
for pset in psets:
|
||||
doc = ifcopenshell.util.doc.get_property_set_doc(version, pset.Name) or {}
|
||||
enum_items.append((pset.Name, pset.Name, doc.get("description", "")))
|
||||
return enum_items
|
||||
|
||||
|
||||
class ObjectQtosData(Data):
|
||||
data = {}
|
||||
|
||||
@@ -120,6 +120,13 @@ class EnablePsetEditing(bpy.types.Operator):
|
||||
self.load_single_value(pset_template, prop_template, data)
|
||||
elif prop_template.TemplateType == "P_ENUMERATEDVALUE":
|
||||
self.load_enumerated_value(prop_template, data)
|
||||
else:
|
||||
# NOTE: currently unsupported types:
|
||||
# - P_BOUNDEDVALUE
|
||||
# - P_LISTVALUE
|
||||
# - P_REFERENCEVALUE
|
||||
# - P_TABLEVALUE
|
||||
pass
|
||||
|
||||
def load_single_value(self, pset_template, prop_template, data):
|
||||
prop = self.props.properties.add()
|
||||
@@ -137,8 +144,12 @@ class EnablePsetEditing(bpy.types.Operator):
|
||||
if prop_template.PrimaryMeasureType in (
|
||||
"IfcPositiveLengthMeasure",
|
||||
"IfcLengthMeasure",
|
||||
) or prop_template.TemplateType in ("Q_LENGTH",):
|
||||
) or prop_template.TemplateType == "Q_LENGTH":
|
||||
special_type = "LENGTH"
|
||||
elif prop_template.PrimaryMeasureType == "IfcAreaMeasure" or prop_template.TemplateType == "Q_AREA":
|
||||
special_type = "AREA"
|
||||
elif prop_template.PrimaryMeasureType == "IfcVolumeMeasure" or prop_template.TemplateType == "Q_VOLUME":
|
||||
special_type = "VOLUME"
|
||||
metadata.special_type = special_type
|
||||
|
||||
if metadata.data_type == "string":
|
||||
|
||||
@@ -22,7 +22,7 @@ import ifcopenshell
|
||||
import ifcopenshell.util.element
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim.prop import Attribute, StrProperty
|
||||
from blenderbim.bim.module.pset.data import AddEditCustomPropertiesData
|
||||
from blenderbim.bim.module.pset.data import AddEditCustomPropertiesData, ObjectPsetsData
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
@@ -57,18 +57,10 @@ def blender_formatted_enum_from_psets(psets):
|
||||
return enum_items
|
||||
|
||||
|
||||
def get_pset_names(self, context):
|
||||
global psetnames
|
||||
obj = context.active_object
|
||||
if not obj.BIMObjectProperties.ifc_definition_id:
|
||||
return []
|
||||
element = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
ifc_class = element.is_a()
|
||||
if ifc_class not in psetnames:
|
||||
psets = blenderbim.bim.schema.ifc.psetqto.get_applicable(ifc_class, pset_only=True)
|
||||
psetnames[ifc_class] = blender_formatted_enum_from_psets(psets)
|
||||
assigned_names = ifcopenshell.util.element.get_psets(element, psets_only=True).keys()
|
||||
return [p for p in psetnames[ifc_class] if p[0] not in assigned_names]
|
||||
def get_pset_name(self, context):
|
||||
if not ObjectPsetsData.is_loaded:
|
||||
ObjectPsetsData.load()
|
||||
return ObjectPsetsData.data["pset_name"]
|
||||
|
||||
|
||||
def get_material_pset_names(self, context):
|
||||
@@ -157,15 +149,10 @@ def get_work_schedule_pset_names(self, context):
|
||||
return psetnames[ifc_class]
|
||||
|
||||
|
||||
def get_qto_names(self, context):
|
||||
global qtonames
|
||||
if "/" in context.active_object.name:
|
||||
ifc_class = context.active_object.name.split("/")[0]
|
||||
if ifc_class not in qtonames:
|
||||
psets = blenderbim.bim.schema.ifc.psetqto.get_applicable(ifc_class, qto_only=True)
|
||||
qtonames[ifc_class] = blender_formatted_enum_from_psets(psets)
|
||||
return qtonames[ifc_class]
|
||||
return []
|
||||
def get_qto_name(self, context):
|
||||
if not ObjectPsetsData.is_loaded:
|
||||
ObjectPsetsData.load()
|
||||
return ObjectPsetsData.data["qto_name"]
|
||||
|
||||
|
||||
def get_template_type(self, context):
|
||||
@@ -197,8 +184,8 @@ class PsetProperties(PropertyGroup):
|
||||
active_pset_name: StringProperty(name="Pset Name")
|
||||
active_pset_type: StringProperty(name="Active Pset Type")
|
||||
properties: CollectionProperty(name="Properties", type=IfcProperty)
|
||||
pset_name: EnumProperty(items=get_pset_names, name="Pset Name")
|
||||
qto_name: EnumProperty(items=get_qto_names, name="Qto Name")
|
||||
pset_name: EnumProperty(items=get_pset_name, name="Pset Name")
|
||||
qto_name: EnumProperty(items=get_qto_name, name="Qto Name")
|
||||
|
||||
|
||||
class MaterialPsetProperties(PropertyGroup):
|
||||
|
||||
@@ -49,7 +49,7 @@ def draw_single_property(prop, layout, copy_operator=None):
|
||||
layout.prop(
|
||||
prop.metadata,
|
||||
value_name,
|
||||
text=prop.metadata.name,
|
||||
text=prop.metadata.display_name,
|
||||
)
|
||||
if prop.metadata.is_uri:
|
||||
op = layout.operator("bim.select_uri_attribute", text="", icon="FILE_FOLDER")
|
||||
|
||||
@@ -168,6 +168,7 @@ class UnlinkObject(bpy.types.Operator):
|
||||
else:
|
||||
objects = context.selected_objects
|
||||
for obj in objects:
|
||||
object_name = obj.name
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element:
|
||||
if self.should_delete:
|
||||
@@ -186,8 +187,8 @@ class UnlinkObject(bpy.types.Operator):
|
||||
material_slot.material = material_slot.material.copy()
|
||||
blenderbim.core.style.unlink_style(tool.Ifc, tool.Style, obj=material_slot.material)
|
||||
blenderbim.core.material.unlink_material(tool.Ifc, obj=material_slot.material)
|
||||
if "Ifc" in obj.name and "/" in obj.name:
|
||||
obj.name = "/".join(obj.name.split("/")[1:])
|
||||
if "Ifc" in object_name and "/" in object_name:
|
||||
obj.name = object_name.split("/", 1)[1]
|
||||
return {"FINISHED"}
|
||||
|
||||
def draw(self, context):
|
||||
|
||||
@@ -261,7 +261,7 @@ def update_color_full(self, context):
|
||||
material = bpy.data.materials.get("color_full")
|
||||
if material:
|
||||
color_full = bpy.context.scene.BIMAnimationProperties.color_full
|
||||
inputs = material.node_tree.nodes["Principled BSDF"].inputs
|
||||
inputs = tool.Blender.get_material_node(material, "BSDF_PRINCIPLED").inputs
|
||||
color = inputs["Base Color"].default_value
|
||||
color[0] = color_full.r
|
||||
color[1] = color_full.g
|
||||
@@ -272,7 +272,7 @@ def update_color_progress(self, context):
|
||||
material = bpy.data.materials.get("color_progress")
|
||||
if material:
|
||||
color_progress = bpy.context.scene.BIMAnimationProperties.color_progress
|
||||
inputs = material.node_tree.nodes["Principled BSDF"].inputs
|
||||
inputs = tool.Blender.get_material_node(material, "BSDF_PRINCIPLED").inputs
|
||||
color = inputs["Base Color"].default_value
|
||||
color[0] = color_progress.r
|
||||
color[1] = color_progress.g
|
||||
|
||||
@@ -37,6 +37,7 @@ classes = (
|
||||
operator.ContractContainer,
|
||||
operator.ExpandContainer,
|
||||
operator.DeleteContainer,
|
||||
operator.SelectDecomposedElements,
|
||||
prop.SpatialElement,
|
||||
prop.BIMSpatialProperties,
|
||||
prop.BIMObjectSpatialProperties,
|
||||
@@ -45,7 +46,7 @@ classes = (
|
||||
ui.BIM_PT_spatial,
|
||||
ui.BIM_UL_containers,
|
||||
ui.BIM_UL_containers_manager,
|
||||
ui.BIM_PT_Storeys,
|
||||
ui.BIM_PT_SpatialManager,
|
||||
workspace.Hotkey,
|
||||
)
|
||||
|
||||
@@ -59,6 +60,8 @@ def register():
|
||||
|
||||
|
||||
def unregister():
|
||||
if not bpy.app.background:
|
||||
bpy.utils.unregister_tool(workspace.SpatialTool)
|
||||
del bpy.types.Scene.BIMSpatialProperties
|
||||
del bpy.types.Object.BIMObjectSpatialProperties
|
||||
del bpy.types.Scene.BIMSpatialManagerProperties
|
||||
|
||||
@@ -246,3 +246,13 @@ class AddBuildingStorey(bpy.types.Operator, tool.Ifc.Operator):
|
||||
part_name="Unnamed",
|
||||
)
|
||||
core.load_container_manager(tool.Spatial)
|
||||
|
||||
|
||||
class SelectDecomposedElements(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.select_decomposed_elements"
|
||||
bl_label = "Select Children"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.select_decomposed_elements(tool.Spatial)
|
||||
return {"FINISHED"}
|
||||
|
||||
Binary file not shown.
@@ -96,17 +96,18 @@ class BIM_UL_containers(UIList):
|
||||
)
|
||||
|
||||
|
||||
class BIM_PT_Storeys(Panel):
|
||||
bl_label = "Spatial Manager"
|
||||
bl_idname = "BIM_PT_Storeys"
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "UI"
|
||||
class BIM_PT_SpatialManager(Panel):
|
||||
bl_label = "IFC Spatial Manager"
|
||||
bl_idname = "BIM_PT_SpatialManager"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_parent_id = "BIM_PT_GridsSpatialManager"
|
||||
bl_parent_id = "BIM_PT_project_setup"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return tool.Ifc.get()
|
||||
return tool.Ifc.get() and tool.Ifc.schema().name() != "IFC2X3"
|
||||
|
||||
def draw(self, context):
|
||||
if not SpatialData.is_loaded:
|
||||
@@ -118,6 +119,7 @@ class BIM_PT_Storeys(Panel):
|
||||
ifc_definition_id = self.props.containers[self.props.active_container_index].ifc_definition_id
|
||||
row = self.layout.row()
|
||||
row.alignment = "RIGHT"
|
||||
row.operator("bim.select_decomposed_elements", icon="RESTRICT_SELECT_OFF", text="Select Children")
|
||||
if SpatialData.data["containers"][ifc_definition_id]["type"] in ["IfcBuildingStorey", "IfcBuilding"]:
|
||||
row.operator("bim.add_building_storey", icon="ADD", text="Add storey").part_class = "IfcBuildingStorey"
|
||||
row.operator("bim.delete_container", icon="X", text="Delete").container = ifc_definition_id
|
||||
|
||||
@@ -21,6 +21,7 @@ import os
|
||||
import bpy
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim.helper import prop_with_search
|
||||
from blenderbim.bim.module.model.data import AuthoringData
|
||||
from bpy.types import WorkSpaceTool
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
import blenderbim.bim.handler
|
||||
@@ -41,10 +42,14 @@ class SpatialTool(WorkSpaceTool):
|
||||
bl_label = "Spatial Tool"
|
||||
bl_description = "Gives you Spatial related superpowers"
|
||||
# TODO: replace with spatial icon
|
||||
bl_icon = os.path.join(os.path.dirname(__file__), "ops.authoring.annotation")
|
||||
bl_icon = os.path.join(os.path.dirname(__file__), "ops.authoring.spatial")
|
||||
bl_widget = None
|
||||
bl_keymap = tool.Blender.get_default_selection_keypmap() + (
|
||||
("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": "T", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_T")]}),
|
||||
("bim.spatial_hotkey", {"type": "G", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_G")]}),
|
||||
)
|
||||
|
||||
def draw_settings(context, layout, ws_tool):
|
||||
@@ -67,6 +72,7 @@ class SpatialToolUI:
|
||||
def draw(cls, context, layout):
|
||||
cls.layout = layout
|
||||
# cls.props = context.scene.BIMSpatialProperties
|
||||
cls.model_props = context.scene.BIMModelProperties
|
||||
|
||||
row = cls.layout.row(align=True)
|
||||
if not tool.Ifc.get():
|
||||
@@ -76,19 +82,46 @@ class SpatialToolUI:
|
||||
# if not SpatialData.is_loaded:
|
||||
# SpatialData.load()
|
||||
|
||||
if not AuthoringData.is_loaded:
|
||||
AuthoringData.load()
|
||||
|
||||
cls.draw_type_selection_interface(context)
|
||||
cls.draw_default_interface(context)
|
||||
|
||||
if context.active_object and context.selected_objects:
|
||||
cls.draw_selected_object_interface(context)
|
||||
cls.draw_default_interface()
|
||||
|
||||
@classmethod
|
||||
def draw_default_interface(cls):
|
||||
add_layout_hotkey(cls.layout, "Placeholder", "S_A", "Placeholder Operator")
|
||||
def draw_default_interface(cls, context):
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_A")
|
||||
if context.selected_objects:
|
||||
row.operator("bim.generate_spaces_from_walls")
|
||||
else:
|
||||
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")
|
||||
|
||||
@classmethod
|
||||
def draw_selected_object_interface(cls, context):
|
||||
pass
|
||||
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")
|
||||
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_B")
|
||||
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")
|
||||
|
||||
@classmethod
|
||||
def draw_type_selection_interface(cls, context):
|
||||
@@ -123,4 +156,26 @@ class Hotkey(bpy.types.Operator, Operator):
|
||||
pass
|
||||
|
||||
def hotkey_S_A(self):
|
||||
pass
|
||||
active_obj = bpy.context.active_object
|
||||
element = tool.Ifc.get_entity(active_obj)
|
||||
if element and bpy.context.selected_objects and element.is_a("IfcWall"):
|
||||
bpy.ops.bim.generate_spaces_from_walls()
|
||||
else:
|
||||
bpy.ops.bim.generate_space()
|
||||
|
||||
def hotkey_S_B(self):
|
||||
bpy.ops.bim.add_boundary()
|
||||
|
||||
def hotkey_A_B(self):
|
||||
if not bpy.context.selected_objects:
|
||||
return
|
||||
if AuthoringData.data["has_visible_boundaries"]:
|
||||
bpy.ops.bim.hide_boundaries()
|
||||
else:
|
||||
bpy.ops.bim.show_boundaries()
|
||||
|
||||
def hotkey_S_T(self):
|
||||
bpy.ops.bim.toggle_space_visibility()
|
||||
|
||||
def hotkey_S_G(self):
|
||||
bpy.ops.bim.generate_space()
|
||||
|
||||
@@ -100,5 +100,7 @@ def register():
|
||||
|
||||
|
||||
def unregister():
|
||||
if not bpy.app.background:
|
||||
bpy.utils.unregister_tool(workspace.StructuralTool)
|
||||
del bpy.types.Scene.BIMStructuralProperties
|
||||
del bpy.types.Object.BIMStructuralProperties
|
||||
|
||||
Binary file not shown.
@@ -41,7 +41,7 @@ class StructuralTool(WorkSpaceTool):
|
||||
bl_label = "Structural Tool"
|
||||
bl_description = "Gives you Structure related superpowers"
|
||||
# TODO: replace with structural icon
|
||||
bl_icon = os.path.join(os.path.dirname(__file__), "ops.authoring.annotation")
|
||||
bl_icon = os.path.join(os.path.dirname(__file__), "ops.authoring.structural")
|
||||
bl_widget = None
|
||||
bl_keymap = tool.Blender.get_default_selection_keypmap() + (
|
||||
("bim.structural_hotkey", {"type": "A", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_A")]}),
|
||||
|
||||
@@ -21,23 +21,32 @@ from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.AddStyle,
|
||||
operator.DisableEditingStyle,
|
||||
operator.DisableEditingStyles,
|
||||
operator.EditStyle,
|
||||
operator.EnableEditingStyle,
|
||||
operator.DisableEditingStyle,
|
||||
operator.EditStyle,
|
||||
operator.UpdateCurrentStyle,
|
||||
operator.EnableEditingExternalStyle,
|
||||
operator.DisableEditingExternalStyle,
|
||||
operator.EditExternalStyle,
|
||||
operator.DisableEditingStyles,
|
||||
operator.BrowseExternalStyle,
|
||||
operator.ActivateExternalStyle,
|
||||
operator.LoadStyles,
|
||||
operator.RemoveStyle,
|
||||
operator.SelectByStyle,
|
||||
operator.UnlinkStyle,
|
||||
operator.UpdateStyleColours,
|
||||
operator.UpdateStyleTextures,
|
||||
operator.ClearTextureMapPath,
|
||||
prop.Style,
|
||||
prop.BIMStylesProperties,
|
||||
prop.BIMStyleProperties,
|
||||
ui.BIM_PT_styles,
|
||||
ui.BIM_PT_style,
|
||||
ui.BIM_PT_style_attributes,
|
||||
ui.BIM_PT_external_style_attributes,
|
||||
ui.BIM_UL_styles,
|
||||
ui.BIM_PT_STYLE_GRAPH,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -56,7 +56,12 @@ class StyleAttributesData:
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {"ifc_style_id": cls.ifc_style_id(), "attributes": cls.get_attributes()}
|
||||
cls.data = {
|
||||
"ifc_style_id": cls.ifc_style_id(),
|
||||
"attributes": cls.get_attributes(),
|
||||
"style_elements": cls.get_style_elements(),
|
||||
}
|
||||
cls.data["external_style_attributes"] = cls.get_external_style_attributes()
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
@@ -72,3 +77,19 @@ class StyleAttributesData:
|
||||
continue
|
||||
results.append({"name": name, "value": str(value)})
|
||||
return results
|
||||
|
||||
@classmethod
|
||||
def get_style_elements(cls):
|
||||
return tool.Style.get_style_elements(bpy.context.active_object.active_material)
|
||||
|
||||
@classmethod
|
||||
def get_external_style_attributes(cls):
|
||||
external_style = cls.data["style_elements"].get("IfcExternallyDefinedSurfaceStyle", None)
|
||||
if not external_style:
|
||||
return None
|
||||
results = []
|
||||
for name, value in external_style.get_info().items():
|
||||
if name in ["id", "type"]:
|
||||
continue
|
||||
results.append({"name": name, "value": str(value)})
|
||||
return results
|
||||
|
||||
@@ -22,15 +22,34 @@ import blenderbim.tool as tool
|
||||
import blenderbim.core.style as core
|
||||
import ifcopenshell.util.representation
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.style.data import StylesData, StyleAttributesData
|
||||
from pathlib import Path
|
||||
import os
|
||||
|
||||
|
||||
class UpdateStyleColours(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.update_style_colours"
|
||||
bl_label = "Update Style Colours"
|
||||
bl_label = "Save Current Shading Style"
|
||||
bl_description = (
|
||||
"Save current style values to IfcSurfaceStyleShading.\n\n" + "ALT+CLICK to see saved values details"
|
||||
)
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
verbose: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
|
||||
|
||||
def invoke(self, context, event):
|
||||
# verobse print to console on alt+click
|
||||
# make sure to use SKIP_SAVE on property, otherwise it might get stuck
|
||||
if event.type == "LEFTMOUSE" and event.alt:
|
||||
self.verbose = True
|
||||
return self.execute(context)
|
||||
|
||||
def _execute(self, context):
|
||||
core.update_style_colours(tool.Ifc, tool.Style, obj=context.active_object.active_material)
|
||||
mat = context.active_object.active_material
|
||||
core.update_style_colours(tool.Ifc, tool.Style, obj=mat, verbose=self.verbose)
|
||||
if self.verbose:
|
||||
self.report({"INFO"}, "Check the system console to see saved style properties")
|
||||
tool.Style.set_surface_style_props(mat)
|
||||
|
||||
|
||||
class UpdateStyleTextures(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -103,6 +122,249 @@ class EditStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
core.edit_style(tool.Ifc, tool.Style, obj=context.active_object.active_material)
|
||||
|
||||
|
||||
class UpdateCurrentStyle(bpy.types.Operator):
|
||||
bl_idname = "bim.update_current_style"
|
||||
bl_label = "Update Current Style"
|
||||
bl_description = (
|
||||
"Update style for all selected objects according to current style type\n(Shading/External).\n\n"
|
||||
+ "SHIFT+CLICK to update ALL styles in the .ifc file to current style type"
|
||||
)
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
update_all: bpy.props.BoolProperty(name="Update All", default=False, options={"SKIP_SAVE"})
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
poll = (
|
||||
context.active_object is not None
|
||||
and context.active_object.active_material is not None
|
||||
and context.active_object.active_material.BIMMaterialProperties.ifc_style_id != 0
|
||||
)
|
||||
if not poll:
|
||||
cls.poll_message_set(
|
||||
"Object is not selected or material is not assigned or material doesn't have IFC Style"
|
||||
)
|
||||
return poll
|
||||
|
||||
def invoke(self, context, event):
|
||||
# updating all styles on shift+click
|
||||
# make sure to use SKIP_SAVE on property, otherwise it might get stuck
|
||||
if event.type == "LEFTMOUSE" and event.shift:
|
||||
self.update_all = True
|
||||
return self.execute(context)
|
||||
|
||||
def execute(self, context):
|
||||
current_style_type = context.active_object.active_material.BIMStyleProperties.active_style_type
|
||||
if self.update_all:
|
||||
context.scene.BIMStylesProperties.active_style_type = current_style_type
|
||||
return {"FINISHED"}
|
||||
|
||||
materials = []
|
||||
for obj in context.selected_objects:
|
||||
mat = obj.active_material
|
||||
if mat and mat.BIMMaterialProperties.ifc_style_id != 0:
|
||||
mat.BIMStyleProperties.active_style_type = current_style_type
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class BrowseExternalStyle(bpy.types.Operator):
|
||||
bl_idname = "bim.browse_external_style"
|
||||
bl_label = "Browse External Style"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
filepath: bpy.props.StringProperty(
|
||||
name="File Path", description="Filepath used to import from", maxlen=1024, default="", subtype="FILE_PATH"
|
||||
)
|
||||
|
||||
filter_glob: bpy.props.StringProperty(
|
||||
default="*.blend",
|
||||
options={"HIDDEN"},
|
||||
)
|
||||
|
||||
def get_data_block_types(self, context):
|
||||
return [("materials", "materials", "materials")]
|
||||
# NOTE: the code below can be used later when we'll be adding other data-blocks besides materials
|
||||
l = [("0", "", "")]
|
||||
SUPPORTED_DATA_BLOCKS = ("materials", "textures", "brushes")
|
||||
if os.path.exists(self.filepath) and self.filepath.endswith(".blend"):
|
||||
with bpy.data.libraries.load(self.filepath) as (data_from, data_to):
|
||||
for data_block_type in dir(data_from):
|
||||
if data_block_type not in SUPPORTED_DATA_BLOCKS:
|
||||
continue
|
||||
data = getattr(data_from, data_block_type)
|
||||
if data:
|
||||
item = (data_block_type,) * 3
|
||||
l.append(item)
|
||||
return l
|
||||
|
||||
def get_data_blocks(self, context):
|
||||
l = [("", "", "")]
|
||||
if self.data_block_type != "0" and os.path.exists(self.filepath) and self.filepath.endswith(".blend"):
|
||||
with bpy.data.libraries.load(self.filepath) as (data_from, data_to):
|
||||
objects = getattr(data_from, self.data_block_type)
|
||||
for o in objects:
|
||||
l.append((o, o, o))
|
||||
return l
|
||||
|
||||
data_block_type: bpy.props.EnumProperty(
|
||||
name="Data Block Type",
|
||||
description="List of data blocks in the .blend file",
|
||||
items=get_data_block_types,
|
||||
)
|
||||
|
||||
data_block: bpy.props.EnumProperty(
|
||||
name="List of objects in the .blend file",
|
||||
description="List of objects in the .blend file",
|
||||
items=get_data_blocks,
|
||||
)
|
||||
use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=True)
|
||||
directory: bpy.props.StringProperty(
|
||||
name="Directory",
|
||||
description="Start file browsing directory",
|
||||
default="",
|
||||
)
|
||||
|
||||
def invoke(self, context, event):
|
||||
mat = context.active_object.active_material
|
||||
external_style = tool.Style.get_style_elements(mat).get("IfcExternallyDefinedSurfaceStyle", None)
|
||||
# automatically select previously selected external style in file browser
|
||||
if external_style and self.filepath == "":
|
||||
style_path = Path(tool.Ifc.resolve_uri(external_style.Location))
|
||||
self.directory = str(style_path.parent)
|
||||
self.filepath = str(style_path)
|
||||
self.data_block_type, self.data_block = external_style.Identification.split("/")
|
||||
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.label(text="Data Block Type")
|
||||
layout.prop(self, "data_block_type", text="", icon="GROUP")
|
||||
layout.label(text="Data Block")
|
||||
layout.prop(self, "data_block", text="")
|
||||
if bpy.data.is_saved:
|
||||
layout.prop(self, "use_relative_path")
|
||||
else:
|
||||
self.use_relative_path = False
|
||||
layout.label(text="Save the .blend file first ")
|
||||
layout.label(text="to use relative paths for .ifc.")
|
||||
|
||||
def execute(self, context):
|
||||
if self.data_block_type == "0":
|
||||
self.report({"ERROR"}, "Select a data block type")
|
||||
return {"CANCELLED"}
|
||||
|
||||
if self.data_block == "":
|
||||
self.report({"ERROR"}, "Select a data block")
|
||||
return {"CANCELLED"}
|
||||
|
||||
if not os.path.exists(self.filepath):
|
||||
self.report({"ERROR"}, f"File not found:\n'{self.filepath}'")
|
||||
return {"CANCELLED"}
|
||||
|
||||
db = tool.Blender.append_data_block(self.filepath, self.data_block_type, self.data_block)
|
||||
if not db["data_block"]:
|
||||
self.report({"ERROR"}, db["msg"])
|
||||
return {"CANCELLED"}
|
||||
|
||||
bpy.data.materials.remove(db["data_block"])
|
||||
|
||||
if not StyleAttributesData.is_loaded:
|
||||
StyleAttributesData.load()
|
||||
external_style = StyleAttributesData.data["style_elements"].get("IfcExternallyDefinedSurfaceStyle", None)
|
||||
|
||||
if self.use_relative_path:
|
||||
filepath = os.path.relpath(self.filepath, bpy.path.abspath("//"))
|
||||
else:
|
||||
filepath = self.filepath
|
||||
|
||||
attributes = {
|
||||
"Location": filepath,
|
||||
"Identification": f"{self.data_block_type}/{self.data_block}",
|
||||
"Name": self.data_block,
|
||||
}
|
||||
if not external_style:
|
||||
core.add_external_style(
|
||||
tool.Ifc, tool.Style, obj=context.active_object.active_material, attributes=attributes
|
||||
)
|
||||
else:
|
||||
core.update_external_style(tool.Ifc, tool.Style, external_style=external_style, attributes=attributes)
|
||||
|
||||
StyleAttributesData.is_loaded = False
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EnableEditingExternalStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.enable_editing_external_style"
|
||||
bl_label = "Enable Editing External Style"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.enable_editing_external_style(tool.Style, obj=context.active_object.active_material)
|
||||
|
||||
|
||||
class DisableEditingExternalStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.disable_editing_external_style"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_label = "Disable Editing External Style"
|
||||
|
||||
def _execute(self, context):
|
||||
core.disable_editing_external_style(tool.Style, obj=context.active_object.active_material)
|
||||
|
||||
|
||||
class EditExternalStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.edit_external_style"
|
||||
bl_label = "Edit External Style"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.edit_external_style(tool.Ifc, tool.Style, obj=context.active_object.active_material)
|
||||
|
||||
|
||||
class ActivateExternalStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.activate_external_style"
|
||||
bl_label = "Activate External Style"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
material_name: bpy.props.StringProperty(name="Material Name", default="")
|
||||
|
||||
def _execute(self, context):
|
||||
if not self.material_name:
|
||||
material = context.active_object.active_material
|
||||
else:
|
||||
material = bpy.data.materials[self.material_name]
|
||||
external_style = tool.Style.get_style_elements(material)["IfcExternallyDefinedSurfaceStyle"]
|
||||
data_block_type, data_block = external_style.Identification.split("/")
|
||||
style_path = Path(tool.Ifc.resolve_uri(external_style.Location))
|
||||
|
||||
if style_path.suffix != ".blend":
|
||||
self.report({"ERROR"}, f"Only Blender external styles are supported")
|
||||
return {"CANCELLED"}
|
||||
|
||||
if not style_path.exists():
|
||||
self.report({"ERROR"}, f"File not found:\n'{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"}, db["msg"])
|
||||
return {"CANCELLED"}
|
||||
|
||||
props_to_copy = [
|
||||
"diffuse_color",
|
||||
"metallic",
|
||||
"roughness",
|
||||
"specular_intensity",
|
||||
"use_nodes",
|
||||
]
|
||||
for prop_name in props_to_copy:
|
||||
setattr(material, prop_name, getattr(db["data_block"], prop_name))
|
||||
|
||||
if material.use_nodes:
|
||||
tool.Blender.copy_node_graph(material, db["data_block"])
|
||||
bpy.data.materials.remove(db["data_block"])
|
||||
|
||||
|
||||
class DisableEditingStyles(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.disable_editing_styles"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
@@ -129,4 +391,26 @@ class SelectByStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
style: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.select_by_style(tool.Style, style=tool.Ifc.get().by_id(self.style))
|
||||
core.select_by_style(tool.Style, tool.Spatial, style=tool.Ifc.get().by_id(self.style))
|
||||
|
||||
|
||||
class ClearTextureMapPath(bpy.types.Operator):
|
||||
bl_idname = "bim.clear_texture_map_path"
|
||||
bl_label = "Clear Texture Map Path"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
texture_map_prop: bpy.props.StringProperty(default="")
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
poll = getattr(context, "material", None)
|
||||
if not poll:
|
||||
cls.poll_message_set("Select a material")
|
||||
return poll
|
||||
|
||||
def execute(self, context):
|
||||
if not self.texture_map_prop:
|
||||
self.report({"ERROR"}, "Provide a texture map")
|
||||
return {"CANCELLED"}
|
||||
props = context.material.BIMStyleProperties
|
||||
setattr(props, self.texture_map_prop, "")
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -31,6 +31,7 @@ from bpy.props import (
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
import blenderbim.tool as tool
|
||||
|
||||
|
||||
def get_style_types(self, context):
|
||||
@@ -45,13 +46,172 @@ class Style(PropertyGroup):
|
||||
total_elements: IntProperty(name="Total Elements")
|
||||
|
||||
|
||||
STYLE_TYPES = [
|
||||
("Shading", "Shading", ""),
|
||||
("External", "External", ""),
|
||||
]
|
||||
|
||||
|
||||
def update_shading_styles(self, context):
|
||||
for mat in bpy.data.materials:
|
||||
if mat.BIMMaterialProperties.ifc_style_id == 0:
|
||||
continue
|
||||
tool.Style.change_current_style_type(mat, self.active_style_type)
|
||||
|
||||
|
||||
class BIMStylesProperties(PropertyGroup):
|
||||
is_editing: BoolProperty(name="Is Editing")
|
||||
style_type: EnumProperty(items=get_style_types, name="Style Type")
|
||||
styles: CollectionProperty(name="Styles", type=Style)
|
||||
active_style_index: IntProperty(name="Active Style Index")
|
||||
active_style_type: EnumProperty(
|
||||
name="Active Style Type",
|
||||
description="Update current blender material to match style type for all objects in the scene",
|
||||
items=STYLE_TYPES,
|
||||
default="Shading",
|
||||
update=update_shading_styles,
|
||||
)
|
||||
|
||||
|
||||
def update_shading_style(self, context):
|
||||
blender_material = self.id_data
|
||||
style_elements = tool.Style.get_style_elements(blender_material)
|
||||
if self.active_style_type == "External":
|
||||
if tool.Style.has_blender_external_style(style_elements):
|
||||
bpy.ops.bim.activate_external_style(material_name=blender_material.name)
|
||||
|
||||
elif self.active_style_type == "Shading":
|
||||
style_elements = tool.Style.get_style_elements(blender_material)
|
||||
rendering_style = None
|
||||
texture_style = None
|
||||
|
||||
for surface_style in style_elements.values():
|
||||
if surface_style.is_a() == "IfcSurfaceStyleShading":
|
||||
tool.Loader.create_surface_style_shading(blender_material, surface_style)
|
||||
elif surface_style.is_a("IfcSurfaceStyleRendering"):
|
||||
rendering_style = surface_style
|
||||
tool.Loader.create_surface_style_rendering(blender_material, surface_style)
|
||||
elif surface_style.is_a("IfcSurfaceStyleWithTextures"):
|
||||
texture_style = surface_style
|
||||
|
||||
if rendering_style and texture_style:
|
||||
tool.Loader.create_surface_style_with_textures(blender_material, rendering_style, texture_style)
|
||||
tool.Style.set_surface_style_props(blender_material)
|
||||
tool.Style.record_shading(blender_material)
|
||||
|
||||
|
||||
# TODO: support more more methods
|
||||
REFLECTANCE_METHODS = [
|
||||
("PHYSICAL", "PHYSICAL", ""),
|
||||
("FLAT", "FLAT", ""),
|
||||
# ("METAL", "METAL", ""),
|
||||
# ("MATT", "MATT", ""),
|
||||
# ("GLASS", "GLASS", ""),
|
||||
# ("NOTDEFINED", "NOTDEFINED", ""),
|
||||
]
|
||||
|
||||
|
||||
def update_shader_graph(self, context):
|
||||
if not self.update_graph:
|
||||
return
|
||||
|
||||
material = self.id_data
|
||||
style_data = tool.Style.get_surface_style_from_props(material)
|
||||
textures_data = tool.Style.get_texture_style_from_props(material)
|
||||
tool.Loader.create_surface_style_rendering(material, style_data)
|
||||
tool.Loader.create_surface_style_with_textures(material, style_data, textures_data)
|
||||
|
||||
|
||||
def update_graph_get(self):
|
||||
return self.get("update_graph", True)
|
||||
|
||||
|
||||
def update_graph_set(self, value):
|
||||
self["update_graph"] = value
|
||||
if value:
|
||||
material = self.id_data
|
||||
tool.Style.set_surface_style_props(material)
|
||||
|
||||
|
||||
class BIMStyleProperties(PropertyGroup):
|
||||
attributes: CollectionProperty(name="Attributes", type=Attribute)
|
||||
is_editing: BoolProperty(name="Is Editing")
|
||||
|
||||
external_style_attributes: CollectionProperty(name="External Style Attributes", type=Attribute)
|
||||
is_editing_external_style: BoolProperty(name="Is Editing External Style")
|
||||
|
||||
active_style_type: EnumProperty(
|
||||
name="Active Style Type",
|
||||
description="Update current blender material to match style type",
|
||||
items=STYLE_TYPES,
|
||||
default="Shading",
|
||||
update=update_shading_style,
|
||||
)
|
||||
|
||||
# GLTF style properties
|
||||
update_graph: BoolProperty(
|
||||
name="Update Shade Graph on Prop Change",
|
||||
description="Update shader graph in real time\nas you update style properties",
|
||||
default=True,
|
||||
get=update_graph_get,
|
||||
set=update_graph_set,
|
||||
)
|
||||
reflectance_method: EnumProperty(
|
||||
name="Reflectance Method",
|
||||
description="Reflectance method to use for the material",
|
||||
items=REFLECTANCE_METHODS,
|
||||
default="PHYSICAL",
|
||||
update=update_shader_graph,
|
||||
)
|
||||
surface_color: bpy.props.FloatVectorProperty(
|
||||
name="Surface Color",
|
||||
subtype="COLOR",
|
||||
default=(1, 1, 1, 1),
|
||||
min=0.0,
|
||||
max=1.0,
|
||||
size=4,
|
||||
update=update_shader_graph,
|
||||
)
|
||||
diffuse_color: bpy.props.FloatVectorProperty(
|
||||
name="Diffuse Color",
|
||||
subtype="COLOR",
|
||||
default=(1, 1, 1, 1),
|
||||
min=0.0,
|
||||
max=1.0,
|
||||
size=4,
|
||||
update=update_shader_graph,
|
||||
)
|
||||
transparency: bpy.props.FloatProperty(
|
||||
name="Transparency", default=0.0, min=0.0, max=1.0, update=update_shader_graph
|
||||
)
|
||||
roughness: bpy.props.FloatProperty(name="Roughness", default=0.0, min=0.0, max=1.0, update=update_shader_graph)
|
||||
metallic: bpy.props.FloatProperty(name="Metallic", default=0.0, min=0.0, max=1.0, update=update_shader_graph)
|
||||
normal_path: bpy.props.StringProperty(
|
||||
name="NormalMap",
|
||||
maxlen=1024,
|
||||
default="",
|
||||
subtype="FILE_PATH",
|
||||
update=update_shader_graph,
|
||||
)
|
||||
emissive_path: bpy.props.StringProperty(
|
||||
name="Emissive",
|
||||
maxlen=1024,
|
||||
default="",
|
||||
subtype="FILE_PATH",
|
||||
update=update_shader_graph,
|
||||
)
|
||||
metallic_roughness_path: bpy.props.StringProperty(
|
||||
name="Metallic/Roughness",
|
||||
maxlen=1024,
|
||||
default="",
|
||||
subtype="FILE_PATH",
|
||||
update=update_shader_graph,
|
||||
description="Green Channel = Roughness,\nBlue Channel = Metallic",
|
||||
)
|
||||
diffuse_path: bpy.props.StringProperty(
|
||||
name="Diffuse",
|
||||
maxlen=1024,
|
||||
default="",
|
||||
subtype="FILE_PATH",
|
||||
update=update_shader_graph,
|
||||
)
|
||||
|
||||
@@ -65,6 +65,24 @@ class BIM_PT_styles(Panel):
|
||||
self.layout.template_list("BIM_UL_styles", "", self.props, "styles", self.props, "active_style_index")
|
||||
|
||||
|
||||
def draw_style_ui(self, context):
|
||||
mat = context.material
|
||||
props = mat.BIMMaterialProperties
|
||||
style_props = mat.BIMStyleProperties
|
||||
row = self.layout.row(align=True)
|
||||
if not props.ifc_style_id:
|
||||
row.operator("bim.add_style", icon="ADD")
|
||||
return
|
||||
|
||||
row.prop(style_props, "active_style_type", icon="SHADING_RENDERED", text="")
|
||||
row.operator("bim.update_current_style", icon="FILE_REFRESH", text="")
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.update_style_colours", icon="GREASEPENCIL")
|
||||
row.operator("bim.update_style_textures", icon="TEXTURE", text="")
|
||||
row.operator("bim.unlink_style", icon="UNLINKED", text="")
|
||||
row.operator("bim.remove_style", icon="X", text="").style = props.ifc_style_id
|
||||
|
||||
|
||||
class BIM_PT_style(MaterialButtonsPanel, Panel):
|
||||
bl_label = "IFC Style"
|
||||
bl_idname = "BIM_PT_style"
|
||||
@@ -81,19 +99,13 @@ class BIM_PT_style(MaterialButtonsPanel, Panel):
|
||||
)
|
||||
|
||||
def draw(self, context):
|
||||
props = context.active_object.active_material.BIMMaterialProperties
|
||||
mat = context.material
|
||||
props = mat.BIMMaterialProperties
|
||||
draw_style_ui(self, context)
|
||||
if not props.ifc_style_id:
|
||||
return
|
||||
row = self.layout.row(align=True)
|
||||
if props.ifc_style_id:
|
||||
row.operator("bim.update_style_colours", icon="GREASEPENCIL")
|
||||
row.operator("bim.update_style_textures", icon="TEXTURE", text="")
|
||||
row.operator("bim.unlink_style", icon="UNLINKED", text="")
|
||||
row.operator("bim.remove_style", icon="X", text="").style = props.ifc_style_id
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(mat, "diffuse_color", text="Color")
|
||||
|
||||
else:
|
||||
row.operator("bim.add_style", icon="ADD")
|
||||
row.prop(mat, "diffuse_color", text="Viewport Color" if mat.use_nodes else "Render Color")
|
||||
|
||||
|
||||
class BIM_PT_style_attributes(Panel):
|
||||
@@ -102,6 +114,7 @@ class BIM_PT_style_attributes(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "material"
|
||||
bl_parent_id = "BIM_PT_style"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -143,6 +156,61 @@ class BIM_PT_style_attributes(Panel):
|
||||
row.label(text=attribute["value"])
|
||||
|
||||
|
||||
class BIM_PT_external_style_attributes(Panel):
|
||||
bl_label = "IFC External Surface Style"
|
||||
bl_idname = "BIM_PT_external_style_attributes"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "material"
|
||||
bl_parent_id = "BIM_PT_style"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if not IfcStore.get_file():
|
||||
return False
|
||||
try:
|
||||
return bool(context.active_object.active_material.BIMMaterialProperties.ifc_style_id)
|
||||
except:
|
||||
return False
|
||||
|
||||
def draw(self, context):
|
||||
if not StyleAttributesData.is_loaded or (
|
||||
context.active_object.active_material.BIMMaterialProperties.ifc_style_id
|
||||
!= StyleAttributesData.data["ifc_style_id"]
|
||||
):
|
||||
StyleAttributesData.load()
|
||||
|
||||
mat = context.active_object.active_material
|
||||
mprops = mat.BIMMaterialProperties
|
||||
props = mat.BIMStyleProperties
|
||||
|
||||
external_style = StyleAttributesData.data["style_elements"].get("IfcExternallyDefinedSurfaceStyle", None)
|
||||
if not external_style:
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.browse_external_style", text="Add External Style", icon="ADD")
|
||||
return
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Parameters:")
|
||||
|
||||
if props.is_editing_external_style:
|
||||
row.operator("bim.edit_external_style", icon="CHECKMARK", text="")
|
||||
row.operator("bim.disable_editing_external_style", icon="CANCEL", text="")
|
||||
blenderbim.bim.helper.draw_attributes(props.external_style_attributes, self.layout)
|
||||
else:
|
||||
row.operator("bim.browse_external_style", icon="APPEND_BLEND", text="")
|
||||
row.operator("bim.enable_editing_external_style", icon="GREASEPENCIL", text="")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="STEP ID")
|
||||
row.label(text=str(external_style.id()))
|
||||
|
||||
for attribute in StyleAttributesData.data["external_style_attributes"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=attribute["name"])
|
||||
row.label(text=attribute["value"])
|
||||
|
||||
|
||||
class BIM_UL_styles(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if item:
|
||||
@@ -151,3 +219,66 @@ class BIM_UL_styles(UIList):
|
||||
row2 = row.row()
|
||||
row2.alignment = "RIGHT"
|
||||
row2.label(text=str(item.total_elements))
|
||||
|
||||
|
||||
class BIM_PT_STYLE_GRAPH(Panel):
|
||||
bl_idname = "BIM_PT_style_graph"
|
||||
bl_space_type = "NODE_EDITOR"
|
||||
bl_label = "IFC Style Graph Settings"
|
||||
bl_region_type = "UI"
|
||||
bl_category = "BBIM"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return getattr(context, "material", None)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
props = context.active_object.active_material.BIMStyleProperties
|
||||
|
||||
draw_style_ui(self, context)
|
||||
layout.separator()
|
||||
box = layout.box()
|
||||
box.label(text="Creating shader from this panel")
|
||||
box.label(text="ensures that shader is")
|
||||
box.label(text="GLTF compatible")
|
||||
box.label(text="and therefore will be ")
|
||||
box.label(text="stored in IFC safely.")
|
||||
layout.separator()
|
||||
|
||||
layout.prop(props, "update_graph", text="Graph Auto Update")
|
||||
layout.label(text="Reflectance Method:")
|
||||
layout.prop(props, "reflectance_method", text="")
|
||||
layout.prop(props, "surface_color")
|
||||
layout.prop(props, "transparency")
|
||||
|
||||
if not (props.reflectance_method == "PHYSICAL" and props.diffuse_path) and not (
|
||||
props.reflectance_method == "FLAT" and props.emissive_path
|
||||
):
|
||||
prop_name = "Emissive Color" if props.reflectance_method == "FLAT" else "Diffuse Color"
|
||||
layout.prop(props, "diffuse_color", text=prop_name)
|
||||
|
||||
if props.reflectance_method == "PHYSICAL" and not props.metallic_roughness_path:
|
||||
layout.prop(props, "metallic")
|
||||
|
||||
if props.reflectance_method not in ("PHYSICAL", "FLAT", "NOTDEFINED") or (
|
||||
props.reflectance_method == "PHYSICAL" and not props.metallic_roughness_path
|
||||
):
|
||||
layout.prop(props, "roughness")
|
||||
|
||||
layout.label(text="Texture Maps:")
|
||||
|
||||
def add_texture_path(path_name):
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, path_name)
|
||||
op = row.operator("bim.clear_texture_map_path", text="", icon="X")
|
||||
op.texture_map_prop = path_name
|
||||
|
||||
if props.reflectance_method == "PHYSICAL":
|
||||
add_texture_path("diffuse_path")
|
||||
add_texture_path("emissive_path")
|
||||
add_texture_path("normal_path")
|
||||
add_texture_path("metallic_roughness_path")
|
||||
|
||||
if props.reflectance_method == "FLAT":
|
||||
add_texture_path("emissive_path")
|
||||
|
||||
@@ -140,7 +140,7 @@ class ShowPorts(bpy.types.Operator, Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.show_ports(tool.Ifc, tool.System, element=tool.Ifc.get_entity(context.active_object))
|
||||
core.show_ports(tool.Ifc, tool.System, tool.Spatial, element=tool.Ifc.get_entity(context.active_object))
|
||||
|
||||
|
||||
class HidePorts(bpy.types.Operator, Operator):
|
||||
|
||||
@@ -141,14 +141,10 @@ class SelectType(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
element = tool.Ifc.get().by_id(self.relating_type)
|
||||
tool.Spatial.select_products([element])
|
||||
obj = tool.Ifc.get_object(element)
|
||||
if obj:
|
||||
if obj in context.selectable_objects:
|
||||
tool.Blender.select_and_activate_single_object(context, obj)
|
||||
else:
|
||||
self.report({"INFO"}, "Type object can't be selected : It may be hidden or in an excluded collection.")
|
||||
context.scene.BIMModelProperties.ifc_class = element.is_a()
|
||||
context.scene.BIMModelProperties.relating_type_id = str(self.relating_type)
|
||||
context.view_layer.objects.active = obj
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -333,8 +329,8 @@ class AddType(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Collector,
|
||||
tool.Root,
|
||||
obj=obj,
|
||||
predefined_type=predefined_type,
|
||||
ifc_class="IfcWindowType",
|
||||
predefined_type=predefined_type if tool.Ifc.get_schema() != "IFC2X3" else None,
|
||||
ifc_class="IfcWindowType" if tool.Ifc.get_schema() != "IFC2X3" else "IfcWindowStyle",
|
||||
should_add_representation=False,
|
||||
)
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
@@ -351,8 +347,8 @@ class AddType(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Collector,
|
||||
tool.Root,
|
||||
obj=obj,
|
||||
predefined_type=predefined_type,
|
||||
ifc_class="IfcDoorType",
|
||||
predefined_type=predefined_type if tool.Ifc.get_schema() != "IFC2X3" else None,
|
||||
ifc_class="IfcDoorType" if tool.Ifc.get_schema() != "IFC2X3" else "IfcDoorStyle",
|
||||
should_add_representation=False,
|
||||
)
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
|
||||
@@ -29,6 +29,14 @@ class AddOpening(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.add_opening"
|
||||
bl_label = "Add Opening"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Adds an opening to an element.\n" \
|
||||
"Need to select two elements, order of selection is not important"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if len(context.selected_objects) != 2:
|
||||
cls.poll_message_set("Select two elements to add an opening")
|
||||
return True
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMModelProperties
|
||||
|
||||
@@ -94,10 +94,11 @@ class SelectIfcFile(bpy.types.Operator, IFCFileSelector):
|
||||
bl_description = "Select a different IFC file"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"})
|
||||
use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=False)
|
||||
|
||||
def execute(self, context):
|
||||
if self.is_existing_ifc_file():
|
||||
context.scene.BIMProperties.ifc_file = self.filepath
|
||||
context.scene.BIMProperties.ifc_file = self.get_filepath()
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
@@ -105,7 +106,7 @@ class SelectIfcFile(bpy.types.Operator, IFCFileSelector):
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
class ReloadSelectedIfcFile(bpy.types.Operator, IFCFileSelector):
|
||||
class ReloadSelectedIfcFile(bpy.types.Operator):
|
||||
bl_idname = "bim.reload_selected_ifc_file"
|
||||
bl_label = "Reload selected IFC File"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
@@ -113,9 +114,12 @@ class ReloadSelectedIfcFile(bpy.types.Operator, IFCFileSelector):
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def execute(self, context):
|
||||
self.filepath = context.scene.BIMProperties.ifc_file
|
||||
if self.is_existing_ifc_file():
|
||||
context.scene.BIMProperties.ifc_file = context.scene.BIMProperties.ifc_file
|
||||
filepath = context.scene.BIMProperties.ifc_file
|
||||
valid_file = os.path.exists(filepath) and "ifc" in os.path.splitext(filepath)[1].lower()
|
||||
if not valid_file:
|
||||
self.report({"ERROR"}, f"Couldn't find .ifc file by the path '{filepath}'")
|
||||
return {"ERROR"}
|
||||
context.scene.BIMProperties.ifc_file = context.scene.BIMProperties.ifc_file
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -161,6 +165,7 @@ class FileAssociate(bpy.types.Operator):
|
||||
def poll(cls, context):
|
||||
if platform.system() == "Linux":
|
||||
return True
|
||||
cls.poll_message_set("Option available only on Linux.")
|
||||
# TODO Windows and Darwin
|
||||
# https://stackoverflow.com/questions/1082889/how-to-change-filetype-association-in-the-registry
|
||||
return False
|
||||
@@ -235,6 +240,7 @@ class FileUnassociate(bpy.types.Operator):
|
||||
def poll(cls, context):
|
||||
if platform.system() == "Linux":
|
||||
return True
|
||||
cls.poll_message_set("Option available only on Linux.")
|
||||
return False
|
||||
|
||||
def execute(self, context):
|
||||
@@ -487,7 +493,7 @@ class BIM_OT_add_section_plane(bpy.types.Operator):
|
||||
continue
|
||||
material.blend_method = "HASHED"
|
||||
material.shadow_method = "HASHED"
|
||||
material_output = self.get_node(material.node_tree.nodes, "OUTPUT_MATERIAL")
|
||||
material_output = tool.Blender.get_material_node(material, "OUTPUT_MATERIAL", {"is_active_output": True})
|
||||
if not material_output:
|
||||
continue
|
||||
from_socket = material_output.inputs[0].links[0].from_socket
|
||||
@@ -497,11 +503,6 @@ class BIM_OT_add_section_plane(bpy.types.Operator):
|
||||
material.node_tree.links.new(from_socket, section_override.inputs[0])
|
||||
material.node_tree.links.new(section_override.outputs[0], material_output.inputs[0])
|
||||
|
||||
def get_node(self, nodes, node_type):
|
||||
for node in nodes:
|
||||
if node.type == node_type:
|
||||
return node
|
||||
|
||||
|
||||
class BIM_OT_remove_section_plane(bpy.types.Operator):
|
||||
"""Remove selected section plane. No effect if executed on a regular object"""
|
||||
|
||||
@@ -233,9 +233,21 @@ def set_lenght_value(self, value):
|
||||
self.float_value = value / si_conversion
|
||||
|
||||
|
||||
def get_display_name(self):
|
||||
name = self.name
|
||||
if not self.special_type or self.special_type == "LENGTH":
|
||||
return name
|
||||
|
||||
unit_type = f"{self.special_type}UNIT"
|
||||
project_unit = ifcopenshell.util.unit.get_project_unit(tool.Ifc.get(), unit_type)
|
||||
unit_symbol = ifcopenshell.util.unit.get_unit_symbol(project_unit)
|
||||
return f"{name}, {unit_symbol}"
|
||||
|
||||
|
||||
class Attribute(PropertyGroup):
|
||||
tooltip = "`Right Click > IFC Description` to read the attribute description and online documentation"
|
||||
name: StringProperty(name="Name")
|
||||
display_name: StringProperty(name="Display Name", get=get_display_name)
|
||||
description: StringProperty(name="Description")
|
||||
ifc_class: StringProperty(name="Ifc Class")
|
||||
data_type: StringProperty(name="Data Type")
|
||||
|
||||
@@ -39,14 +39,23 @@ class IFCFileSelector:
|
||||
filepath = self.filepath
|
||||
return os.path.exists(filepath) and "ifc" in os.path.splitext(filepath)[1].lower()
|
||||
|
||||
def get_filepath(self):
|
||||
"""get filepath taking into account relative paths"""
|
||||
if self.use_relative_path:
|
||||
filepath = os.path.relpath(self.filepath, bpy.path.abspath("//"))
|
||||
else:
|
||||
filepath = self.filepath
|
||||
return filepath
|
||||
|
||||
def draw(self, context):
|
||||
# Access filepath & Directory https://blender.stackexchange.com/a/207665
|
||||
params = context.space_data.params
|
||||
# Decode byte string https://stackoverflow.com/a/47737082/
|
||||
directory = Path(params.directory.decode("utf-8"))
|
||||
filepath = os.path.join(directory, params.filename)
|
||||
layout = self.layout
|
||||
if self.is_existing_ifc_file(filepath):
|
||||
box = self.layout.box()
|
||||
box = layout.box()
|
||||
box.label(text="IFC Header Specifications", icon="INFO")
|
||||
header_data = IfcHeaderExtractor(filepath).extract()
|
||||
for key, value in header_data.items():
|
||||
@@ -65,6 +74,13 @@ class IFCFileSelector:
|
||||
op.outfile = filepath[0:-4] + "-IFC4.ifc"
|
||||
op.schema = "IFC4"
|
||||
|
||||
if bpy.data.is_saved:
|
||||
layout.prop(self, "use_relative_path")
|
||||
else:
|
||||
self.use_relative_path = False
|
||||
layout.label(text="Save the .blend file first ")
|
||||
layout.label(text="to use relative paths for .ifc.")
|
||||
|
||||
|
||||
class BIM_PT_section_plane(Panel):
|
||||
bl_label = "Temporary Section Cutaways"
|
||||
@@ -198,6 +214,8 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
|
||||
|
||||
row = layout.row()
|
||||
row.prop(context.scene.BIMProjectProperties, "should_disable_undo_on_save")
|
||||
row = layout.row()
|
||||
row.prop(context.scene.BIMProjectProperties, "should_stream")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(context.scene.BIMModelProperties, "occurrence_name_style")
|
||||
|
||||
@@ -59,9 +59,10 @@ def contract_cost_items(cost):
|
||||
cost.load_cost_schedule_tree()
|
||||
|
||||
|
||||
def remove_cost_item(ifc, cost, cost_item):
|
||||
def remove_cost_item(ifc, cost, cost_item_id):
|
||||
cost_item = ifc.get().by_id(cost_item_id)
|
||||
ifc.run("cost.remove_cost_item", cost_item=cost_item)
|
||||
cost.clean_up_cost_item_tree(cost_item)
|
||||
cost.clean_up_cost_item_tree(cost_item_id)
|
||||
cost.load_cost_schedule_tree()
|
||||
|
||||
|
||||
@@ -236,9 +237,9 @@ def calculate_cost_item_resource_value(ifc, cost_item):
|
||||
ifc.run("cost.calculate_cost_item_resource_value", cost_item=cost_item)
|
||||
|
||||
|
||||
def export_cost_schedules(cost, format, cost_schedule=None):
|
||||
def export_cost_schedules(cost, filepath, format, cost_schedule=None):
|
||||
cost.play_sound()
|
||||
return cost.export_cost_schedules(format, cost_schedule)
|
||||
return cost.export_cost_schedules(filepath, format, cost_schedule)
|
||||
|
||||
|
||||
def clear_cost_item_assignments(ifc, cost, cost_item, related_object_type):
|
||||
|
||||
@@ -318,7 +318,7 @@ def update_drawing_name(ifc, drawing_tool, drawing=None, name=None):
|
||||
ifc.run("attribute.edit_attributes", product=group, attributes={"Name": name})
|
||||
collection = drawing_tool.get_drawing_collection(drawing)
|
||||
if collection:
|
||||
drawing_tool.set_drawing_collection_name(group, collection)
|
||||
drawing_tool.set_drawing_collection_name(drawing, collection)
|
||||
|
||||
reference = drawing_tool.get_drawing_document(drawing)
|
||||
information = drawing_tool.get_reference_document(reference)
|
||||
|
||||
@@ -26,7 +26,7 @@ def discard_uncomitted(ifcgit, ifc):
|
||||
ifcgit.load_project(path_ifc)
|
||||
|
||||
|
||||
def commit_changes(ifcgit, ifc, repo, context):
|
||||
def commit_changes(ifcgit, ifc, repo):
|
||||
path_ifc = ifc.get_path()
|
||||
ifcgit.git_commit(path_ifc)
|
||||
|
||||
@@ -42,11 +42,26 @@ def delete_tag(ifcgit, repo, tag_name):
|
||||
ifcgit.delete_tag(repo, tag_name)
|
||||
|
||||
|
||||
def add_remote(ifcgit, repo):
|
||||
ifcgit.add_remote(repo)
|
||||
|
||||
|
||||
def delete_remote(ifcgit, repo):
|
||||
ifcgit.delete_remote(repo)
|
||||
|
||||
|
||||
def push(ifcgit, repo, remote_name, operator):
|
||||
error_message = ifcgit.push(repo, remote_name, repo.active_branch.name)
|
||||
if error_message:
|
||||
operator.report({"ERROR"}, error_message)
|
||||
|
||||
|
||||
def refresh_revision_list(ifcgit, repo, ifc):
|
||||
ifcgit.refresh_revision_list(ifc.get_path())
|
||||
if repo.heads:
|
||||
ifcgit.refresh_revision_list(ifc.get_path())
|
||||
|
||||
|
||||
def colourise_revision(ifcgit, context):
|
||||
def colourise_revision(ifcgit):
|
||||
|
||||
step_ids = ifcgit.get_revisions_step_ids()
|
||||
if not step_ids:
|
||||
|
||||
@@ -70,13 +70,15 @@ def disable_editing_materials(material):
|
||||
material.disable_editing_materials()
|
||||
|
||||
|
||||
def select_by_material(material_tool, material=None):
|
||||
material_tool.select_elements(material_tool.get_elements_by_material(material))
|
||||
def select_by_material(material_tool, spatial, material=None):
|
||||
spatial.select_products(material_tool.get_elements_by_material(material))
|
||||
|
||||
|
||||
def enable_editing_material(material_tool, material):
|
||||
material_tool.load_material_attributes(material)
|
||||
material_tool.enable_editing_material(material)
|
||||
|
||||
|
||||
def edit_material(ifc, material_tool, material):
|
||||
attributes = material_tool.get_material_attributes()
|
||||
ifc.run("material.edit_material", material=material, attributes=attributes)
|
||||
@@ -85,5 +87,51 @@ def edit_material(ifc, material_tool, material):
|
||||
material_tool.import_material_definitions(material_type)
|
||||
material_tool.enable_editing_materials()
|
||||
|
||||
|
||||
def disable_editing_material(material_tool):
|
||||
material_tool.disable_editing_material()
|
||||
|
||||
|
||||
def assign_material(ifc, material_tool, material_type, objects):
|
||||
material_type = material_type or material_tool.get_active_object_material()
|
||||
material = material_tool.get_active_material()
|
||||
for obj in objects:
|
||||
element = ifc.get_entity(obj)
|
||||
if not element:
|
||||
continue
|
||||
ifc.run("material.assign_material", product=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)
|
||||
|
||||
|
||||
def unassign_material(ifc, material_tool, objects):
|
||||
for obj in objects:
|
||||
element = ifc.get_entity(obj)
|
||||
if element:
|
||||
material = material_tool.get_material(element, should_inherit=False)
|
||||
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)
|
||||
elif not material and inherited_material:
|
||||
element_type = material_tool.get_type(element)
|
||||
ifc.run("material.unassign_material", product=element_type)
|
||||
elif material:
|
||||
ifc.run("material.unassign_material", product=element)
|
||||
|
||||
|
||||
def patch_non_parametric_mep_segment(ifc, material_tool, profile_tool, obj):
|
||||
element = ifc.get_entity(obj)
|
||||
if not element:
|
||||
return
|
||||
if not material_tool.is_a_flow_segment(element):
|
||||
return
|
||||
has_material_profile = material_tool.has_material_profile(element)
|
||||
if has_material_profile:
|
||||
return
|
||||
representation_profile = profile_tool.get_profile(element)
|
||||
if not representation_profile:
|
||||
return
|
||||
material_profile = material_tool.replace_material_with_material_profile(element=element)
|
||||
ifc.run("material.assign_profile", material_profile=material_profile, profile=representation_profile)
|
||||
|
||||
@@ -115,3 +115,8 @@ def expand_container(spatial, container=None):
|
||||
def delete_container(ifc, spatial, geometry, container=None):
|
||||
geometry.delete_ifc_object(ifc.get_object(container))
|
||||
spatial.load_container_manager()
|
||||
|
||||
def select_decomposed_elements(spatial):
|
||||
container = spatial.get_active_container()
|
||||
if container:
|
||||
spatial.select_products(spatial.get_decomposed_elements(container))
|
||||
|
||||
@@ -33,6 +33,17 @@ def add_style(ifc, style, obj=None):
|
||||
return element
|
||||
|
||||
|
||||
def add_external_style(ifc, style, obj, attributes):
|
||||
element = style.get_style(obj)
|
||||
ifc.run(
|
||||
"style.add_surface_style", style=element, ifc_class="IfcExternallyDefinedSurfaceStyle", attributes=attributes
|
||||
)
|
||||
|
||||
|
||||
def update_external_style(ifc, style, external_style, attributes):
|
||||
ifc.run("style.edit_surface_style", style=external_style, attributes=attributes)
|
||||
|
||||
|
||||
def remove_style(ifc, material, style_tool, style=None):
|
||||
obj = ifc.get_object(style)
|
||||
ifc.unlink(obj=obj, element=style)
|
||||
@@ -43,18 +54,32 @@ def remove_style(ifc, material, style_tool, style=None):
|
||||
style_tool.import_presentation_styles(style_tool.get_active_style_type())
|
||||
|
||||
|
||||
def update_style_colours(ifc, style, obj=None):
|
||||
def update_style_colours(ifc, style, obj=None, verbose=False):
|
||||
element = style.get_style(obj)
|
||||
|
||||
if style.can_support_rendering_style(obj):
|
||||
rendering_style = style.get_surface_rendering_style(obj)
|
||||
attributes = style.get_surface_rendering_attributes(obj)
|
||||
texture_style = style.get_texture_style(obj)
|
||||
attributes = style.get_surface_rendering_attributes(obj, verbose)
|
||||
if rendering_style:
|
||||
ifc.run("style.edit_surface_style", style=rendering_style, attributes=attributes)
|
||||
else:
|
||||
ifc.run(
|
||||
"style.add_surface_style", style=element, ifc_class="IfcSurfaceStyleRendering", attributes=attributes
|
||||
)
|
||||
|
||||
# TODO: uvs?
|
||||
textures = ifc.run("style.add_surface_textures", material=obj)
|
||||
if not texture_style and textures:
|
||||
ifc.run(
|
||||
"style.add_surface_style",
|
||||
style=element,
|
||||
ifc_class="IfcSurfaceStyleWithTextures",
|
||||
attributes={"Textures": textures},
|
||||
)
|
||||
elif texture_style:
|
||||
# TODO: should we remove blender images and IFCIMAGETEXTURE here if they're not used by other objects?
|
||||
ifc.run("style.edit_surface_style", style=texture_style, attributes={"Textures": textures})
|
||||
else:
|
||||
shading_style = style.get_surface_shading_style(obj)
|
||||
attributes = style.get_surface_shading_attributes(obj)
|
||||
@@ -95,16 +120,33 @@ def enable_editing_style(style, obj=None):
|
||||
style.import_surface_attributes(style.get_style(obj), obj)
|
||||
|
||||
|
||||
def enable_editing_external_style(style, obj=None):
|
||||
external_style = style.get_external_style(obj)
|
||||
style.enable_editing_external_style(obj)
|
||||
style.import_external_style_attributes(external_style, obj)
|
||||
|
||||
|
||||
def disable_editing_style(style, obj=None):
|
||||
style.disable_editing(obj)
|
||||
|
||||
|
||||
def disable_editing_external_style(style, obj=None):
|
||||
style.disable_editing_external_style(obj)
|
||||
|
||||
|
||||
def edit_style(ifc, style, obj=None):
|
||||
attributes = style.export_surface_attributes(obj)
|
||||
ifc.run("style.edit_presentation_style", style=style.get_style(obj), attributes=attributes)
|
||||
style.disable_editing(obj)
|
||||
|
||||
|
||||
def edit_external_style(ifc, style, obj=None):
|
||||
attributes = style.export_external_style_attributes(obj)
|
||||
external_style = style.get_style_elements(obj)["IfcExternallyDefinedSurfaceStyle"]
|
||||
update_external_style(ifc, style, external_style, attributes)
|
||||
style.disable_editing_external_style(obj)
|
||||
|
||||
|
||||
def load_styles(style, style_type=None):
|
||||
style.import_presentation_styles(style_type)
|
||||
style.enable_editing_styles()
|
||||
@@ -114,5 +156,5 @@ def disable_editing_styles(style):
|
||||
style.disable_editing_styles()
|
||||
|
||||
|
||||
def select_by_style(style_tool, style=None):
|
||||
style_tool.select_elements(style_tool.get_elements_by_style(style))
|
||||
def select_by_style(style_tool, spatial, style=None):
|
||||
spatial.select_products(style_tool.get_elements_by_style(style))
|
||||
|
||||
@@ -66,14 +66,14 @@ def select_system_products(system_tool, system=None):
|
||||
system_tool.select_system_products(system)
|
||||
|
||||
|
||||
def show_ports(ifc, system, element=None):
|
||||
def show_ports(ifc, system, spatial, element=None):
|
||||
obj = ifc.get_object(element)
|
||||
if obj and ifc.is_moved(obj):
|
||||
system.run_geometry_edit_object_placement(obj=obj)
|
||||
|
||||
ports = system.get_ports(element)
|
||||
system.load_ports(element, ports)
|
||||
system.select_elements(ports)
|
||||
spatial.select_products(ports)
|
||||
|
||||
|
||||
def hide_ports(ifc, system, element=None):
|
||||
|
||||
@@ -165,7 +165,7 @@ class Cost:
|
||||
def expand_cost_item_rate(cls, cost_item): pass
|
||||
def expand_cost_item(cls, cost_item): pass
|
||||
def expand_cost_items(cls): pass
|
||||
def export_cost_schedules(cls, format, cost_schedule): pass
|
||||
def export_cost_schedules(cls, filepath, format, cost_schedule): pass
|
||||
def format_unit(cls, unit): pass
|
||||
def get_active_cost_item(cls): pass
|
||||
def get_active_cost_schedule(cls): pass
|
||||
@@ -311,7 +311,7 @@ class Drawing:
|
||||
def run_drawing_activate_model(cls): pass
|
||||
def run_root_assign_class(cls, obj=None, ifc_class=None, predefined_type=None, should_add_representation=True, context=None, ifc_representation_class=None): pass
|
||||
def select_assigned_product(cls, drawing): pass
|
||||
def set_drawing_collection_name(cls, group, collection): pass
|
||||
def set_drawing_collection_name(cls, drawing, collection): pass
|
||||
def set_name(cls, element, name): pass
|
||||
def setup_annotation_object(cls, obj, object_type): pass
|
||||
def setup_shading_styles_path(cls, resource_path): pass
|
||||
@@ -433,21 +433,28 @@ class Loader:
|
||||
@interface
|
||||
class Material:
|
||||
def add_default_material_object(cls): pass
|
||||
def add_material_to_set(cls, material_set, material): pass
|
||||
def delete_object(cls, obj): pass
|
||||
def disable_editing_material(cls): pass
|
||||
def disable_editing_materials(cls): pass
|
||||
def enable_editing_material(cls, material): pass
|
||||
def enable_editing_materials(cls): pass
|
||||
def get_active_material_type(cls): pass
|
||||
def get_active_material_type(cls): pass
|
||||
def get_active_material(cls): pass
|
||||
def get_active_object_material(cls, obj): pass
|
||||
def get_elements_by_material(cls, material): pass
|
||||
def get_material_attributes(cls): pass
|
||||
def get_material(cls, element, should_inherit): pass
|
||||
def get_name(cls, obj): pass
|
||||
def get_type(cls, element): pass
|
||||
def has_material_profile(cls, element): pass
|
||||
def import_material_definitions(cls, material_type): pass
|
||||
def is_a_flow_segment(cls, element): pass
|
||||
def is_a_material_set(cls, material): pass
|
||||
def is_editing_materials(cls): pass
|
||||
def is_material_used_in_sets(cls, material): pass
|
||||
def load_material_attributes(cls, material): pass
|
||||
def select_elements(cls, elements): pass
|
||||
def replace_material_with_material_profile(cls, element): pass
|
||||
|
||||
|
||||
@interface
|
||||
@@ -547,6 +554,8 @@ class Project:
|
||||
@interface
|
||||
class Profile:
|
||||
def draw_image_for_ifc_profile(cls, draw, profile, size): pass
|
||||
def is_editing_profile(cls): pass
|
||||
def get_profile(cls, element): pass
|
||||
|
||||
|
||||
@interface
|
||||
@@ -806,8 +815,11 @@ class Style:
|
||||
def get_elements_by_style(cls, style): pass
|
||||
def get_name(cls, obj): pass
|
||||
def get_style(cls, obj): pass
|
||||
def get_surface_rendering_attributes(cls, obj): pass
|
||||
def get_style_elements(cls, blender_material): pass
|
||||
def get_surface_rendering_attributes(cls, obj, verbose=True): pass
|
||||
def get_surface_rendering_style(cls, obj): pass
|
||||
def get_texture_style(cls, obj): pass
|
||||
def get_external_style(cls, obj): pass
|
||||
def get_surface_shading_attributes(cls, obj): pass
|
||||
def get_surface_shading_style(cls, obj): pass
|
||||
def get_surface_texture_style(cls, obj): pass
|
||||
@@ -816,7 +828,6 @@ class Style:
|
||||
def import_surface_attributes(cls, style, obj): pass
|
||||
def is_editing_styles(cls): pass
|
||||
def record_shading(cls, obj): pass
|
||||
def select_elements(cls, elements): pass
|
||||
|
||||
|
||||
@interface
|
||||
@@ -839,7 +850,6 @@ class System:
|
||||
def load_ports(cls, element, ports): pass
|
||||
def run_geometry_edit_object_placement(cls, obj=None): pass
|
||||
def run_root_assign_class(cls, obj=None, ifc_class=None, predefined_type=None, should_add_representation=True, context=None, ifc_representation_class=None): pass
|
||||
def select_elements(cls, elements): pass
|
||||
def select_system_products(cls, system): pass
|
||||
def set_active_system(cls, system): pass
|
||||
|
||||
|
||||
@@ -34,8 +34,8 @@ def assign_type(ifc, type_tool, element=None, type=None):
|
||||
def purge_unused_types(ifc, type):
|
||||
for element_type in type.get_model_types():
|
||||
if not type.get_type_occurrences(element_type):
|
||||
ifc.run("root.remove_product", product=element_type)
|
||||
obj = ifc.get_object(element_type)
|
||||
ifc.run("root.remove_product", product=element_type)
|
||||
if obj:
|
||||
ifc.unlink(obj=obj)
|
||||
type.remove_object(obj)
|
||||
|
||||
@@ -20,6 +20,19 @@ import bpy
|
||||
import ifcopenshell.api
|
||||
import blenderbim.tool as tool
|
||||
from mathutils import Vector
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
VIEWPORT_ATTRIBUTES = [
|
||||
"view_matrix",
|
||||
"view_distance",
|
||||
"view_perspective",
|
||||
"use_box_clip",
|
||||
"use_clip_planes",
|
||||
"is_perspective",
|
||||
"show_sync_view",
|
||||
"clip_planes",
|
||||
]
|
||||
|
||||
|
||||
class Blender:
|
||||
@@ -94,6 +107,24 @@ class Blender:
|
||||
return False
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def show_error_message(cls, text):
|
||||
"""useful for showing error messages outside blender operators"""
|
||||
|
||||
def error(self, context):
|
||||
self.layout.label(text=text)
|
||||
|
||||
bpy.context.window_manager.popup_menu(error, title="Error", icon="ERROR")
|
||||
|
||||
@classmethod
|
||||
def get_blender_prop_default_value(cls, props, prop_name):
|
||||
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
|
||||
else:
|
||||
prop_value = prop_bl_rna.default
|
||||
return prop_value
|
||||
|
||||
@classmethod
|
||||
def get_viewport_context(cls):
|
||||
"""Get viewport area context for context overriding.
|
||||
@@ -108,11 +139,70 @@ class Blender:
|
||||
return context_override
|
||||
|
||||
@classmethod
|
||||
def update_viewport(cls):
|
||||
# if it stops working in future Blender versions
|
||||
# there is an alternative:
|
||||
# bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1)
|
||||
def get_viewport_position(cls):
|
||||
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):
|
||||
region_3d = cls.get_viewport_context()["area"].spaces[0].region_3d
|
||||
for attr in VIEWPORT_ATTRIBUTES:
|
||||
setattr(region_3d, attr, data[attr])
|
||||
|
||||
@classmethod
|
||||
def get_shader_editor_context(cls):
|
||||
for screen in bpy.data.screens:
|
||||
for area in screen.areas:
|
||||
if area.type == "NODE_EDITOR":
|
||||
for space in area.spaces:
|
||||
if space.tree_type == "ShaderNodeTree":
|
||||
context_override = {"area": area, "space": space, "screen": screen}
|
||||
return context_override
|
||||
|
||||
@classmethod
|
||||
def copy_node_graph(cls, material_to, material_from):
|
||||
temp_override = cls.get_shader_editor_context()
|
||||
shader_editor = temp_override["space"]
|
||||
|
||||
# remove all nodes from the current material
|
||||
for n in material_to.node_tree.nodes[:]:
|
||||
material_to.node_tree.nodes.remove(n)
|
||||
|
||||
previous_pin_setting = shader_editor.pin
|
||||
# required to be able to change material to something else
|
||||
shader_editor.pin = True
|
||||
shader_editor.node_tree = material_from.node_tree
|
||||
|
||||
# select all nodes and copy them to clipboard
|
||||
for node in material_from.node_tree.nodes:
|
||||
node.select = True
|
||||
bpy.ops.node.clipboard_copy(temp_override)
|
||||
|
||||
# back to original material
|
||||
shader_editor.node_tree = material_to.node_tree
|
||||
bpy.ops.node.clipboard_paste(temp_override, offset=(0, 0))
|
||||
|
||||
# restore shader editor settings
|
||||
shader_editor.pin = previous_pin_setting
|
||||
|
||||
@classmethod
|
||||
def get_material_node(cls, blender_material, node_type, kwargs={}):
|
||||
"""returns first node from the `blender_material` shader graph with type `node_type`"""
|
||||
if not blender_material.use_nodes:
|
||||
return
|
||||
nodes = blender_material.node_tree.nodes
|
||||
for node in nodes:
|
||||
if node.type == node_type and all(getattr(node, a) == kwargs[a] for a in kwargs):
|
||||
return node
|
||||
|
||||
@classmethod
|
||||
def update_screen(cls):
|
||||
bpy.ops.wm.redraw_timer(type="DRAW_WIN_SWAP", iterations=1)
|
||||
|
||||
@classmethod
|
||||
def update_viewport(cls):
|
||||
tool.Blender.get_viewport_context()["area"].tag_redraw()
|
||||
|
||||
@classmethod
|
||||
@@ -215,6 +305,20 @@ class Blender:
|
||||
context.view_layer.objects.active = active_object
|
||||
active_object.select_set(True)
|
||||
|
||||
@classmethod
|
||||
def append_data_block(cls, filepath, data_block_type, name, link=False, relative=False):
|
||||
if Path(filepath) == Path(bpy.data.filepath):
|
||||
data_block = getattr(bpy.data, data_block_type).get(name, None)
|
||||
if not data_block:
|
||||
return {"data_block": None, "msg": f"Data-block {data_block_type}/{name} not found in {filepath}"}
|
||||
return {"data_block": data_block.copy(), "msg": ""}
|
||||
|
||||
with bpy.data.libraries.load(filepath, link=link, relative=relative) as (data_from, data_to):
|
||||
if name not in getattr(data_from, data_block_type):
|
||||
return {"data_block": None, "msg": f"Data-block {data_block_type}/{name} not found in {filepath}"}
|
||||
getattr(data_to, data_block_type).append(name)
|
||||
return {"data_block": getattr(data_to, data_block_type)[0], "msg": ""}
|
||||
|
||||
## BMESH UTILS ##
|
||||
@classmethod
|
||||
def apply_bmesh(cls, mesh, bm, obj=None):
|
||||
|
||||
@@ -143,11 +143,8 @@ class Collector(blenderbim.core.tool.Collector):
|
||||
return axes_col[0]
|
||||
return bpy.data.collections.new(axes)
|
||||
|
||||
if element.is_a("IfcAnnotation"):
|
||||
for rel in element.HasAssignments or []:
|
||||
if rel.is_a("IfcRelAssignsToGroup") and rel.RelatingGroup.ObjectType == "DRAWING":
|
||||
name = "IfcGroup/" + rel.RelatingGroup.Name
|
||||
return bpy.data.collections.get(name) or bpy.data.collections.new(name)
|
||||
if element.is_a("IfcAnnotation") and element.ObjectType == "DRAWING":
|
||||
return cls._create_own_collection(obj)
|
||||
|
||||
if element.is_a("IfcStructuralMember"):
|
||||
return bpy.data.collections.get("Members") or bpy.data.collections.new("Members")
|
||||
@@ -178,12 +175,18 @@ class Collector(blenderbim.core.tool.Collector):
|
||||
axes = "WAxes"
|
||||
grid_obj = tool.Ifc.get_object(grid)
|
||||
if grid_obj:
|
||||
return bpy.data.collections.get(grid_obj.name)
|
||||
return grid_obj.BIMObjectProperties.collection
|
||||
|
||||
if element.is_a("IfcAnnotation"):
|
||||
if element.ObjectType == "DRAWING":
|
||||
return cls._create_project_child_collection("Views")
|
||||
for rel in element.HasAssignments or []:
|
||||
if rel.is_a("IfcRelAssignsToGroup") and rel.RelatingGroup.ObjectType == "DRAWING":
|
||||
return cls._create_project_child_collection("Views")
|
||||
for related_object in rel.RelatedObjects:
|
||||
if related_object.is_a("IfcAnnotation") and related_object.ObjectType == "DRAWING":
|
||||
drawing_obj = tool.Ifc.get_object(related_object)
|
||||
if drawing_obj:
|
||||
return drawing_obj.BIMObjectProperties.collection
|
||||
|
||||
if element.is_a("IfcStructuralItem"):
|
||||
return cls._create_project_child_collection("StructuralItems")
|
||||
|
||||
@@ -96,11 +96,11 @@ class Cost(blenderbim.core.tool.Cost):
|
||||
props.contracted_cost_items = json.dumps(cls.contracted_cost_items)
|
||||
|
||||
@classmethod
|
||||
def contract_cost_item(cls, cost_item):
|
||||
def contract_cost_item(cls, cost_item_id):
|
||||
props = bpy.context.scene.BIMCostProperties
|
||||
if not hasattr(cls, "contracted_cost_items"):
|
||||
cls.contracted_cost_items = json.loads(props.contracted_cost_items)
|
||||
cls.contracted_cost_items.append(cost_item.id())
|
||||
cls.contracted_cost_items.append(cost_item_id)
|
||||
props.contracted_cost_items = json.dumps(cls.contracted_cost_items)
|
||||
|
||||
@classmethod
|
||||
@@ -114,11 +114,11 @@ class Cost(blenderbim.core.tool.Cost):
|
||||
props.contracted_cost_items = json.dumps(cls.contracted_cost_items)
|
||||
|
||||
@classmethod
|
||||
def clean_up_cost_item_tree(cls, cost_item):
|
||||
def clean_up_cost_item_tree(cls, cost_item_id):
|
||||
props = bpy.context.scene.BIMCostProperties
|
||||
if not hasattr(cls, "contracted_cost_items"):
|
||||
cls.contracted_cost_items = json.loads(props.contracted_cost_items)
|
||||
if props.active_cost_item_id == cost_item.id():
|
||||
if props.active_cost_item_id == cost_item_id:
|
||||
props.active_cost_item_id = 0
|
||||
if props.active_cost_item_index in cls.contracted_cost_items:
|
||||
cls.contracted_cost_items.remove(props.active_cost_item_index)
|
||||
@@ -505,11 +505,15 @@ class Cost(blenderbim.core.tool.Cost):
|
||||
props.is_cost_update_enabled = True
|
||||
|
||||
@classmethod
|
||||
def export_cost_schedules(cls, format=None, cost_schedule=None):
|
||||
def export_cost_schedules(cls, filepath, format=None, cost_schedule=None):
|
||||
import subprocess
|
||||
import os
|
||||
import sys
|
||||
path = os.path.join(bpy.context.scene.BIMProperties.data_dir, "cost_schedules")
|
||||
if filepath:
|
||||
path=filepath
|
||||
else:
|
||||
path = os.path.join(bpy.context.scene.BIMProperties.data_dir, "build", "cost_schedules")
|
||||
|
||||
if not os.path.exists(path):
|
||||
os.makedirs(path)
|
||||
if format == "CSV":
|
||||
|
||||
@@ -364,7 +364,7 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
def get_drawing_collection(cls, drawing):
|
||||
obj = tool.Ifc.get_object(drawing)
|
||||
if obj:
|
||||
return obj.users_collection[0]
|
||||
return obj.BIMObjectProperties.collection
|
||||
|
||||
@classmethod
|
||||
def get_drawing_group(cls, drawing):
|
||||
@@ -759,8 +759,8 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def set_drawing_collection_name(cls, group, collection):
|
||||
collection.name = f"IfcGroup/{group.Name}"
|
||||
def set_drawing_collection_name(cls, drawing, collection):
|
||||
collection.name = tool.Loader.get_name(drawing)
|
||||
|
||||
@classmethod
|
||||
def set_name(cls, element, name):
|
||||
@@ -1486,8 +1486,8 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
project_collection.children["Views"].children[collection.name].hide_viewport = True
|
||||
bpy.data.collections.get(collection.name).hide_render = True
|
||||
|
||||
project_collection.children["Views"].children[camera.users_collection[0].name].hide_viewport = False
|
||||
bpy.data.collections.get(camera.users_collection[0].name).hide_render = False
|
||||
project_collection.children["Views"].children[camera.BIMObjectProperties.collection.name].hide_viewport = False
|
||||
camera.BIMObjectProperties.collection.hide_render = False
|
||||
tool.Spatial.set_active_object(camera)
|
||||
|
||||
# Sync viewport objects visibility with selectors from EPset_Drawing/Include and /Exclude
|
||||
@@ -1498,12 +1498,11 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
bpy.ops.object.hide_view_clear()
|
||||
|
||||
filtered_elements = cls.get_drawing_elements(drawing) | cls.get_drawing_spaces(drawing)
|
||||
hidden_objs = [o for o in bpy.context.visible_objects if tool.Ifc.get_entity(o) not in filtered_elements]
|
||||
|
||||
for hidden_obj in hidden_objs:
|
||||
if bpy.context.view_layer.objects.get(hidden_obj.name):
|
||||
hidden_obj.hide_set(True)
|
||||
hidden_obj.hide_render = True
|
||||
for visible_obj in bpy.context.visible_objects:
|
||||
hide = tool.Ifc.get_entity(visible_obj) not in filtered_elements
|
||||
if bpy.context.view_layer.objects.get(visible_obj.name):
|
||||
visible_obj.hide_set(hide)
|
||||
visible_obj.hide_render = hide
|
||||
|
||||
subcontexts = []
|
||||
target_view = cls.get_drawing_target_view(drawing)
|
||||
|
||||
@@ -356,6 +356,16 @@ class Geometry(blenderbim.core.tool.Geometry):
|
||||
return True
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def is_profile_based(cls, data):
|
||||
return data.BIMMeshProperties.subshape_type == "PROFILE"
|
||||
|
||||
@classmethod
|
||||
def is_swept_profile(cls, representation):
|
||||
return ifcopenshell.util.representation.resolve_representation(representation).RepresentationType in (
|
||||
"SweptSolid",
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def is_type_product(cls, element):
|
||||
return element.is_a("IfcTypeProduct")
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
|
||||
# allows git import even if git executable isn't found
|
||||
os.environ["GIT_PYTHON_REFRESH"] = "quiet"
|
||||
@@ -103,6 +102,30 @@ class IfcGit:
|
||||
if tag_name in repo.tags:
|
||||
repo.delete_tag(tag_name)
|
||||
|
||||
@classmethod
|
||||
def add_remote(cls, repo):
|
||||
props = bpy.context.scene.IfcGitProperties
|
||||
repo.create_remote(name=props.remote_name, url=props.remote_url)
|
||||
props.remote_name = ""
|
||||
props.remote_url = ""
|
||||
|
||||
@classmethod
|
||||
def delete_remote(cls, repo):
|
||||
props = bpy.context.scene.IfcGitProperties
|
||||
remote_name = props.select_remote
|
||||
if remote_name in repo.remotes:
|
||||
repo.delete_remote(remote_name)
|
||||
if repo.remotes:
|
||||
props.select_remote = repo.remotes[0].name
|
||||
|
||||
@classmethod
|
||||
def push(cls, repo, remote_name, branch_name):
|
||||
remote = repo.remotes[remote_name]
|
||||
try:
|
||||
remote.push(tags=True, refspec=branch_name).raise_if_error()
|
||||
except git.exc.GitCommandError as exc:
|
||||
return exc.stderr
|
||||
|
||||
@classmethod
|
||||
def create_new_branch(cls):
|
||||
props = bpy.context.scene.IfcGitProperties
|
||||
@@ -369,11 +392,15 @@ class IfcGit:
|
||||
@classmethod
|
||||
def config_ifcmerge(cls):
|
||||
config_reader = IfcGitRepo.repo.config_reader()
|
||||
config_writer = IfcGitRepo.repo.config_writer()
|
||||
section = 'mergetool "ifcmerge"'
|
||||
if not config_reader.has_section(section):
|
||||
config_writer = IfcGitRepo.repo.config_writer()
|
||||
config_writer.set_value(section, "cmd", "ifcmerge $BASE $LOCAL $REMOTE $MERGED")
|
||||
config_writer.set_value(section, "trustExitCode", True)
|
||||
section = 'mergetool "ifcmerge-forward"'
|
||||
if not config_reader.has_section(section):
|
||||
config_writer.set_value(section, "cmd", "ifcmerge $BASE $REMOTE $LOCAL $MERGED")
|
||||
config_writer.set_value(section, "trustExitCode", True)
|
||||
|
||||
@classmethod
|
||||
def config_info_attributes(cls, repo):
|
||||
@@ -402,13 +429,19 @@ class IfcGit:
|
||||
for branch in lookup[item.hexsha]:
|
||||
if branch.name == props.display_branch:
|
||||
# this is a branch!
|
||||
if re.match("^(origin/)?(HEAD|main|master)$", branch.name):
|
||||
# preserve remote IDs in origin/main or main
|
||||
mergetool = "ifcmerge"
|
||||
else:
|
||||
# rewrite remote IDs
|
||||
mergetool = "ifcmerge-forward"
|
||||
try:
|
||||
# NOTE this is calling the git binary in a subprocess
|
||||
repo.git.merge(branch)
|
||||
except git.exc.GitCommandError:
|
||||
# merge is expected to fail, run ifcmerge
|
||||
try:
|
||||
repo.git.mergetool(tool="ifcmerge")
|
||||
repo.git.mergetool(tool=mergetool)
|
||||
except git.exc.GitCommandError as exc:
|
||||
message = re.sub("( stderr: '|')", "", exc.stderr)
|
||||
# ifcmerge failed, rollback
|
||||
@@ -416,23 +449,16 @@ class IfcGit:
|
||||
|
||||
operator.report({"ERROR"}, "IFC Merge failed:" + message)
|
||||
return False
|
||||
else:
|
||||
if os.name == "nt":
|
||||
cls.dos2unix(path_ifc)
|
||||
repo.index.add(path_ifc)
|
||||
repo.git.commit("--no-edit")
|
||||
except:
|
||||
|
||||
operator.report({"ERROR"}, "Unknown IFC Merge failure")
|
||||
return False
|
||||
|
||||
repo.index.add(path_ifc)
|
||||
|
||||
message_summary = ""
|
||||
branch_commits = repo.iter_commits(rev=repo.active_branch.name + ".." + props.display_branch)
|
||||
for commit in branch_commits:
|
||||
message_summary += "\n\n" + commit.author.name + " <" + commit.author.email + ">\n"
|
||||
message_summary += time.strftime("%c", time.localtime(commit.committed_date)) + "\n"
|
||||
message_summary += commit.message
|
||||
|
||||
props.commit_message = (
|
||||
"Merge branch '" + props.display_branch + "' into " + repo.active_branch.name + message_summary
|
||||
)
|
||||
props.display_branch = repo.active_branch.name
|
||||
|
||||
cls.load_project(path_ifc)
|
||||
|
||||
@@ -21,6 +21,9 @@ import bpy
|
||||
import ifcopenshell.util.element
|
||||
import blenderbim.core.tool
|
||||
import blenderbim.tool as tool
|
||||
import os
|
||||
from mathutils import Vector
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
# Progressively we'll refactor loading elements into Blender objects into this
|
||||
@@ -28,6 +31,7 @@ import blenderbim.tool as tool
|
||||
# partially load and unload objects for huge models, partial model editing, and
|
||||
# supplementary objects (e.g. drawings, structural analysis models, etc).
|
||||
|
||||
|
||||
class Loader(blenderbim.core.tool.Loader):
|
||||
@classmethod
|
||||
def get_mesh_name(cls, geometry):
|
||||
@@ -52,3 +56,233 @@ class Loader(blenderbim.core.tool.Loader):
|
||||
else:
|
||||
# TODO: See #2002
|
||||
mesh.BIMMeshProperties.ifc_definition_id = int(geometry.id.replace(",", ""))
|
||||
|
||||
@classmethod
|
||||
def create_surface_style_shading(cls, blender_material, surface_style):
|
||||
surface_style = cls.surface_style_to_dict(surface_style)
|
||||
alpha = 1.0
|
||||
# Transparency was added in IFC4
|
||||
if transparency := surface_style.get("Transparency", None):
|
||||
alpha = 1 - transparency
|
||||
blender_material.diffuse_color = surface_style["SurfaceColour"][:3] + (alpha,)
|
||||
|
||||
@classmethod
|
||||
def restart_material_node_tree(cls, blender_material):
|
||||
nodes = blender_material.node_tree.nodes
|
||||
links = blender_material.node_tree.links
|
||||
for n in nodes[:]:
|
||||
nodes.remove(n)
|
||||
output = nodes.new("ShaderNodeOutputMaterial")
|
||||
output.location = Vector((300, 300))
|
||||
bsdf = nodes.new("ShaderNodeBsdfPrincipled")
|
||||
bsdf.location = Vector((10, 300))
|
||||
links.new(bsdf.outputs["BSDF"], output.inputs["Surface"])
|
||||
|
||||
@classmethod
|
||||
def surface_style_to_dict(cls, surface_style):
|
||||
if isinstance(surface_style, dict):
|
||||
return surface_style
|
||||
surface_style = surface_style.get_info()
|
||||
color_to_tuple = lambda x: (x.Red, x.Green, x.Blue, 1)
|
||||
|
||||
if surface_style["SurfaceColour"]:
|
||||
surface_style["SurfaceColour"] = color_to_tuple(surface_style["SurfaceColour"])
|
||||
|
||||
if surface_style.get("DiffuseColour", None) and surface_style["DiffuseColour"].is_a("IfcColourRgb"):
|
||||
surface_style["DiffuseColour"] = ("IfcColourRgb", color_to_tuple(surface_style["DiffuseColour"]))
|
||||
|
||||
elif surface_style.get("DiffuseColour", None) and surface_style["DiffuseColour"].is_a("IfcNormalisedRatioMeasure"):
|
||||
diffuse_color_value = surface_style["DiffuseColour"].wrappedValue
|
||||
diffuse_color = [v * diffuse_color_value for v in surface_style["SurfaceColor"][:3]] + [1]
|
||||
surface_style["DiffuseColour"] = ("IfcNormalisedRatioMeasure", diffuse_color)
|
||||
else:
|
||||
surface_style["DiffuseColour"] = None
|
||||
|
||||
if surface_style.get("SpecularColour", None) and surface_style["SpecularColour"].is_a("IfcNormalisedRatioMeasure"):
|
||||
surface_style["SpecularColour"] = surface_style["SpecularColour"].wrappedValue
|
||||
else:
|
||||
surface_style["SpecularColour"] = None
|
||||
|
||||
if surface_style.get("SpecularHighlight", None) and surface_style["SpecularHighlight"].is_a("IfcSpecularRoughness"):
|
||||
surface_style["SpecularHighlight"] = surface_style["SpecularHighlight"].wrappedValue
|
||||
else:
|
||||
surface_style["SpecularHighlight"] = None
|
||||
return surface_style
|
||||
|
||||
@classmethod
|
||||
def create_surface_style_rendering(cls, blender_material, surface_style):
|
||||
surface_style = cls.surface_style_to_dict(surface_style)
|
||||
cls.create_surface_style_shading(blender_material, surface_style)
|
||||
|
||||
reflectance_method = surface_style["ReflectanceMethod"]
|
||||
if reflectance_method not in ("PHYSICAL", "NOTDEFINED", "FLAT"):
|
||||
print(f'WARNING. Unsupported reflectance method "{reflectance_method}" on style {surface_style}')
|
||||
return
|
||||
|
||||
if reflectance_method in ["PHYSICAL", "NOTDEFINED"]:
|
||||
blender_material.use_nodes = True
|
||||
cls.restart_material_node_tree(blender_material)
|
||||
bsdf = tool.Blender.get_material_node(blender_material, "BSDF_PRINCIPLED")
|
||||
if surface_style["DiffuseColour"]:
|
||||
color_type, color_value = surface_style["DiffuseColour"]
|
||||
if color_type == "IfcColourRgb":
|
||||
bsdf.inputs["Base Color"].default_value = color_value
|
||||
elif color_type == "IfcNormalisedRatioMeasure":
|
||||
bsdf.inputs["Base Color"].default_value = color_value
|
||||
if surface_style["SpecularColour"]:
|
||||
bsdf.inputs["Metallic"].default_value = surface_style["SpecularColour"]
|
||||
if surface_style["SpecularHighlight"]:
|
||||
bsdf.inputs["Roughness"].default_value = surface_style["SpecularHighlight"]
|
||||
if transparency := surface_style.get("Transparency", None):
|
||||
bsdf.inputs["Alpha"].default_value = 1 - transparency
|
||||
blender_material.blend_method = "BLEND"
|
||||
|
||||
elif reflectance_method == "FLAT":
|
||||
blender_material.use_nodes = True
|
||||
cls.restart_material_node_tree(blender_material)
|
||||
|
||||
output = tool.Blender.get_material_node(blender_material, "OUTPUT_MATERIAL")
|
||||
bsdf = tool.Blender.get_material_node(blender_material, "BSDF_PRINCIPLED")
|
||||
|
||||
mix = blender_material.node_tree.nodes.new(type="ShaderNodeMixShader")
|
||||
mix.location = bsdf.location
|
||||
blender_material.node_tree.links.new(mix.outputs[0], output.inputs["Surface"])
|
||||
|
||||
blender_material.node_tree.nodes.remove(bsdf)
|
||||
|
||||
lightpath = blender_material.node_tree.nodes.new(type="ShaderNodeLightPath")
|
||||
lightpath.location = mix.location - Vector((200, -200))
|
||||
blender_material.node_tree.links.new(lightpath.outputs[0], mix.inputs[0])
|
||||
|
||||
bsdf = blender_material.node_tree.nodes.new(type="ShaderNodeBsdfTransparent")
|
||||
bsdf.location = mix.location - Vector((200, 150))
|
||||
blender_material.node_tree.links.new(bsdf.outputs[0], mix.inputs[1])
|
||||
|
||||
rgb = blender_material.node_tree.nodes.new(type="ShaderNodeRGB")
|
||||
rgb.location = mix.location - Vector((200, 250))
|
||||
blender_material.node_tree.links.new(rgb.outputs[0], mix.inputs[2])
|
||||
|
||||
if surface_style["DiffuseColour"]:
|
||||
color_type, color_value = surface_style["DiffuseColour"]
|
||||
if color_type == "IfcColourRgb":
|
||||
rgb.outputs[0].default_value = color_value
|
||||
|
||||
@classmethod
|
||||
def create_surface_style_with_textures(cls, blender_material, rendering_style, texture_style):
|
||||
"""supposed to be called after `create_surface_style_rendering`"""
|
||||
if not isinstance(texture_style, list):
|
||||
textures = [t.get_info() for t in texture_style.Textures]
|
||||
else:
|
||||
textures = texture_style
|
||||
rendering_style = cls.surface_style_to_dict(rendering_style)
|
||||
|
||||
reflectance_method = rendering_style["ReflectanceMethod"]
|
||||
if reflectance_method not in ("PHYSICAL", "NOTDEFINED", "FLAT"):
|
||||
print(f'WARNING. Unsupported reflectance method "{reflectance_method}" on style {rendering_style}')
|
||||
return
|
||||
|
||||
for texture in textures:
|
||||
mode = texture.get("Mode", None)
|
||||
node = None
|
||||
|
||||
if texture["type"] == "IfcImageTexture":
|
||||
image_url = texture["URLReference"]
|
||||
ifc_path = tool.Ifc.get_path()
|
||||
if ifc_path:
|
||||
image_url = bpy.path.abspath(image_url, start=Path(ifc_path).parent)
|
||||
|
||||
if not os.path.exists(image_url):
|
||||
print(f"WARNING. Couldn't find texture by path {image_url}, it will be skipped.")
|
||||
continue
|
||||
|
||||
if reflectance_method in ["PHYSICAL", "NOTDEFINED"]:
|
||||
bsdf = tool.Blender.get_material_node(blender_material, "BSDF_PRINCIPLED")
|
||||
if mode == "NORMAL":
|
||||
# add normal map node
|
||||
normalmap = blender_material.node_tree.nodes.new(type="ShaderNodeNormalMap")
|
||||
normalmap.location = bsdf.location - Vector((200, 600))
|
||||
blender_material.node_tree.links.new(normalmap.outputs[0], bsdf.inputs["Normal"])
|
||||
|
||||
# add normal map sampler
|
||||
node = blender_material.node_tree.nodes.new(type="ShaderNodeTexImage")
|
||||
node.location = normalmap.location - Vector((300, 0))
|
||||
image = bpy.data.images.load(image_url)
|
||||
image.colorspace_settings.name = "Non-Color"
|
||||
node.image = image
|
||||
blender_material.node_tree.links.new(node.outputs[0], normalmap.inputs["Color"])
|
||||
|
||||
elif mode == "EMISSIVE":
|
||||
output = tool.Blender.get_material_node(blender_material, "OUTPUT_MATERIAL")
|
||||
|
||||
# add "Add Shader" node
|
||||
add = blender_material.node_tree.nodes.new(type="ShaderNodeAddShader")
|
||||
add.location = bsdf.location + Vector((200, 350))
|
||||
blender_material.node_tree.links.new(bsdf.outputs[0], add.inputs[1])
|
||||
blender_material.node_tree.links.new(add.outputs[0], output.inputs[0])
|
||||
|
||||
# add emssion shader node
|
||||
emission = blender_material.node_tree.nodes.new(type="ShaderNodeEmission")
|
||||
emission.location = add.location - Vector((200, 0))
|
||||
blender_material.node_tree.links.new(emission.outputs[0], add.inputs[0])
|
||||
|
||||
# add emission texture sampler
|
||||
node = blender_material.node_tree.nodes.new(type="ShaderNodeTexImage")
|
||||
node.location = emission.location - Vector((350, 0))
|
||||
image = bpy.data.images.load(image_url)
|
||||
node.image = image
|
||||
blender_material.node_tree.links.new(node.outputs[0], emission.inputs[0])
|
||||
|
||||
elif mode == "METALLICROUGHNESS":
|
||||
separate = blender_material.node_tree.nodes.new(type="ShaderNodeSeparateRGB")
|
||||
separate.location = bsdf.location - Vector((200, 300))
|
||||
blender_material.node_tree.links.new(separate.outputs[1], bsdf.inputs["Roughness"])
|
||||
blender_material.node_tree.links.new(separate.outputs[2], bsdf.inputs["Metallic"])
|
||||
|
||||
node = blender_material.node_tree.nodes.new(type="ShaderNodeTexImage")
|
||||
node.location = separate.location - Vector((300, 0))
|
||||
image = bpy.data.images.load(image_url)
|
||||
image.colorspace_settings.name = "Non-Color"
|
||||
node.image = image
|
||||
blender_material.node_tree.links.new(node.outputs[0], separate.inputs[0])
|
||||
|
||||
elif mode == "OCCLUSION":
|
||||
# TODO work out how to implement glTF settings here
|
||||
# https://docs.blender.org/manual/en/dev/addons/import_export/scene_gltf2.html
|
||||
pass
|
||||
|
||||
elif mode == "DIFFUSE":
|
||||
node = blender_material.node_tree.nodes.new(type="ShaderNodeTexImage")
|
||||
node.location = bsdf.location - Vector((400, 0))
|
||||
image = bpy.data.images.load(image_url)
|
||||
node.image = image
|
||||
blender_material.node_tree.links.new(node.outputs[0], bsdf.inputs["Base Color"])
|
||||
blender_material.node_tree.links.new(node.outputs[1], bsdf.inputs["Alpha"])
|
||||
blender_material.blend_method = "BLEND"
|
||||
|
||||
elif reflectance_method == "FLAT":
|
||||
bsdf = tool.Blender.get_material_node(blender_material, "MIX_SHADER")
|
||||
if mode != "EMISSIVE":
|
||||
continue
|
||||
|
||||
# remove RGB node from `create_surface_style_rendering`
|
||||
prev_node = bsdf.inputs[2].links[0].from_node
|
||||
blender_material.node_tree.nodes.remove(prev_node)
|
||||
|
||||
node = blender_material.node_tree.nodes.new(type="ShaderNodeTexImage")
|
||||
node.location = bsdf.location - Vector((200, 250))
|
||||
image = bpy.data.images.load(image_url)
|
||||
# TODO: orphaned textures after shader recreated?
|
||||
node.image = image
|
||||
blender_material.node_tree.links.new(node.outputs[0], bsdf.inputs[2])
|
||||
|
||||
# TODO: add support for texture data not ifc elements
|
||||
if node and getattr(texture, "IsMappedBy", None):
|
||||
coordinates = texture.IsMappedBy[0]
|
||||
coord = blender_material.node_tree.nodes.new(type="ShaderNodeTexCoord")
|
||||
coord.location = node.location - Vector((200, 0))
|
||||
if coordinates.is_a("IfcTextureCoordinateGenerator") and coordinates.Mode == "COORD":
|
||||
blender_material.node_tree.links.new(coord.outputs["Generated"], node.inputs["Vector"])
|
||||
elif coordinates.is_a("IfcTextureCoordinateGenerator") and coordinates.Mode == "COORD-EYE":
|
||||
blender_material.node_tree.links.new(coord.outputs["Camera"], node.inputs["Vector"])
|
||||
else:
|
||||
blender_material.node_tree.links.new(coord.outputs["UV"], node.inputs["Vector"])
|
||||
|
||||
@@ -21,6 +21,8 @@ import ifcopenshell
|
||||
import blenderbim.core.tool
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.bim.helper
|
||||
import ifcopenshell.util.unit
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
class Material(blenderbim.core.tool.Material):
|
||||
@@ -101,17 +103,6 @@ class Material(blenderbim.core.tool.Material):
|
||||
return True
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def select_elements(cls, elements):
|
||||
for element in elements:
|
||||
obj = tool.Ifc.get_object(element)
|
||||
if obj:
|
||||
obj.select_set(True)
|
||||
|
||||
@classmethod
|
||||
def get_active_material_type(cls):
|
||||
return bpy.context.scene.BIMMaterialProperties.material_type
|
||||
|
||||
@classmethod
|
||||
def load_material_attributes(cls, material):
|
||||
props = bpy.context.scene.BIMMaterialProperties
|
||||
@@ -122,7 +113,7 @@ class Material(blenderbim.core.tool.Material):
|
||||
def enable_editing_material(cls, material):
|
||||
props = bpy.context.scene.BIMMaterialProperties
|
||||
props.active_material_id = material.id()
|
||||
props.editing_material_type = "ATTRIBUTES"
|
||||
props.editing_material_type = "ATTRIBUTES"
|
||||
|
||||
@classmethod
|
||||
def get_material_attributes(cls):
|
||||
@@ -133,3 +124,101 @@ class Material(blenderbim.core.tool.Material):
|
||||
props = bpy.context.scene.BIMMaterialProperties
|
||||
props.active_material_id = 0
|
||||
props.editing_material_type = ""
|
||||
|
||||
@classmethod
|
||||
def get_type(cls, element):
|
||||
return ifcopenshell.util.element.get_type(element)
|
||||
|
||||
@classmethod
|
||||
def get_active_object_material(cls):
|
||||
active_obj = bpy.context.active_object
|
||||
if not active_obj:
|
||||
return
|
||||
return active_obj.BIMObjectMaterialProperties.material_type
|
||||
|
||||
@classmethod
|
||||
def get_active_material(cls):
|
||||
return tool.Ifc.get().by_id(int(bpy.context.active_object.BIMObjectMaterialProperties.material))
|
||||
|
||||
@classmethod
|
||||
def get_material(cls, element, should_inherit=False):
|
||||
return ifcopenshell.util.element.get_material(element, should_inherit=should_inherit)
|
||||
|
||||
@classmethod
|
||||
def is_a_material_set(cls, material):
|
||||
return material.is_a() in [
|
||||
"IfcMaterialConstituentSet",
|
||||
"IfcMaterialLayerSet",
|
||||
"IfcMaterialProfileSet",
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def add_material_to_set(cls, material_set, material):
|
||||
if material_set.is_a("IfcMaterialConstituentSet"):
|
||||
if not material_set.MaterialConstituents:
|
||||
tool.Ifc.run(
|
||||
"material.add_constituent",
|
||||
constituent_set=material_set,
|
||||
material=material,
|
||||
)
|
||||
elif material_set.is_a() == "IfcMaterialLayerSet":
|
||||
if not material_set.MaterialLayers:
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
layer = tool.Ifc.run(
|
||||
"material.add_layer",
|
||||
layer_set=material_set,
|
||||
material=material,
|
||||
)
|
||||
thickness = 0.1 # Arbitrary metric thickness for now
|
||||
layer.LayerThickness = thickness / unit_scale
|
||||
elif material_set.is_a("IfcMaterialProfileSet"):
|
||||
if not material_set.MaterialProfiles:
|
||||
named_profiles = [p for p in tool.Ifc.get().by_type("IfcProfileDef") if p.ProfileName]
|
||||
if named_profiles:
|
||||
profile = named_profiles[0]
|
||||
else:
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
size = 0.5 / unit_scale
|
||||
profile = tool.Ifc.get().create_entity(
|
||||
"IfcRectangleProfileDef",
|
||||
ProfileName="New Profile",
|
||||
ProfileType="AREA",
|
||||
XDim=size,
|
||||
YDim=size,
|
||||
)
|
||||
material_profile = tool.Ifc.run(
|
||||
"material.add_profile",
|
||||
profile_set=material_set,
|
||||
material=material,
|
||||
profile=profile,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def has_material_profile(cls, element):
|
||||
material = cls.get_material(element, should_inherit=False)
|
||||
inherited_material = cls.get_material(element, should_inherit=True)
|
||||
if material and "Profile" in material.is_a():
|
||||
return True
|
||||
if inherited_material and "Profile" in inherited_material.is_a():
|
||||
return True
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def is_a_flow_segment(cls, element):
|
||||
return element.is_a("IfcFlowSegment")
|
||||
|
||||
@classmethod
|
||||
def replace_material_with_material_profile(cls, element):
|
||||
old_material = cls.get_material(element, should_inherit=False)
|
||||
old_inherited_material = cls.get_material(element, should_inherit=True)
|
||||
material = old_material if old_material and old_material.is_a("IfcMaterial") else None
|
||||
if not material and old_inherited_material:
|
||||
material = old_inherited_material if old_inherited_material.is_a("IfcMaterial") else None
|
||||
if not 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)
|
||||
assinged_material = cls.get_material(element)
|
||||
material_profile = tool.Ifc.run("material.add_profile", profile_set=assinged_material, material=material)
|
||||
return material_profile
|
||||
|
||||
@@ -21,6 +21,7 @@ import ifcopenshell.util.unit
|
||||
import ifcopenshell.util.placement
|
||||
import ifcopenshell.util.representation
|
||||
import blenderbim.core.tool
|
||||
from blenderbim.bim.module.model.decorator import ProfileDecorator
|
||||
|
||||
|
||||
class Profile(blenderbim.core.tool.Profile):
|
||||
@@ -52,3 +53,20 @@ class Profile(blenderbim.core.tool.Profile):
|
||||
|
||||
for e in grouped_edges:
|
||||
draw.line((tuple(grouped_verts[e[0]]), tuple(grouped_verts[e[1]])), fill="white", width=2)
|
||||
|
||||
@classmethod
|
||||
def is_editing_profile(cls):
|
||||
return ProfileDecorator.installed
|
||||
|
||||
@classmethod
|
||||
def get_profile(cls, element):
|
||||
representations = element.Representation
|
||||
for representation in representations.Representations:
|
||||
if not representation.is_a("IfcShapeRepresentation"):
|
||||
continue
|
||||
for representation_item in representation.Items:
|
||||
if representation_item.is_a("IfcExtrudedAreaSolid"):
|
||||
profile = representation_item.SweptArea
|
||||
if profile:
|
||||
return profile
|
||||
return None
|
||||
|
||||
@@ -947,7 +947,7 @@ class Sequence(blenderbim.core.tool.Sequence):
|
||||
def set_material(name, r, g, b):
|
||||
material = bpy.data.materials.new(name)
|
||||
material.use_nodes = True
|
||||
material.node_tree.nodes["Principled BSDF"].inputs[0].default_value = (r, g, b, 1.0)
|
||||
tool.Blender.get_material_node(material, "BSDF_PRINCIPLED").inputs[0].default_value = (r, g, b, 1.0)
|
||||
return material
|
||||
|
||||
def get_animation_materials():
|
||||
|
||||
@@ -203,7 +203,7 @@ class Spatial(blenderbim.core.tool.Spatial):
|
||||
cls.props.is_container_update_enabled = False
|
||||
parent = tool.Ifc.get().by_type("IfcProject")[0]
|
||||
|
||||
for object in ifcopenshell.util.element.get_parts(parent):
|
||||
for object in ifcopenshell.util.element.get_parts(parent) or []:
|
||||
if object.is_a("IfcSpatialElement"):
|
||||
cls.create_new_storey_li(object, 0)
|
||||
cls.props.is_container_update_enabled = True
|
||||
@@ -222,7 +222,7 @@ class Spatial(blenderbim.core.tool.Spatial):
|
||||
if new.has_decomposition:
|
||||
new.has_children = True
|
||||
if new.is_expanded:
|
||||
for related_object in ifcopenshell.util.element.get_parts(element):
|
||||
for related_object in ifcopenshell.util.element.get_parts(element) or []:
|
||||
if related_object.is_a("IfcSpatialElement"):
|
||||
cls.create_new_storey_li(related_object, level_index + 1)
|
||||
|
||||
|
||||
@@ -22,6 +22,30 @@ import ifcopenshell
|
||||
import blenderbim.core.tool
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.bim.helper
|
||||
import os.path
|
||||
|
||||
# fmt: off
|
||||
TEXTURE_MAPS_BY_METHODS = {
|
||||
"PHYSICAL": ("NORMAL", "EMISSIVE", "METALLICROUGHNESS", "DIFFUSE"),
|
||||
"FLAT": ("EMISSIVE",)
|
||||
}
|
||||
# fmt: on
|
||||
|
||||
STYLE_PROPS_MAP = {
|
||||
"reflectance_method": "ReflectanceMethod",
|
||||
"diffuse_color": "DiffuseColour",
|
||||
"surface_color": "SurfaceColour",
|
||||
"transparency": "Transparency",
|
||||
"roughness": "SpecularHighlight",
|
||||
"metallic": "SpecularColour",
|
||||
}
|
||||
|
||||
STYLE_TEXTURE_PROPS_MAP = {
|
||||
"EMISSIVE": "emissive_path",
|
||||
"NORMAL": "normal_path",
|
||||
"METALLICROUGHNESS": "metallic_roughness_path",
|
||||
"DIFFUSE": "diffuse_path",
|
||||
}
|
||||
|
||||
|
||||
class Style(blenderbim.core.tool.Style):
|
||||
@@ -33,6 +57,10 @@ class Style(blenderbim.core.tool.Style):
|
||||
def disable_editing(cls, obj):
|
||||
obj.BIMStyleProperties.is_editing = False
|
||||
|
||||
@classmethod
|
||||
def disable_editing_external_style(cls, obj):
|
||||
obj.BIMStyleProperties.is_editing_external_style = False
|
||||
|
||||
@classmethod
|
||||
def disable_editing_styles(cls):
|
||||
bpy.context.scene.BIMStylesProperties.is_editing = False
|
||||
@@ -41,6 +69,10 @@ class Style(blenderbim.core.tool.Style):
|
||||
def enable_editing(cls, obj):
|
||||
obj.BIMStyleProperties.is_editing = True
|
||||
|
||||
@classmethod
|
||||
def enable_editing_external_style(cls, obj):
|
||||
obj.BIMStyleProperties.is_editing_external_style = True
|
||||
|
||||
@classmethod
|
||||
def enable_editing_styles(cls):
|
||||
bpy.context.scene.BIMStylesProperties.is_editing = True
|
||||
@@ -49,6 +81,10 @@ class Style(blenderbim.core.tool.Style):
|
||||
def export_surface_attributes(cls, obj):
|
||||
return blenderbim.bim.helper.export_attributes(obj.BIMStyleProperties.attributes)
|
||||
|
||||
@classmethod
|
||||
def export_external_style_attributes(cls, obj):
|
||||
return blenderbim.bim.helper.export_attributes(obj.BIMStyleProperties.external_style_attributes)
|
||||
|
||||
@classmethod
|
||||
def get_active_style_type(cls):
|
||||
return bpy.context.scene.BIMStylesProperties.style_type
|
||||
@@ -74,70 +110,272 @@ class Style(blenderbim.core.tool.Style):
|
||||
return
|
||||
|
||||
@classmethod
|
||||
def get_surface_rendering_attributes(cls, obj):
|
||||
def get_style_elements(cls, blender_material):
|
||||
if not blender_material.BIMMaterialProperties.ifc_style_id:
|
||||
return {}
|
||||
style = tool.Ifc.get().by_id(blender_material.BIMMaterialProperties.ifc_style_id)
|
||||
style_elements = {}
|
||||
for style in style.Styles:
|
||||
style_elements[style.is_a()] = style
|
||||
return style_elements
|
||||
|
||||
@classmethod
|
||||
def get_surface_style_from_props(cls, blender_material):
|
||||
"""convert blender style props to ifc props"""
|
||||
surface_style_data = dict()
|
||||
props = blender_material.BIMStyleProperties
|
||||
for prop_blender, prop_ifc in STYLE_PROPS_MAP.items():
|
||||
surface_style_data[prop_ifc] = getattr(props, prop_blender)
|
||||
if surface_style_data["ReflectanceMethod"] == "PHYSICAL" and tool.Ifc.get_schema() != "IFC4X3":
|
||||
surface_style_data["ReflectanceMethod"] = "NOTDEFINED"
|
||||
surface_style_data["DiffuseColour"] = ("IfcColourRgb", surface_style_data["DiffuseColour"])
|
||||
return surface_style_data
|
||||
|
||||
@classmethod
|
||||
def get_texture_style_from_props(cls, blender_material):
|
||||
props = blender_material.BIMStyleProperties
|
||||
|
||||
textures = []
|
||||
texture_maps = TEXTURE_MAPS_BY_METHODS[props.reflectance_method]
|
||||
for prop_mode in texture_maps:
|
||||
prop_name = STYLE_TEXTURE_PROPS_MAP[prop_mode]
|
||||
path = getattr(props, prop_name)
|
||||
if not path:
|
||||
continue
|
||||
if not os.path.abspath(path) and tool.Ifc.get_path():
|
||||
path = os.path.join(os.path.dirname(tool.Ifc.get_path()), path)
|
||||
|
||||
texture_data = {
|
||||
"Mode": prop_mode,
|
||||
"type": "IfcImageTexture",
|
||||
"URLReference": path,
|
||||
}
|
||||
textures.append(texture_data)
|
||||
|
||||
return textures
|
||||
|
||||
@classmethod
|
||||
def set_surface_style_props(cls, blender_material):
|
||||
"""set blender style props based on current surface material"""
|
||||
props = blender_material.BIMStyleProperties
|
||||
# make sure won't be updating while we changing it
|
||||
prev_update_graph_value = props.update_graph
|
||||
props["update_graph"] = False
|
||||
|
||||
style_elements = tool.Style.get_style_elements(blender_material)
|
||||
surface_style = style_elements.get("IfcSurfaceStyleRendering", None)
|
||||
texture_style = style_elements.get("IfcSurfaceStyleWithTextures", None)
|
||||
|
||||
# in case we have just IfcSurfaceStyleShading
|
||||
if not surface_style:
|
||||
return
|
||||
|
||||
style_data = tool.Loader.surface_style_to_dict(surface_style)
|
||||
if style_data["ReflectanceMethod"] == "NOTDEFINED":
|
||||
style_data["ReflectanceMethod"] = "PHYSICAL"
|
||||
style_data["DiffuseColour"] = style_data["DiffuseColour"][1]
|
||||
|
||||
for prop_blender, prop_ifc in STYLE_PROPS_MAP.items():
|
||||
prop_value = style_data[prop_ifc]
|
||||
if prop_value is None:
|
||||
prop_value = tool.Blender.get_blender_prop_default_value(props, prop_blender)
|
||||
setattr(props, prop_blender, prop_value)
|
||||
|
||||
texture_maps = TEXTURE_MAPS_BY_METHODS[style_data["ReflectanceMethod"]]
|
||||
unused_texture_maps = list(STYLE_TEXTURE_PROPS_MAP.keys())
|
||||
|
||||
if texture_style:
|
||||
for texture in texture_style.Textures:
|
||||
if texture.Mode not in texture_maps:
|
||||
print(f"WARNING. Unsupported texture mode: {texture.Mode}. Supported maps: {texture_maps}")
|
||||
continue
|
||||
prop_blender = STYLE_TEXTURE_PROPS_MAP.get(texture.Mode, None)
|
||||
setattr(props, prop_blender, texture.URLReference)
|
||||
unused_texture_maps.remove(texture.Mode)
|
||||
|
||||
# clear empty texture fields
|
||||
for texture_mode in unused_texture_maps:
|
||||
prop_blender = STYLE_TEXTURE_PROPS_MAP[texture_mode]
|
||||
setattr(props, prop_blender, "")
|
||||
|
||||
props["update_graph"] = prev_update_graph_value
|
||||
|
||||
@classmethod
|
||||
def get_surface_rendering_attributes(cls, obj, verbose=True):
|
||||
report = (lambda *x: print(*x)) if verbose else (lambda *x: None)
|
||||
|
||||
def color_to_ifc_format(color):
|
||||
return {
|
||||
"Name": None,
|
||||
"Red": color[0],
|
||||
"Green": color[1],
|
||||
"Blue": color[2],
|
||||
}
|
||||
|
||||
def get_input_node(node, input_name=None, of_type=None, input_index=None):
|
||||
input_pin = node.inputs[input_name] if input_index is None else node.inputs[input_index]
|
||||
if of_type:
|
||||
return next((l.from_node for l in input_pin.links if l.from_node.type == of_type), None)
|
||||
return next((l.from_node for l in input_pin.links), None)
|
||||
|
||||
props = obj.BIMStyleProperties
|
||||
transparency = 1 - obj.diffuse_color[3]
|
||||
diffuse_color = obj.diffuse_color
|
||||
|
||||
viewport_color = color_to_ifc_format(obj.diffuse_color)
|
||||
attributes = {
|
||||
"SurfaceColour": {
|
||||
"Name": None,
|
||||
"Red": obj.diffuse_color[0],
|
||||
"Green": obj.diffuse_color[1],
|
||||
"Blue": obj.diffuse_color[2],
|
||||
},
|
||||
"SurfaceColour": viewport_color,
|
||||
"Transparency": transparency,
|
||||
}
|
||||
GREEN = "\033[32m"
|
||||
BLUE = "\033[1;34m"
|
||||
R = "\033[0m" # RESET symbol
|
||||
|
||||
bsdfs = {n.type: n for n in obj.node_tree.nodes}
|
||||
if "BSDF_GLOSSY" in bsdfs:
|
||||
attributes["ReflectanceMethod"] = "METAL"
|
||||
bsdf = bsdfs["BSDF_GLOSSY"]
|
||||
report("--------------------")
|
||||
report("Verbose method of getting surface rendering attributes enabled.")
|
||||
report("If some attribute is not mentioned below, then it won't be saved to IFC.")
|
||||
report("--------------------")
|
||||
report(f"{GREEN}Viewport color{R} saved as {GREEN}SurfaceColour{R}")
|
||||
|
||||
# TODO: make sure bsdf is connected to the output?
|
||||
bsdfs = {n.type: n for n in obj.node_tree.nodes if n.outputs and n.outputs[0].is_linked}
|
||||
if "BSDF_PRINCIPLED" not in bsdfs:
|
||||
report(f"{GREEN}Viewport color alpha{R} saved as {GREEN}Transparency{R}")
|
||||
|
||||
# TODO: should escape referring to pins by their name to support different languages
|
||||
material_output = tool.Blender.get_material_node(obj, "OUTPUT_MATERIAL", {"is_active_output": True})
|
||||
surface_output = get_input_node(material_output, "Surface")
|
||||
|
||||
if surface_output and surface_output.type == "MIX_SHADER":
|
||||
mix_shader = surface_output
|
||||
if (
|
||||
get_input_node(mix_shader, "Fac", "LIGHT_PATH")
|
||||
and get_input_node(mix_shader, input_index=1, of_type="BSDF_TRANSPARENT")
|
||||
and (second_input_node := get_input_node(mix_shader, input_index=2))
|
||||
and second_input_node.type in ("RGB", "TEX_IMAGE")
|
||||
):
|
||||
report(
|
||||
f"Because of {BLUE}MIX_SHADER + LIGHT_PATH + BSDF_TRANSPARENT + RGB/TEX{R} node setup reflectance method identified as {BLUE}FLAT{R}"
|
||||
)
|
||||
attributes["ReflectanceMethod"] = "FLAT"
|
||||
attributes["SpecularHighlight"] = None
|
||||
|
||||
if second_input_node.type == "RGB":
|
||||
report(f"RGB {GREEN}Color{R} saved as {GREEN}DiffuseColour{R}")
|
||||
diffuse_color = second_input_node.outputs[0].default_value
|
||||
elif second_input_node.type == "TEX_IMAGE":
|
||||
report(f"{GREEN}BBIM Panel Diffuse Color{R} saved as {GREEN}DiffuseColour{R}")
|
||||
diffuse_color = props.diffuse_color
|
||||
|
||||
elif surface_output and (
|
||||
(surface_output.type == "BSDF_PRINCIPLED" and (bsdf := surface_output))
|
||||
or (
|
||||
surface_output.type == "ADD_SHADER"
|
||||
and (bsdf := get_input_node(surface_output, input_index=1, of_type="BSDF_PRINCIPLED"))
|
||||
)
|
||||
):
|
||||
report(f"Because of {BLUE}BSDF_PRINCIPLED{R} node reflectance method identified as {BLUE}PHYSICAL{R}")
|
||||
attributes["ReflectanceMethod"] = "NOTDEFINED" if tool.Ifc.get_schema() != "IFC4X3" else "PHYSICAL"
|
||||
|
||||
report(f"BSDF {GREEN}Base Color{R} saved as {GREEN}DiffuseColour{R}")
|
||||
diffuse_color = bsdf.inputs["Base Color"].default_value
|
||||
|
||||
report(f"BSDF {GREEN}Metallic{R} saved as {GREEN}SpecularColour{R}")
|
||||
attributes["SpecularColour"] = round(bsdf.inputs["Metallic"].default_value, 3)
|
||||
|
||||
report(f"BSDF {GREEN}Roughness{R} saved as {GREEN}IfcSpecularRoughness{R}")
|
||||
attributes["SpecularHighlight"] = {"IfcSpecularRoughness": round(bsdf.inputs["Roughness"].default_value, 3)}
|
||||
|
||||
report(f"BSDF {GREEN}Alpha{R} saved as {GREEN}Transparency{R}")
|
||||
attributes["Transparency"] = 1 - bsdf.inputs["Alpha"].default_value
|
||||
|
||||
elif "BSDF_GLOSSY" in bsdfs:
|
||||
attributes["ReflectanceMethod"] = "METAL"
|
||||
report(f"Because of {BLUE}BSDF_GLOSSY{R} node reflectance method identified as {BLUE}METAL{R}")
|
||||
bsdf = bsdfs["BSDF_GLOSSY"]
|
||||
|
||||
report(f"BSDF {GREEN}Roughness{R} saved as {GREEN}IfcSpecularRoughness{R}")
|
||||
attributes["SpecularHighlight"] = {"IfcSpecularRoughness": round(bsdf.inputs["Roughness"].default_value, 3)}
|
||||
|
||||
report(f"BSDF {GREEN}Color{R} saved as {GREEN}DiffuseColour{R}")
|
||||
diffuse_color = bsdf.inputs["Color"].default_value
|
||||
|
||||
elif "BSDF_DIFFUSE" in bsdfs:
|
||||
report(f"Because of {BLUE}BSDF_DIFFUSE{R} node reflectance method identified as {BLUE}MATT{R}")
|
||||
attributes["ReflectanceMethod"] = "MATT"
|
||||
bsdf = bsdfs["BSDF_DIFFUSE"]
|
||||
|
||||
report(f"BSDF {GREEN}Roughness{R} saved as {GREEN}IfcSpecularRoughness{R}")
|
||||
attributes["SpecularHighlight"] = {"IfcSpecularRoughness": round(bsdf.inputs["Roughness"].default_value, 3)}
|
||||
|
||||
report(f"BSDF {GREEN}Color{R} saved as {GREEN}DiffuseColour{R}")
|
||||
diffuse_color = bsdf.inputs["Color"].default_value
|
||||
|
||||
elif "BSDF_GLASS" in bsdfs:
|
||||
report(f"Because of {BLUE}BSDF_GLASS{R} node reflectance method identified as {BLUE}GLASS{R}")
|
||||
attributes["ReflectanceMethod"] = "GLASS"
|
||||
bsdf = bsdfs["BSDF_GLASS"]
|
||||
|
||||
report(f"BSDF {GREEN}Roughness{R} saved as {GREEN}IfcSpecularRoughness{R}")
|
||||
attributes["SpecularHighlight"] = {"IfcSpecularRoughness": round(bsdf.inputs["Roughness"].default_value, 3)}
|
||||
|
||||
report(f"BSDF {GREEN}Color{R} saved as {GREEN}DiffuseColour{R}")
|
||||
diffuse_color = bsdf.inputs["Color"].default_value
|
||||
|
||||
# TODO: remove?
|
||||
elif "EMISSION" in bsdfs:
|
||||
report(f"Because of {BLUE}EMISSION{R} node reflectance method identified as {BLUE}FLAT{R}")
|
||||
attributes["ReflectanceMethod"] = "FLAT"
|
||||
bsdf = bsdfs["EMISSION"]
|
||||
|
||||
attributes["SpecularHighlight"] = None
|
||||
report(f"BSDF {GREEN}Color{R} saved as {GREEN}DiffuseColour{R}")
|
||||
|
||||
diffuse_color = bsdf.inputs["Color"].default_value
|
||||
|
||||
# TODO: remove?
|
||||
elif "BSDF_PRINCIPLED" in bsdfs:
|
||||
attributes["ReflectanceMethod"] = "NOTDEFINED"
|
||||
report(f"Because of {BLUE}BSDF_PRINCIPLED{R} node reflectance method identified as {BLUE}PHYSICAL{R}")
|
||||
attributes["ReflectanceMethod"] = "NOTDEFINED" if tool.Ifc.get_schema() != "IFC4X3" else "PHYSICAL"
|
||||
bsdf = bsdfs["BSDF_PRINCIPLED"]
|
||||
attributes["SpecularColour"] = round(bsdf.inputs["Metallic"].default_value, 3)
|
||||
attributes["SpecularHighlight"] = {"IfcSpecularRoughness": round(bsdf.inputs["Roughness"].default_value, 3)}
|
||||
|
||||
report(f"BSDF {GREEN}Base Color{R} saved as {GREEN}DiffuseColour{R}")
|
||||
diffuse_color = bsdf.inputs["Base Color"].default_value
|
||||
|
||||
report(f"BSDF {GREEN}Metallic{R} saved as {GREEN}SpecularColour{R}")
|
||||
attributes["SpecularColour"] = round(bsdf.inputs["Metallic"].default_value, 3)
|
||||
|
||||
report(f"BSDF {GREEN}Roughness{R} saved as {GREEN}IfcSpecularRoughness{R}")
|
||||
attributes["SpecularHighlight"] = {"IfcSpecularRoughness": round(bsdf.inputs["Roughness"].default_value, 3)}
|
||||
|
||||
report(f"BSDF {GREEN}Alpha{R} saved as {GREEN}Transparency{R}")
|
||||
attributes["Transparency"] = 1 - bsdf.inputs["Alpha"].default_value
|
||||
|
||||
else:
|
||||
report(f"No supported bsdfs found - reflectance method identified as {BLUE}NOTDEFINED{R}")
|
||||
attributes["ReflectanceMethod"] = "NOTDEFINED"
|
||||
|
||||
attributes["SpecularHighlight"] = None
|
||||
attributes["DiffuseColour"] = attributes["SurfaceColour"]
|
||||
report(f"{GREEN}Viewport color{R} saved as {GREEN}DiffuseColour{R}")
|
||||
attributes["DiffuseColour"] = viewport_color
|
||||
return attributes
|
||||
|
||||
attributes["DiffuseColour"] = {
|
||||
"Name": None,
|
||||
"Red": diffuse_color[0],
|
||||
"Green": diffuse_color[1],
|
||||
"Blue": diffuse_color[2],
|
||||
}
|
||||
|
||||
attributes["DiffuseColour"] = color_to_ifc_format(diffuse_color)
|
||||
return attributes
|
||||
|
||||
@classmethod
|
||||
def get_surface_rendering_style(cls, obj):
|
||||
if obj.BIMMaterialProperties.ifc_style_id:
|
||||
style = tool.Ifc.get().by_id(obj.BIMMaterialProperties.ifc_style_id)
|
||||
items = [s for s in style.Styles if s.is_a("IfcSurfaceStyleRendering")]
|
||||
if items:
|
||||
return items[0]
|
||||
style_elements = cls.get_style_elements(obj)
|
||||
return style_elements.get("IfcSurfaceStyleRendering", None)
|
||||
|
||||
@classmethod
|
||||
def get_texture_style(cls, obj):
|
||||
style_elements = cls.get_style_elements(obj)
|
||||
return style_elements.get("IfcSurfaceStyleWithTextures", None)
|
||||
|
||||
@classmethod
|
||||
def get_external_style(cls, obj):
|
||||
style_elements = cls.get_style_elements(obj)
|
||||
return style_elements.get("IfcExternallyDefinedSurfaceStyle", None)
|
||||
|
||||
@classmethod
|
||||
def get_surface_shading_attributes(cls, obj):
|
||||
@@ -197,8 +435,20 @@ class Style(blenderbim.core.tool.Style):
|
||||
|
||||
@classmethod
|
||||
def import_surface_attributes(cls, style, obj):
|
||||
obj.BIMStyleProperties.attributes.clear()
|
||||
blenderbim.bim.helper.import_attributes2(style, obj.BIMStyleProperties.attributes)
|
||||
attributes = obj.BIMStyleProperties.attributes
|
||||
attributes.clear()
|
||||
blenderbim.bim.helper.import_attributes2(style, attributes)
|
||||
|
||||
@classmethod
|
||||
def import_external_style_attributes(cls, style, obj):
|
||||
attributes = obj.BIMStyleProperties.external_style_attributes
|
||||
attributes.clear()
|
||||
blenderbim.bim.helper.import_attributes2(style, attributes)
|
||||
|
||||
@classmethod
|
||||
def has_blender_external_style(cls, style_elements):
|
||||
external_style = style_elements.get("IfcExternallyDefinedSurfaceStyle", None)
|
||||
return bool(external_style and external_style.Location.endswith(".blend"))
|
||||
|
||||
@classmethod
|
||||
def is_editing_styles(cls):
|
||||
@@ -214,3 +464,7 @@ class Style(blenderbim.core.tool.Style):
|
||||
obj = tool.Ifc.get_object(element)
|
||||
if obj:
|
||||
obj.select_set(True)
|
||||
|
||||
@classmethod
|
||||
def change_current_style_type(cls, blender_material, style_type):
|
||||
blender_material.BIMStyleProperties.active_style_type = style_type
|
||||
|
||||
@@ -129,16 +129,10 @@ class System(blenderbim.core.tool.System):
|
||||
ifc_representation_class=ifc_representation_class,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def select_elements(cls, elements):
|
||||
for element in elements:
|
||||
obj = tool.Ifc.get_object(element)
|
||||
if obj:
|
||||
obj.select_set(True)
|
||||
|
||||
@classmethod
|
||||
def select_system_products(cls, system):
|
||||
cls.select_elements(ifcopenshell.util.system.get_system_elements(system))
|
||||
tool.Spatial.select_products(ifcopenshell.util.system.get_system_elements(system))
|
||||
|
||||
@classmethod
|
||||
def set_active_system(cls, system):
|
||||
|
||||
Binary file not shown.
@@ -72,34 +72,39 @@ restart Blender to see changes).
|
||||
|
||||
For Linux or Mac:
|
||||
|
||||
::
|
||||
:: code-block:: console
|
||||
|
||||
$ git clone https://github.com/IfcOpenShell/IfcOpenShell.git
|
||||
$ cd IfcOpenShell
|
||||
|
||||
# path to BlenderBIM addon
|
||||
# default path on Mac: "/Users/$USER/Library/Application Support/Blender/X.X/scripts/addons/blenderbim"
|
||||
# default path on Linux: "$HOME/.config/blender/X.X/"
|
||||
$ BLENDER_ADDON_PATH="/path/to/blender/X.XX/scripts/addons/blenderbim"
|
||||
|
||||
# Remove the Blender add-on Python code
|
||||
$ rm -r /path/to/blender/X.XX/scripts/addons/blenderbim/core/
|
||||
$ rm -r /path/to/blender/X.XX/scripts/addons/blenderbim/tool/
|
||||
$ rm -r /path/to/blender/X.XX/scripts/addons/blenderbim/bim/
|
||||
$ rm -r $BLENDER_ADDON_PATH/core/
|
||||
$ rm -r $BLENDER_ADDON_PATH/tool/
|
||||
$ rm -r $BLENDER_ADDON_PATH/bim/
|
||||
|
||||
# Replace them with links to the Git repository
|
||||
$ ln -s src/blenderbim/blenderbim/core /path/to/blender/X.XX/scripts/addons/blenderbim/core
|
||||
$ ln -s src/blenderbim/blenderbim/tool /path/to/blender/X.XX/scripts/addons/blenderbim/tool
|
||||
$ ln -s src/blenderbim/blenderbim/bim /path/to/blender/X.XX/scripts/addons/blenderbim/bim
|
||||
$ ln -s $PWD/src/blenderbim/blenderbim/core $BLENDER_ADDON_PATH/core
|
||||
$ ln -s $PWD/src/blenderbim/blenderbim/tool $BLENDER_ADDON_PATH/tool
|
||||
$ ln -s $PWD/src/blenderbim/blenderbim/bim $BLENDER_ADDON_PATH/bim
|
||||
|
||||
# Remove the IfcOpenShell dependency Python code
|
||||
$ rm -r /path/to/blender/X.XX/scripts/addons/blenderbim/libs/site/packages/ifcopenshell/api
|
||||
$ rm -r /path/to/blender/X.XX/scripts/addons/blenderbim/libs/site/packages/ifcopenshell/util
|
||||
$ rm -r $BLENDER_ADDON_PATH/libs/site/packages/ifcopenshell/api
|
||||
$ rm -r $BLENDER_ADDON_PATH/libs/site/packages/ifcopenshell/util
|
||||
|
||||
# Replace them with links to the Git repository
|
||||
$ ln -s src/ifcopenshell-python/ifcopenshell/api /path/to/blender/X.XX/scripts/addons/blenderbim/libs/site/packages/ifcopenshell/api
|
||||
$ ln -s src/ifcopenshell-python/ifcopenshell/util /path/to/blender/X.XX/scripts/addons/blenderbim/libs/site/packages/ifcopenshell/util
|
||||
$ ln -s $PWD/src/ifcopenshell-python/ifcopenshell/api $BLENDER_ADDON_PATH/libs/site/packages/ifcopenshell/api
|
||||
$ ln -s $PWD/src/ifcopenshell-python/ifcopenshell/util $BLENDER_ADDON_PATH/libs/site/packages/ifcopenshell/util
|
||||
|
||||
# Manually download some third party dependencies
|
||||
$ cd /path/to/blender/X.XX/scripts/addons/blenderbim/bim/data/gantt
|
||||
$ cd $BLENDER_ADDON_PATH/bim/data/gantt
|
||||
$ wget https://raw.githubusercontent.com/jsGanttImproved/jsgantt-improved/master/dist/jsgantt.js
|
||||
$ wget https://raw.githubusercontent.com/jsGanttImproved/jsgantt-improved/master/dist/jsgantt.css
|
||||
$ cd /path/to/blender/X.XX/scripts/addons/blenderbim/bim/schema
|
||||
$ cd $BLENDER_ADDON_PATH/bim/schema
|
||||
$ wget https://github.com/BrickSchema/Brick/releases/download/nightly/Brick.ttl
|
||||
|
||||
Or, if you're on Windows, you can use the batch script below.
|
||||
|
||||
@@ -33,6 +33,12 @@ Any Blender object that you want to be part of IFC project must be converted
|
||||
into a IFC object by assigning a category. This category is known as the **IFC
|
||||
Class**.
|
||||
|
||||
.. seealso::
|
||||
|
||||
Use the `IFC Class search tool
|
||||
<https://blenderbim.org/search-ifc-class.html>`__ to help choose an **IFC
|
||||
Class**!
|
||||
|
||||
Select only the default Blender Cube (selected objects are highlighted in
|
||||
orange, careful not to select anything else!), switch to the **Object
|
||||
Properties** tab, and find the **IFC Class** panel. Let's pretend our Cube is a
|
||||
|
||||
@@ -137,13 +137,21 @@ FAQ
|
||||
**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"**
|
||||
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. **Some other error prevents me from installing or doing basic functions with
|
||||
3. **I get an error saying "ModuleNotFoundError: No module named 'numpy'"**"
|
||||
|
||||
If you have installed Blender from another source instead of from
|
||||
`Blender.org <https://www.blender.org/download/>`__, 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
|
||||
the add-on. Is it specific to my environment?**
|
||||
|
||||
Sometimes it is helpful to try installing and using the BlenderBIM Add-on on
|
||||
@@ -157,3 +165,15 @@ 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.
|
||||
|
||||
5. **I get an error similar to RuntimeError: Instance #1234 not found**
|
||||
|
||||
Blender saves and loads projects to a ``.blend`` file. However. the
|
||||
BlenderBIM Add-on works with native IFC, and this means instead of saving
|
||||
and loading ``.blend`` files, you should instead save and load the ``.ifc``
|
||||
project.
|
||||
|
||||
If you have opened a ``.blend`` file, there is a risk that the contents of
|
||||
the ``.blend`` session do not correlate to the contents of the ``.ifc``,
|
||||
which can cause this error. Unless you are an advanced user, only save and
|
||||
load ``.ifc`` files.
|
||||
|
||||
@@ -226,7 +226,7 @@ Scenario: Add classification reference - cost
|
||||
And I press "bim.add_cost_schedule"
|
||||
And the variable "cost_schedule" is "{ifc}.by_type('IfcCostSchedule')[0].id()"
|
||||
And I press "bim.enable_editing_cost_items(cost_schedule={cost_schedule})"
|
||||
And I press "bim.add_summary_cost_item(cost_schedule={cost_schedule})"
|
||||
And I press "bim.add_summary_cost_item()"
|
||||
And I press "bim.change_classification_level(parent_id={classification})"
|
||||
And the variable "reference" is "{classification_ifc}.by_type('IfcClassificationReference')[0].id()"
|
||||
When I press "bim.add_classification_reference(reference={reference}, obj='', obj_type='Cost')"
|
||||
@@ -241,7 +241,7 @@ Scenario: Remove classification reference - cost
|
||||
And I press "bim.add_cost_schedule"
|
||||
And the variable "cost_schedule" is "{ifc}.by_type('IfcCostSchedule')[0].id()"
|
||||
And I press "bim.enable_editing_cost_items(cost_schedule={cost_schedule})"
|
||||
And I press "bim.add_summary_cost_item(cost_schedule={cost_schedule})"
|
||||
And I press "bim.add_summary_cost_item()"
|
||||
And I press "bim.change_classification_level(parent_id={classification})"
|
||||
And the variable "reference" is "{classification_ifc}.by_type('IfcClassificationReference')[0].id()"
|
||||
And I press "bim.add_classification_reference(reference={reference}, obj='', obj_type='Cost')"
|
||||
|
||||
@@ -57,9 +57,9 @@ Scenario: Remove drawing
|
||||
And the variable "wall1" is "IfcStore.get_file().by_type('IfcWall')[-1].id()"
|
||||
And I press "bim.add_drawing"
|
||||
And the variable "drawing" is "IfcStore.get_file().by_type('IfcAnnotation')[0].id()"
|
||||
And the collection "IfcGroup/PLAN_VIEW" exists
|
||||
And the collection "IfcAnnotation/PLAN_VIEW" exists
|
||||
When I press "bim.remove_drawing(drawing={drawing})"
|
||||
Then the collection "IfcGroup/PLAN_VIEW" does not exist
|
||||
Then the collection "IfcAnnotation/PLAN_VIEW" does not exist
|
||||
|
||||
Scenario: Remove drawing - via object deletion
|
||||
Given an empty IFC project
|
||||
@@ -70,9 +70,10 @@ Scenario: Remove drawing - via object deletion
|
||||
And the variable "wall1" is "IfcStore.get_file().by_type('IfcWall')[-1].id()"
|
||||
And I press "bim.add_drawing"
|
||||
And the variable "drawing" is "IfcStore.get_file().by_type('IfcAnnotation')[0].id()"
|
||||
And the collection "IfcAnnotation/PLAN_VIEW" exists
|
||||
And the object "IfcAnnotation/PLAN_VIEW" is selected
|
||||
When I press "bim.override_object_delete"
|
||||
Then the collection "IfcGroup/PLAN_VIEW" does not exist
|
||||
Then the collection "IfcAnnotation/PLAN_VIEW" does not exist
|
||||
|
||||
Scenario: Remove drawing - deleting active drawing
|
||||
Given an empty IFC project
|
||||
@@ -83,9 +84,10 @@ Scenario: Remove drawing - deleting active drawing
|
||||
And the variable "wall1" is "IfcStore.get_file().by_type('IfcWall')[-1].id()"
|
||||
And I press "bim.add_drawing"
|
||||
And the variable "drawing" is "IfcStore.get_file().by_type('IfcAnnotation')[0].id()"
|
||||
And the collection "IfcAnnotation/PLAN_VIEW" exists
|
||||
And I set "scene.DocProperties.active_drawing_index" to "0"
|
||||
And I press "bim.activate_drawing(drawing={drawing})"
|
||||
And the object "IfcAnnotation/PLAN_VIEW" is selected
|
||||
When I press "bim.override_object_delete"
|
||||
Then the collection "IfcGroup/PLAN_VIEW" does not exist
|
||||
Then the collection "IfcAnnotation/PLAN_VIEW" does not exist
|
||||
|
||||
|
||||
@@ -156,26 +156,31 @@ Scenario: Unassign material - material layer set
|
||||
When I press "bim.unassign_material"
|
||||
Then nothing happens
|
||||
|
||||
Scenario: Unassign material - material layer set usages will not be removed
|
||||
Scenario: Unassign material - removing inherited material
|
||||
Given an empty IFC project
|
||||
And I add a cube
|
||||
And the object "Cube" is selected
|
||||
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
|
||||
And I press "bim.assign_class"
|
||||
|
||||
And I add an empty
|
||||
And the object "Empty" is selected
|
||||
And I set "scene.BIMRootProperties.ifc_product" to "IfcElementType"
|
||||
And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType"
|
||||
And I press "bim.assign_class"
|
||||
|
||||
And I press "bim.add_material(obj='')"
|
||||
And I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterialLayerSet"
|
||||
And I press "bim.assign_material"
|
||||
|
||||
And the object "IfcWall/Cube" is selected
|
||||
When the variable "type" is "{ifc}.by_type('IfcWallType')[0].id()"
|
||||
And I press "bim.assign_type(relating_type={type}, related_object='IfcWall/Cube')"
|
||||
|
||||
Then the object "IfcWall/Cube" has a "100" thick layered material containing the material "Default"
|
||||
|
||||
When I press "bim.unassign_material"
|
||||
Then the object "IfcWall/Cube" has a "100" thick layered material containing the material "Default"
|
||||
Then the object "IfcWall/Cube" has no IFC materials
|
||||
|
||||
Scenario: Enable editing assigned material - material layer set
|
||||
Given an empty IFC project
|
||||
@@ -435,17 +440,20 @@ Scenario: Remove material set layer
|
||||
And the object "Cube" is selected
|
||||
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
|
||||
And I press "bim.assign_class"
|
||||
|
||||
And I add an empty
|
||||
And the object "Empty" is selected
|
||||
And I set "scene.BIMRootProperties.ifc_product" to "IfcElementType"
|
||||
And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType"
|
||||
And I press "bim.assign_class"
|
||||
|
||||
And I press "bim.add_material(obj='')"
|
||||
And I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterialLayerSet"
|
||||
And I press "bim.assign_material"
|
||||
|
||||
And I press "bim.enable_editing_assigned_material"
|
||||
And the variable "material_set" is "{ifc}.by_type('IfcMaterialLayerSet')[0].id()"
|
||||
And I press "bim.remove_layer(layer={ifc}.by_id({material_set}).MaterialLayers[0].id())"
|
||||
|
||||
And I press "bim.add_layer(layer_set={material_set})"
|
||||
When I press "bim.remove_layer(layer={ifc}.by_id({material_set}).MaterialLayers[0].id())"
|
||||
Then nothing happens
|
||||
And I press "bim.remove_layer(layer={ifc}.by_id({material_set}).MaterialLayers[0].id())"
|
||||
Then I press "bim.remove_layer(layer={ifc}.by_id({material_set}).MaterialLayers[0].id())" and expect error "Error: At least one layer must exist"
|
||||
|
||||
@@ -393,7 +393,7 @@ Scenario: Export IFC - with basic contents and saving as a relative path
|
||||
Given an empty Blender session
|
||||
And I press "bim.load_project(filepath='{cwd}/test/files/basic.ifc')"
|
||||
When I press "wm.save_mainfile(filepath='{cwd}/test/files/temp/export.blend')"
|
||||
And I press "export_ifc.bim(filepath='{cwd}/test/files/temp/export.ifc', use_relative_path=True)"
|
||||
And I press "export_ifc.bim(filepath='{cwd}/test/files/temp/export.ifc', use_relative_path=True, save_as_invoked=True)"
|
||||
Then "scene.BIMProperties.ifc_file" is "export.ifc"
|
||||
|
||||
Scenario: Export IFC - with deleted objects synchronised
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user