mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 09:21:46 +00:00
Compare commits
279 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 628fcb2999 | |||
| 22f830b053 | |||
| 579c72fc69 | |||
| 642a0f7084 | |||
| 7cd5aab1c5 | |||
| 4b15535c9c | |||
| b978da24d9 | |||
| 7132c4e9bf | |||
| 5aa6123806 | |||
| e48299cb3f | |||
| c65088a7a7 | |||
| 4f53d8c5f1 | |||
| 424b2589d9 | |||
| 05f6ed1084 | |||
| 9ec80cee0d | |||
| a5daf4177a | |||
| 1b1eccb0c1 | |||
| ece9e5c834 | |||
| 6005ff641b | |||
| fa603244b9 | |||
| 6d6010be3b | |||
| 3691052f06 | |||
| e38eafd034 | |||
| 976eeaef20 | |||
| 09da42d649 | |||
| 97b23a9ac6 | |||
| 5e8a57f089 | |||
| 7f34d18bbf | |||
| 97cc19ed43 | |||
| d49faf7412 | |||
| fcaac6bd98 | |||
| c35cdcfcad | |||
| 205da63d8e | |||
| 3f529ae21b | |||
| 89f7105e9c | |||
| e5dd06c461 | |||
| ec7af4e719 | |||
| a755ec99f9 | |||
| 812389bce2 | |||
| b67b5e8d9f | |||
| e2a06e10b6 | |||
| 7fa26b2d60 | |||
| cdd332d1ee | |||
| 22f21a3f0b | |||
| ee27bc3b74 | |||
| 8307bf5877 | |||
| 09e473a0dc | |||
| abeefeeee7 | |||
| d949180461 | |||
| 708bd3207e | |||
| 68d9d04e5b | |||
| 7c7bd3dc53 | |||
| 24d2b59a2e | |||
| ac2684af8d | |||
| 8291a0ca6a | |||
| b72b23741e | |||
| 858692f3f5 | |||
| c74bfa24d2 | |||
| 66c29f0da1 | |||
| f20fd3357f | |||
| e4eb76d4e0 | |||
| 22c95828ce | |||
| e87ca960e8 | |||
| 6881e7d220 | |||
| 28b54c869a | |||
| 9596117953 | |||
| 8dbeb928e3 | |||
| 4fdb78aebc | |||
| 01724913e1 | |||
| 44dd07547e | |||
| cc157239a2 | |||
| 0576914e07 | |||
| a064173ffe | |||
| 8e502c9bb7 | |||
| 4d23706145 | |||
| 6e8de292c6 | |||
| d209cc9ff4 | |||
| ddfaf3308b | |||
| 301ddab1ee | |||
| 1160d20d33 | |||
| 28099ddc8f | |||
| c1bca22638 | |||
| 3d5d868812 | |||
| b015647543 | |||
| c54f2131af | |||
| 2a832202fa | |||
| a3d970e014 | |||
| 24912bb827 | |||
| 1cd0bc9e7d | |||
| 8880ab0fe9 | |||
| 1c052d1417 | |||
| 20cb6ab007 | |||
| e854447714 | |||
| 7f394a5807 | |||
| c80b437682 | |||
| b793ab63cc | |||
| 8c28f52e6a | |||
| 9d71b7483f | |||
| e6e71adba7 | |||
| 7ab8a5921b | |||
| d68d4d54a9 | |||
| bce0f0d8d4 | |||
| 50648fd350 | |||
| b7e26bacea | |||
| a2fdd93e6b | |||
| aef00a4dfc | |||
| efae8ebdcd | |||
| 61403fcf42 | |||
| badee8fdc5 | |||
| 36e779e32f | |||
| c50ac3db4f | |||
| b17c261344 | |||
| 956aae37a8 | |||
| 48d6cc8a28 | |||
| cf9d3026f4 | |||
| e2f0b1ed5c | |||
| b833cffbd3 | |||
| 7a7c89e475 | |||
| 01b0164e0d | |||
| 13f26d9b70 | |||
| 8a88b0b3a1 | |||
| a15e293db5 | |||
| 9509f07da8 | |||
| 37b15de3e4 | |||
| 66b4509b06 | |||
| 41dcb83bdb | |||
| 0bda41710c | |||
| cbd85fafda | |||
| dc7ebab0d1 | |||
| 0c2a3ff400 | |||
| 6af6fda799 | |||
| 2c9e443425 | |||
| 2bede1cdf0 | |||
| 4f3397a04e | |||
| 9b8adad8c8 | |||
| c48bccf727 | |||
| 5d41336342 | |||
| b8628cc9df | |||
| 519cb86163 | |||
| 0008d8bd2a | |||
| bc7946091a | |||
| ac2db7b4c9 | |||
| 0b57d5fc5f | |||
| 82af25d09a | |||
| 4a82525bdb | |||
| 002112dd50 | |||
| 844a9842cf | |||
| 3a098e55c4 | |||
| e186b81f0c | |||
| 527d16c5f0 | |||
| 8c1b33e1d5 | |||
| e46f18f686 | |||
| 931fd9cc9f | |||
| aebc073416 | |||
| 56e00d059b | |||
| 8a6b4aa5fb | |||
| 46bee8aed9 | |||
| eaa2aa05a4 | |||
| abfa6edc5c | |||
| e3a1c29593 | |||
| e6691989dc | |||
| 83845bdd6d | |||
| 0f4c19fd2d | |||
| ce8bdffe57 | |||
| 53e052eb77 | |||
| f21b472cfe | |||
| 324b440f5c | |||
| 37a7049a61 | |||
| 1c1035e251 | |||
| d83924b73e | |||
| a04542dfd1 | |||
| 066616707a | |||
| c1cd228911 | |||
| 515830e52d | |||
| a971860094 | |||
| f872906e0c | |||
| 0597100356 | |||
| d5feaaa1af | |||
| caca209c8f | |||
| 2e45d4a57e | |||
| 0d8c86a0f6 | |||
| 7f0df72fec | |||
| 863a8248af | |||
| bdc991856c | |||
| 1a38b7f5c6 | |||
| 91199308a9 | |||
| 1f7930c5c8 | |||
| 54c7f355ff | |||
| 551f3b0d8d | |||
| 049c796629 | |||
| 412700c8fd | |||
| 96a8c1e8ae | |||
| 602e18a262 | |||
| 9e30e1ad31 | |||
| b6f939f1b1 | |||
| d964f924d7 | |||
| 36b702630d | |||
| ccf70c7aad | |||
| a2ff63f9ec | |||
| a82b0f30b0 | |||
| bc2fd232d0 | |||
| 5d7b6ad998 | |||
| 0e0678b926 | |||
| 1d4dfd0eb3 | |||
| 65179db444 | |||
| 5457ee3a40 | |||
| 4f3ba7ae22 | |||
| a33a1700dc | |||
| 8a48769760 | |||
| 7e55872ca8 | |||
| edbe1d80bf | |||
| bd093303c4 | |||
| c5c7363a93 | |||
| 5014fef129 | |||
| d710a01df5 | |||
| 23c65742e1 | |||
| d8f52afa6c | |||
| 29ce8d5d80 | |||
| 5e5de0bbd8 | |||
| b9b58f5726 | |||
| 5c7271ddc8 | |||
| 2db35556e9 | |||
| 2449656311 | |||
| c66cf0a22b | |||
| e1a1ba62c5 | |||
| 6489f4dc1d | |||
| beab99bb0d | |||
| 8b50f1bcd6 | |||
| 4617834578 | |||
| 426dcbee43 | |||
| 2a12151c07 | |||
| c08958c1bd | |||
| e355a33d48 | |||
| ece9e2fdd1 | |||
| c0d84713bc | |||
| ca9f165b6a | |||
| 8f9c97a663 | |||
| 4ed8310b93 | |||
| 31be899dc9 | |||
| 4463dd8af9 | |||
| 95c0ac901f | |||
| 6d7a6eeda7 | |||
| c520e0eeb5 | |||
| 73817acdff | |||
| 3d7e8d7c6c | |||
| a701840826 | |||
| 5de2ac9042 | |||
| 573e2626ad | |||
| 9de59afd49 | |||
| 74a9671075 | |||
| 799658ff41 | |||
| bd64f42a54 | |||
| 8eab7f155e | |||
| 5351966430 | |||
| 2663c079f4 | |||
| 6747bd9c8f | |||
| 6ece295be4 | |||
| 64a9f85f25 | |||
| d8fa853308 | |||
| ce37c49624 | |||
| 0da3270c7f | |||
| fda6fc6c48 | |||
| 3865f01df9 | |||
| 6bf61d7163 | |||
| b8ad73668f | |||
| b7a3dd4164 | |||
| 566fd443db | |||
| 51b5a507cf | |||
| a44357dad5 | |||
| 8e792ebb50 | |||
| 352321364c | |||
| 2ab32e41b2 | |||
| 3fbd9d7cdf | |||
| 7a7fde0a2a | |||
| 7992bd9902 | |||
| 3182fe9c15 | |||
| e8858aeeb3 | |||
| ec79d61095 | |||
| 266236ee49 |
@@ -76,8 +76,14 @@ src/ifcopenshell-python/test/build
|
||||
# blenderbim libs
|
||||
src/blenderbim/blenderbim/libs
|
||||
|
||||
# blenderbim i18n
|
||||
src/blenderbim/blenderbim/translations.py
|
||||
|
||||
# blenderbim test temp files
|
||||
src/blenderbim/test/files/temp
|
||||
src/blenderbim/test/files/basic.ifc.cache.blend
|
||||
src/blenderbim/test/files/basic.ifc.cache.sqlite
|
||||
|
||||
src/blenderbim/drawings
|
||||
src/blenderbim/layouts
|
||||
|
||||
|
||||
+45
-15
@@ -17,7 +17,7 @@
|
||||
# #
|
||||
################################################################################
|
||||
|
||||
cmake_minimum_required(VERSION 3.1.3)
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
set(CMAKE_CXX_STANDARD 14)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON) # not necessary, but encouraged
|
||||
|
||||
@@ -27,6 +27,9 @@ cmake_policy(SET CMP0048 NEW)
|
||||
cmake_policy(SET CMP0074 NEW)
|
||||
cmake_policy(SET CMP0078 OLD)
|
||||
cmake_policy(SET CMP0086 NEW)
|
||||
if (POLICY CMP0144)
|
||||
cmake_policy(SET CMP0144 NEW) # find_package() uses upper-case <PACKAGENAME>_ROOT variables.
|
||||
endif()
|
||||
|
||||
if(NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE "Release")
|
||||
@@ -186,7 +189,7 @@ if(GLTF_SUPPORT AND BUILD_CONVERT)
|
||||
set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_GLTF)
|
||||
endif()
|
||||
|
||||
# Add USD support to serializers
|
||||
# Add USD support to serializers
|
||||
if(USD_SUPPORT)
|
||||
UNIFY_ENVVARS_AND_CACHE(USD_INCLUDE_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(USD_LIBRARY_DIR)
|
||||
@@ -209,13 +212,13 @@ if(USD_SUPPORT)
|
||||
endif()
|
||||
|
||||
set(USD_LIBRARIES
|
||||
usd_usd
|
||||
usd_usd
|
||||
usd_usdGeom
|
||||
usd_usdShade
|
||||
usd_usdLux
|
||||
usd_vt
|
||||
usd_sdf
|
||||
usd_tf
|
||||
usd_usdShade
|
||||
usd_usdLux
|
||||
usd_vt
|
||||
usd_sdf
|
||||
usd_tf
|
||||
usd_gf
|
||||
)
|
||||
|
||||
@@ -373,13 +376,34 @@ if(BUILD_IFCGEOM)
|
||||
message(STATUS "Looking for Open CASCADE include files in: ${OCC_INCLUDE_DIR}")
|
||||
endif()
|
||||
|
||||
if(OCC_INCLUDE_DIR)
|
||||
file(STRINGS ${OCC_INCLUDE_DIR}/Standard_Version.hxx OCC_MAJOR
|
||||
REGEX "#define OCC_VERSION_MAJOR.*"
|
||||
)
|
||||
string(REGEX MATCH "[0-9]+" OCC_MAJOR ${OCC_MAJOR})
|
||||
file(STRINGS ${OCC_INCLUDE_DIR}/Standard_Version.hxx OCC_MINOR
|
||||
REGEX "#define OCC_VERSION_MINOR.*"
|
||||
)
|
||||
string(REGEX MATCH "[0-9]+" OCC_MINOR ${OCC_MINOR})
|
||||
file(STRINGS ${OCC_INCLUDE_DIR}/Standard_Version.hxx OCC_MAINT
|
||||
REGEX "#define OCC_VERSION_MAINTENANCE.*"
|
||||
)
|
||||
string(REGEX MATCH "[0-9]+" OCC_MAINT ${OCC_MAINT})
|
||||
set(OCC_VERSION_STRING "${OCC_MAJOR}.${OCC_MINOR}.${OCC_MAINT}")
|
||||
endif(OCC_INCLUDE_DIR)
|
||||
|
||||
set(OPENCASCADE_LIBRARY_NAMES
|
||||
TKernel TKMath TKBRep TKGeomBase TKGeomAlgo TKG3d TKG2d TKShHealing TKTopAlgo TKMesh TKPrim TKBool TKBO
|
||||
TKFillet TKSTEP TKSTEPBase TKSTEPAttr TKXSBase TKSTEP209 TKIGES TKOffset TKHLR
|
||||
TKFillet TKXSBase TKOffset TKHLR
|
||||
|
||||
# @todo investigate the exact conditions when this is necessary
|
||||
TKBin
|
||||
)
|
||||
if(OCC_VERSION_STRING VERSION_LESS 7.8.0)
|
||||
list(APPEND OPENCASCADE_LIBRARY_NAMES TKIGES TKSTEPBase TKSTEPAttr TKSTEP209 TKSTEP)
|
||||
else(OCC_VERSION_STRING VERSION_LESS 7.8.0)
|
||||
list(APPEND OPENCASCADE_LIBRARY_NAMES TKDESTEP TKDEIGES)
|
||||
endif(OCC_VERSION_STRING VERSION_LESS 7.8.0)
|
||||
|
||||
if("${OCC_LIBRARY_DIR}" STREQUAL "")
|
||||
find_library(OCC_LIBRARY TKernel
|
||||
@@ -435,7 +459,7 @@ if(BUILD_IFCGEOM)
|
||||
|
||||
if(OCCT_STATIC)
|
||||
find_package(Threads)
|
||||
|
||||
|
||||
if(WASM_BUILD)
|
||||
set(OPENCASCADE_LIBRARIES ${OPENCASCADE_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
|
||||
else()
|
||||
@@ -447,7 +471,7 @@ if(BUILD_IFCGEOM)
|
||||
set(OPENCASCADE_LIBRARIES -Wl,--start-group ${OPENCASCADE_LIBRARIES} -Wl,--end-group ${CMAKE_THREAD_LIBS_INIT})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
||||
if (NOT APPLE AND NOT WIN32)
|
||||
set(OPENCASCADE_LIBRARIES ${OPENCASCADE_LIBRARIES} "rt")
|
||||
endif()
|
||||
@@ -656,6 +680,10 @@ if(ENABLE_BUILD_OPTIMIZATIONS)
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3")
|
||||
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELEASE} -O3")
|
||||
endif()
|
||||
else()
|
||||
if(MSVC)
|
||||
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} /Od")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(MSVC)
|
||||
@@ -755,6 +783,8 @@ if(NOT SCHEMA_VERSIONS)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
message(STATUS "IFC SCHEMA_VERSIONS that will be used for the build: ${SCHEMA_VERSIONS}.")
|
||||
|
||||
foreach(schema ${SCHEMA_VERSIONS})
|
||||
add_definitions(-DHAS_SCHEMA_${schema})
|
||||
endforeach()
|
||||
@@ -837,7 +867,7 @@ if (BUILD_IFCGEOM)
|
||||
if (WASM_BUILD)
|
||||
set(IFCOPENSHELL_LIBRARIES ${IFCOPENSHELL_LIBRARIES} IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES})
|
||||
else()
|
||||
set(IFCOPENSHELL_LIBRARIES ${IFCOPENSHELL_LIBRARIES} IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES} IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES})
|
||||
set(IFCOPENSHELL_LIBRARIES ${IFCOPENSHELL_LIBRARIES} IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES} IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@@ -900,7 +930,7 @@ if(BUILD_IFCGEOM)
|
||||
add_library(IfcGeom_ifc${schema} STATIC ${IFCGEOM_FILES})
|
||||
set_target_properties(IfcGeom_ifc${schema} PROPERTIES COMPILE_FLAGS "-DIFC_GEOM_EXPORTS -DIfcSchema=Ifc${schema}")
|
||||
if (NOT WASM_BUILD)
|
||||
target_link_libraries(IfcGeom_ifc${schema} IfcParse ${OPENCASCADE_LIBRARIES})
|
||||
target_link_libraries(IfcGeom_ifc${schema} IfcParse ${OPENCASCADE_LIBRARIES})
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
@@ -936,7 +966,7 @@ if(BUILD_CONVERT OR BUILD_IFCPYTHON)
|
||||
foreach(schema ${SCHEMA_VERSIONS})
|
||||
add_library(Serializers_ifc${schema} STATIC ${SERIALIZERS_S_FILES})
|
||||
set_target_properties(Serializers_ifc${schema} PROPERTIES COMPILE_FLAGS "-DIFC_GEOM_EXPORTS -DIfcSchema=Ifc${schema}")
|
||||
|
||||
|
||||
if (WASM_BUILD)
|
||||
target_link_libraries(Serializers_ifc${schema} ${HDF5_LIBRARIES})
|
||||
else()
|
||||
@@ -1142,7 +1172,7 @@ endforeach(COMPONENT)
|
||||
|
||||
set(CPACK_DEBIAN_PACKAGE_NAME "${PROJECT_NAME}")
|
||||
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "${CPACK_PACKAGE_CONTACT}")
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "python3, libxml2, libocct-foundation-dev, libocct-modeling-algorithms-dev, libocct-modeling-data-dev, libocct-ocaf-dev, libocct-visualization-dev, libocct-data-exchange-dev, libhdf5-serial-dev, libpython3.8, python3-pytest ${BOOST_DEPS}")
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "python3, libxml2, libocct-foundation-dev, libocct-modeling-algorithms-dev, libocct-modeling-data-dev, libocct-ocaf-dev, libocct-visualization-dev, libocct-data-exchange-dev, libhdf5-serial-dev, libpython3-dev, python3-pytest ${BOOST_DEPS}")
|
||||
set(CPACK_DEBIAN_PACKAGE_DESCRIPTION_SUMMARY "${CPACK_PACKAGE_DESCRIPTION_SUMMARY}")
|
||||
set(CPACK_DEBIAN_PACKAGE_DESCRIPTION "${CPACK_PACKAGE_DESCRIPTION}")
|
||||
set(CPACK_DEBIAN_PACKAGE_PRIORITY "optional")
|
||||
|
||||
+9
-3
@@ -76,7 +76,7 @@ PROJECT_NAME = "IfcOpenShell"
|
||||
USE_CURRENT_PYTHON_VERSION = os.getenv("USE_CURRENT_PYTHON_VERSION")
|
||||
ADD_COMMIT_SHA = os.getenv("ADD_COMMIT_SHA")
|
||||
|
||||
PYTHON_VERSIONS = ["3.6.14", "3.7.13", "3.8.13", "3.9.11", "3.10.3", "3.11.7", "3.12.1"]
|
||||
PYTHON_VERSIONS = ["3.9.11", "3.10.3", "3.11.8", "3.12.1"]
|
||||
JSON_VERSION = "v3.6.1"
|
||||
OCE_VERSION = "0.18.3"
|
||||
OCCT_VERSION = "7.5.3"
|
||||
@@ -254,6 +254,11 @@ if not os.path.exists(LOG_FILE):
|
||||
open(LOG_FILE, "w").close()
|
||||
logger.info(f"using command log file '{LOG_FILE}'")
|
||||
|
||||
# Causing havoc in python 3.11 build
|
||||
try:
|
||||
del os.environ['__PYVENV_LAUNCHER__']
|
||||
except: pass
|
||||
|
||||
def run(cmds, cwd=None, can_fail=False):
|
||||
|
||||
"""
|
||||
@@ -284,8 +289,9 @@ if platform.system() == "Darwin":
|
||||
# Apparently not supported
|
||||
PYTHON_VERSIONS = [pv for pv in PYTHON_VERSIONS if tuple(map(int, pv.split("."))) >= (3, 7)]
|
||||
if run(["sw_vers", "-productVersion"]) < "10.16":
|
||||
# Apparently not supported
|
||||
PYTHON_VERSIONS = [pv for pv in PYTHON_VERSIONS if tuple(map(int, pv.split("."))) < (3, 11)]
|
||||
# This is now solved with the '__PYVENV_LAUNCHER__' hack
|
||||
# PYTHON_VERSIONS = [pv for pv in PYTHON_VERSIONS if tuple(map(int, pv.split("."))) < (3, 11)]
|
||||
pass
|
||||
|
||||
BOOST_VERSION_UNDERSCORE = BOOST_VERSION.replace(".", "_")
|
||||
|
||||
|
||||
+6
-256
@@ -61,26 +61,6 @@ PYPI_PLATFORM:=--platform manylinux_2_17_x86_64
|
||||
LXML_ARCH:=manylinux_2_28_x86_64
|
||||
SHAPELY_ARCH:=manylinux_2_17_x86_64.manylinux2014_x86_64
|
||||
PILLOW_ARCH:=manylinux_2_28_x86_64
|
||||
ifeq ($(PYVERSION), py39)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.4.0/download/linux-64/hpp-fcl-2.4.0-py39h359ec1e_0.conda
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.2.0/download/linux-64/eigenpy-3.2.0-py39hb1babde_0.conda
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/linux-64/boost-1.78.0-py39h7c9e3ff_4.tar.bz2
|
||||
endif
|
||||
ifeq ($(PYVERSION), py310)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.4.0/download/linux-64/hpp-fcl-2.4.0-py310hd002f03_0.conda
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.2.0/download/linux-64/eigenpy-3.2.0-py310hcfe0b05_0.conda
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/linux-64/boost-1.78.0-py310hc4a4660_4.tar.bz2
|
||||
endif
|
||||
ifeq ($(PYVERSION), py311)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.4.0/download/linux-64/hpp-fcl-2.4.0-py311h04f086a_0.conda
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.2.0/download/linux-64/eigenpy-3.2.0-py311h995a085_0.conda
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/linux-64/boost-1.78.0-py311h59ea3da_3.tar.bz2
|
||||
endif
|
||||
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/linux-64/assimp-5.0.1-hedfc422_6.tar.bz2
|
||||
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/linux-64/qhull-2020.2-h4bd325d_2.tar.bz2
|
||||
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.8/download/linux-64/octomap-1.9.8-h924138e_0.tar.bz2
|
||||
BOOSTCPP_URL:=https://anaconda.org/conda-forge/boost-cpp/1.78.0/download/linux-64/boost-cpp-1.78.0-h6582d0a_3.conda
|
||||
ZLIB_URL:=https://anaconda.org/conda-forge/zlib/1.2.11/download/linux-64/zlib-1.2.11-h516909a_1010.tar.bz2
|
||||
endif
|
||||
|
||||
ifeq ($(PLATFORM), macos)
|
||||
@@ -89,27 +69,13 @@ SHAPELY_ARCH:=macosx_10_9_x86_64
|
||||
PILLOW_ARCH:=macosx_10_10_x86_64
|
||||
ifeq ($(PYVERSION), py39)
|
||||
LXML_ARCH:=macosx_11_0_x86_64
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.4.0/download/osx-64/hpp-fcl-2.4.0-py39h26350b9_0.conda
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.2.0/download/osx-64/eigenpy-3.2.0-py39h3323527_0.conda
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-64/boost-1.78.0-py39h953a6b8_4.tar.bz2
|
||||
endif
|
||||
ifeq ($(PYVERSION), py310)
|
||||
LXML_ARCH:=macosx_11_0_x86_64
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.4.0/download/osx-64/hpp-fcl-2.4.0-py310ha216469_0.conda
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.2.0/download/osx-64/eigenpy-3.2.0-py310h33a8c9b_0.conda
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-64/boost-1.78.0-py310h3e792ce_4.tar.bz2
|
||||
endif
|
||||
ifeq ($(PYVERSION), py311)
|
||||
LXML_ARCH:=macosx_11_0_universal2
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.4.0/download/osx-64/hpp-fcl-2.4.0-py311hebdd1a2_0.conda
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.2.0/download/osx-64/eigenpy-3.2.0-py311h62e90b6_0.conda
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-64/boost-1.78.0-py311h349b758_4.tar.bz2
|
||||
endif
|
||||
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/osx-64/assimp-5.0.1-h1224e73_6.tar.bz2
|
||||
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/osx-64/qhull-2020.2-h940c156_2.tar.bz2
|
||||
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.8/download/osx-64/octomap-1.9.8-hb8565cd_0.tar.bz2
|
||||
BOOSTCPP_URL:=https://anaconda.org/conda-forge/boost-cpp/1.78.0/download/osx-64/boost-cpp-1.78.0-hf5ba120_3.conda
|
||||
ZLIB_URL:=https://anaconda.org/conda-forge/zlib/1.2.11/download/osx-64/zlib-1.2.11-h7795811_1010.tar.bz2
|
||||
endif
|
||||
|
||||
ifeq ($(PLATFORM), macosm1)
|
||||
@@ -117,26 +83,6 @@ PYPI_PLATFORM:=--platform macosx_11_0_arm64
|
||||
LXML_ARCH:=macosx_11_0_universal2
|
||||
SHAPELY_ARCH:=macosx_11_0_arm64
|
||||
PILLOW_ARCH:=macosx_11_0_arm64
|
||||
ifeq ($(PYVERSION), py39)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.4.0/download/osx-arm64/hpp-fcl-2.4.0-py39h82b2ff2_0.conda
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.2.0/download/osx-arm64/eigenpy-3.2.0-py39h3a9e884_0.conda
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-arm64/boost-1.78.0-py39h99de9ae_4.tar.bz2
|
||||
endif
|
||||
ifeq ($(PYVERSION), py310)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.4.0/download/osx-arm64/hpp-fcl-2.4.0-py310hdb6c3eb_0.conda
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.2.0/download/osx-arm64/eigenpy-3.2.0-py310h96e9823_0.conda
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-arm64/boost-1.78.0-py310h629746b_4.tar.bz2
|
||||
endif
|
||||
ifeq ($(PYVERSION), py311)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.4.0/download/osx-arm64/hpp-fcl-2.4.0-py311hc0c29b9_0.conda
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.2.0/download/osx-arm64/eigenpy-3.2.0-py311h24fa84d_0.conda
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-arm64/boost-1.78.0-py311h5c64717_4.tar.bz2
|
||||
endif
|
||||
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/osx-arm64/assimp-5.0.1-h0f81e16_7.tar.bz2
|
||||
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/osx-arm64/qhull-2020.2-hc021e02_2.tar.bz2
|
||||
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.8/download/osx-arm64/octomap-1.9.8-hffc8910_0.tar.bz2
|
||||
BOOSTCPP_URL:=https://anaconda.org/conda-forge/boost-cpp/1.78.0/download/osx-arm64/boost-cpp-1.78.0-h9ed8d21_3.conda
|
||||
ZLIB_URL:=https://anaconda.org/conda-forge/zlib/1.2.11/download/osx-arm64/zlib-1.2.11-h90dfc92_1014.tar.bz2
|
||||
endif
|
||||
|
||||
ifeq ($(PLATFORM), win)
|
||||
@@ -144,26 +90,6 @@ PYPI_PLATFORM:=--platform win_amd64
|
||||
LXML_ARCH:=win_amd64
|
||||
SHAPELY_ARCH:=win_amd64
|
||||
PILLOW_ARCH:=win_amd64
|
||||
ifeq ($(PYVERSION), py39)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.4.0/download/win-64/hpp-fcl-2.4.0-py39hf3ebc58_0.conda
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.2.0/download/win-64/eigenpy-3.2.0-py39ha1f6a47_0.conda
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/win-64/boost-1.78.0-py39hea4d8d1_4.tar.bz2
|
||||
endif
|
||||
ifeq ($(PYVERSION), py310)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.4.0/download/win-64/hpp-fcl-2.4.0-py310h1bf3f0a_0.conda
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.2.0/download/win-64/eigenpy-3.2.0-py310h7fb871d_0.conda
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/win-64/boost-1.78.0-py310h220cb41_4.tar.bz2
|
||||
endif
|
||||
ifeq ($(PYVERSION), py311)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.4.0/download/win-64/hpp-fcl-2.4.0-py311hd263b2c_0.conda
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.2.0/download/win-64/eigenpy-3.2.0-py311hd354db1_0.conda
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/win-64/boost-1.78.0-py311h12feb9d_3.tar.bz2
|
||||
endif
|
||||
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.2.5/download/win-64/assimp-5.2.5-h4dcb625_0.tar.bz2
|
||||
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/win-64/qhull-2020.2-h70d2c02_2.tar.bz2
|
||||
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.8/download/win-64/octomap-1.9.8-h91493d7_0.tar.bz2
|
||||
BOOSTCPP_URL:=https://anaconda.org/conda-forge/boost-cpp/1.78.0/download/win-64/boost-cpp-1.78.0-h9f4b32c_3.conda
|
||||
ZLIB_URL:=https://anaconda.org/conda-forge/zlib/1.2.11/download/win-64/zlib-1.2.11-h62dcd97_1010.tar.bz2
|
||||
endif
|
||||
|
||||
LXML_NAME:=lxml
|
||||
@@ -211,7 +137,7 @@ endif
|
||||
cp -r blenderbim/* dist/blenderbim/
|
||||
|
||||
# Provides IfcOpenShell Python functionality
|
||||
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-$(PYNUMBER)-v0.7.0-6c9e130-$(PLATFORM)64.zip
|
||||
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-$(PYNUMBER)-v0.7.0-e38eafd-$(PLATFORM)64.zip
|
||||
cd dist/working && unzip ifcopenshell-python*
|
||||
cp -r dist/working/ifcopenshell dist/blenderbim/libs/site/packages/
|
||||
|
||||
@@ -427,6 +353,8 @@ endif
|
||||
cd dist/working && mkdir site-packages
|
||||
# Required by IfcCSV
|
||||
cd dist/working && . env/bin/activate && $(PIP) install pandas $(PYPI_PLATFORM) --python-version $(PYPI_VERSION) --implementation $(PYPI_IMP) --only-binary=:all: --target=./site-packages
|
||||
# Pandas ships with very heavy tests
|
||||
cd dist/working/site-packages/pandas && rm -rf tests
|
||||
cd dist/working && . env/bin/activate && $(PIP) install openpyxl --target=./site-packages
|
||||
cd dist/working && . env/bin/activate && $(PIP) install odfpy --target=./site-packages
|
||||
# Required by IFCCityJSON
|
||||
@@ -438,157 +366,6 @@ endif
|
||||
cp -r dist/working/site-packages/* dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by IFCClash
|
||||
mkdir dist/working
|
||||
cd dist/working && wget $(HPPFCL_URL)
|
||||
cd dist/working && unzip hpp-fcl*
|
||||
cd dist/working && tar -I zstd -xvf pkg*
|
||||
ifeq ($(PLATFORM), linux)
|
||||
cp -r dist/working/lib/$(PYLIBDIR)/site-packages/hppfcl dist/blenderbim/libs/site/packages/
|
||||
cp -r dist/working/lib/*.so* dist/blenderbim/libs/
|
||||
endif
|
||||
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/
|
||||
endif
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by hpp-fcl
|
||||
mkdir dist/working
|
||||
cd dist/working && wget $(ASSIMP_URL)
|
||||
cd dist/working && tar -xf assimp*
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
mkdir dist/working
|
||||
cd dist/working && wget $(EIGENPY_URL)
|
||||
cd dist/working && unzip eigenpy*
|
||||
cd dist/working && tar -I zstd -xvf pkg*
|
||||
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
|
||||
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
|
||||
mkdir dist/working
|
||||
cd dist/working && wget $(OCTOMAP_URL)
|
||||
cd dist/working && tar -xf octomap*
|
||||
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
|
||||
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
|
||||
mkdir dist/working
|
||||
cd dist/working && wget $(BOOST_URL)
|
||||
cd dist/working && tar -xf boost*
|
||||
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
|
||||
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
|
||||
mkdir dist/working
|
||||
cd dist/working && wget $(BOOSTCPP_URL)
|
||||
cd dist/working && unzip boost-cpp*
|
||||
cd dist/working && tar -I zstd -xvf pkg*
|
||||
ifeq ($(PLATFORM), linux)
|
||||
cp -r dist/working/lib/libboost_serialization.so* dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), macos)
|
||||
cp -r dist/working/lib/libboost_serialization.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), macosm1)
|
||||
cp -r dist/working/lib/libboost_serialization.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), win)
|
||||
cp -r dist/working/Library/bin/boost_serialization.dll dist/blenderbim/libs/site/packages/hppfcl/
|
||||
endif
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by hpp-fcl
|
||||
mkdir dist/working
|
||||
cd dist/working && wget $(ZLIB_URL)
|
||||
cd dist/working && tar -xf zlib*
|
||||
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
|
||||
ifeq ($(PLATFORM), win)
|
||||
cp -r dist/working/Library/bin/*.dll dist/blenderbim/libs/site/packages/hppfcl/
|
||||
endif
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by BIMTester
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/c8/4b/d0a8c23b6c8985e5544ea96d27105a273ea22051317f850c2cdbf2029fe4/behave-1.2.6.tar.gz
|
||||
@@ -636,34 +413,6 @@ endif
|
||||
cd dist/working/parse_type-0.5.2/ && cp -r parse_type ../../blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by augin
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/76/b4/b7baffbda025efd5dc8fcd8d2e953e3aa939c236a484084fa8f4c3588ee9/boto3-1.17.17.tar.gz
|
||||
cd dist/working && tar -xzvf boto3*
|
||||
cd dist/working/boto3-1.17.17/ && cp -r boto3 ../../blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by boto3
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/7f/2e/e02fdfd0f0377c4e44e61de27b05d5cfe93575770661ef9ded80ed90fa88/botocore-1.20.17.tar.gz
|
||||
cd dist/working && tar -xzvf botocore*
|
||||
cd dist/working/botocore-1.20.17/ && cp -r botocore ../../blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by boto3
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/3c/56/3f325b1eef9791759784aa5046a8f6a1aff8f7c898a2e34506771d3b99d8/jmespath-0.10.0.tar.gz
|
||||
cd dist/working && tar -xzvf jmespath*
|
||||
cd dist/working/jmespath-0.10.0/ && cp -r jmespath ../../blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by boto3
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/08/e1/3ee2096ebaeeb8c186d20ed16c8faf4a503913e5c9a0e14cd6b8ffc405a3/s3transfer-0.3.4.tar.gz
|
||||
cd dist/working && tar -xzvf s3transfer*
|
||||
cd dist/working/s3transfer-0.3.4/ && cp -r s3transfer ../../blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Provides IFCJSON functionality
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://github.com/IFCJSON-Team/IFC2JSON_python/archive/master.zip
|
||||
@@ -696,9 +445,9 @@ endif
|
||||
# Required for Desktop icon and file association
|
||||
cp -r blenderbim/libs/desktop dist/blenderbim/libs/
|
||||
|
||||
# generate translations module for BBIM build
|
||||
# Generate translations module for BBIM build
|
||||
git clone https://github.com/IfcOpenShell/blenderbim-translations.git dist/working
|
||||
python scripts/bbim_translations.py -i "dist/working" -o "dist/blenderbim"
|
||||
$(PYTHON) scripts/bbim_translations.py -i "dist/working" -o "dist/blenderbim"
|
||||
rm -rf dist/working
|
||||
|
||||
# Remove dependencies also bundled with Blender
|
||||
@@ -706,6 +455,7 @@ endif
|
||||
rm -rf dist/blenderbim/libs/site/packages/numpy.libs
|
||||
|
||||
cd dist/blenderbim && $(SED) "s/999999/$(VERSION)/" __init__.py
|
||||
cd dist/blenderbim && $(SED) "s/999999/$(VERSION)/" blender_manifest.toml
|
||||
cd dist/blenderbim/bim && $(SED) "s/8888888/$(LAST_COMMIT_HASH)/" __init__.py
|
||||
cd dist && zip -r blenderbim-$(VERSION)-$(PYVERSION)-$(PLATFORM).zip ./*
|
||||
rm -rf dist/blenderbim
|
||||
|
||||
@@ -99,6 +99,7 @@ classes = [
|
||||
operator.BIM_OT_open_webbrowser,
|
||||
operator.BIM_OT_remove_section_plane,
|
||||
operator.BIM_OT_show_description,
|
||||
operator.ClippingPlaneCutWithCappings,
|
||||
operator.EditBlenderCollection,
|
||||
operator.FileAssociate,
|
||||
operator.FileUnassociate,
|
||||
@@ -106,6 +107,7 @@ classes = [
|
||||
operator.OpenUri,
|
||||
operator.ReloadIfcFile,
|
||||
operator.RemoveIfcFile,
|
||||
operator.RevertClippingPlaneCut,
|
||||
operator.SelectDataDir,
|
||||
operator.SelectIfcFile,
|
||||
operator.SelectSchemaDir,
|
||||
@@ -175,6 +177,7 @@ classes = [
|
||||
ui.BIM_PT_tab_sandbox,
|
||||
# TODO: move this somewhere else and clean it up
|
||||
ui.BIM_PT_section_plane,
|
||||
ui.BIM_PT_section_with_cappings,
|
||||
]
|
||||
|
||||
for mod in modules.values():
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* but WITHOUT ANY WARRANTY, without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* but WITHOUT ANY WARRANTY, without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
ISO-10303-21;
|
||||
HEADER;
|
||||
FILE_DESCRIPTION(('ViewDefinition[DesignTransferView]'),'2;1');
|
||||
FILE_NAME('/dev/null','2023-09-07T13:40:06+10:00',(),(),'IfcOpenShell v0.7.0-fbd8ea1ed','IfcOpenShell v0.7.0-fbd8ea1ed','Nobody');
|
||||
FILE_NAME('/dev/null','2024-03-03T16:03:58+11:00',(),(),'IfcOpenShell v0.7.0-eaa2aa05a','IfcOpenShell v0.7.0-eaa2aa05a','Nobody');
|
||||
FILE_SCHEMA(('IFC4'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#1=IFCPROJECT('1arVsMni92iPUaaryGTUz2',$,'BlenderBIM Demo',$,$,$,$,(#12,#16),#7);
|
||||
#2=IFCPROJECTLIBRARY('1FfDG5b21DuBDOO4eUE9rF',$,'BlenderBIM Demo Library',$,$,$,$,$,$);
|
||||
#3=IFCRELDECLARES('3tfDIC3Tf9wP8EUOoFeycK',$,$,$,#1,(#2));
|
||||
#1=IFCPROJECT('3NJUxHyTzEv96oezyDYEqf',$,'BlenderBIM Demo',$,$,$,$,(#12,#16),#7);
|
||||
#2=IFCPROJECTLIBRARY('0D39k7hbT5ce$jWaCd2niG',$,'BlenderBIM Demo Library',$,$,$,$,$,$);
|
||||
#3=IFCRELDECLARES('2RWVohyP57PBHT9zCs4H2O',$,$,$,#1,(#2));
|
||||
#4=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);
|
||||
#5=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.);
|
||||
#6=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.);
|
||||
#7=IFCUNITASSIGNMENT((#5,#6,#4));
|
||||
#7=IFCUNITASSIGNMENT((#6,#4,#5));
|
||||
#8=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#9=IFCDIRECTION((0.,0.,1.));
|
||||
#10=IFCDIRECTION((1.,0.,0.));
|
||||
@@ -23,84 +23,84 @@ DATA;
|
||||
#16=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Plan',2,1.E-05,#15,$);
|
||||
#17=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Body','Model',*,*,*,*,#12,$,.MODEL_VIEW.,$);
|
||||
#18=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Body','Plan',*,*,*,*,#16,$,.PLAN_VIEW.,$);
|
||||
#19=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Annotation','Plan',*,*,*,*,#16,$,.PLAN_VIEW.,$);
|
||||
#19=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Annotation','Model',*,*,*,*,#16,$,.MODEL_VIEW.,$);
|
||||
#20=IFCMATERIAL('Unknown',$,$);
|
||||
#21=IFCWALLTYPE('2o8NqQQbHA5BeErrgwfr7d',$,'WAL50',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#21=IFCWALLTYPE('2F1k78lI53fwS0rg3rqrRb',$,'WAL50',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#22=IFCMATERIALLAYERSET((#24),$,$);
|
||||
#23=IFCRELASSOCIATESMATERIAL('1Q$5DZKr98n82Eitz2dWey',$,$,$,(#21),#22);
|
||||
#23=IFCRELASSOCIATESMATERIAL('0khP_Smdj12f2JrOTRbRKj',$,$,$,(#21),#22);
|
||||
#24=IFCMATERIALLAYER(#20,0.05,$,$,$,$,$);
|
||||
#25=IFCRELDECLARES('1KXKUEn8z0g8lvyEsCTWD3',$,$,$,#2,(#38,#82,#77,#42,#26,#48,#72,#59,#92,#30,#96,#63,#21,#54,#87,#153,#34,#67,#210));
|
||||
#26=IFCWALLTYPE('3KlIOv_P9A79tkt8D_jq_m',$,'WAL100',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#25=IFCRELDECLARES('31JriXdev6BP2sC9lcxdC1',$,$,$,#2,(#26,#48,#72,#59,#92,#30,#96,#63,#21,#54,#87,#210,#153,#34,#67,#38,#82,#77,#42));
|
||||
#26=IFCWALLTYPE('3R5onkTtX1IxdCMPoThRaw',$,'WAL100',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#27=IFCMATERIALLAYERSET((#29),$,$);
|
||||
#28=IFCRELASSOCIATESMATERIAL('32B9Wx8Z1FxhIN7m77MiHG',$,$,$,(#26),#27);
|
||||
#28=IFCRELASSOCIATESMATERIAL('0$mXRPCT16nP6Q70XFNvZv',$,$,$,(#26),#27);
|
||||
#29=IFCMATERIALLAYER(#20,0.1,$,$,$,$,$);
|
||||
#30=IFCWALLTYPE('2DqrSeel1AGw6h3b$ujafZ',$,'WAL200',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#30=IFCWALLTYPE('1juanculbDJPqvqT2FCm8m',$,'WAL200',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#31=IFCMATERIALLAYERSET((#33),$,$);
|
||||
#32=IFCRELASSOCIATESMATERIAL('2FeYUpt3D4tAvwgGmwXWZn',$,$,$,(#30),#31);
|
||||
#32=IFCRELASSOCIATESMATERIAL('0FsB2A$frFn9AZW2IG38ny',$,$,$,(#30),#31);
|
||||
#33=IFCMATERIALLAYER(#20,0.2,$,$,$,$,$);
|
||||
#34=IFCWALLTYPE('2jNdEOFIP1eQb3WZePlFGY',$,'WAL300',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#34=IFCWALLTYPE('05UYduR9r1vRxZZgP64Rdw',$,'WAL300',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#35=IFCMATERIALLAYERSET((#37),$,$);
|
||||
#36=IFCRELASSOCIATESMATERIAL('2bFeM6xevAwucuwMlauX77',$,$,$,(#34),#35);
|
||||
#36=IFCRELASSOCIATESMATERIAL('2FYhYrvmv4SBoZHVWDTTte',$,$,$,(#34),#35);
|
||||
#37=IFCMATERIALLAYER(#20,0.3,$,$,$,$,$);
|
||||
#38=IFCCOVERINGTYPE('3310gCgH59w9tu$DfmiCEN',$,'COV10',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#38=IFCCOVERINGTYPE('0iLxgfHB9F2hRH2vMcw7Yv',$,'COV10',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#39=IFCMATERIALLAYERSET((#41),$,$);
|
||||
#40=IFCRELASSOCIATESMATERIAL('3pTNmWd_jBoOWyHc8Yf4qI',$,$,$,(#38),#39);
|
||||
#40=IFCRELASSOCIATESMATERIAL('0yvFX0zgb9uvJ3ARnf4mfe',$,$,$,(#38),#39);
|
||||
#41=IFCMATERIALLAYER(#20,0.01,$,$,$,$,$);
|
||||
#42=IFCCOVERINGTYPE('2sksWFuPzCgeNk1ofXS6PZ',$,'COV20',$,$,(#46),$,$,$,.NOTDEFINED.);
|
||||
#42=IFCCOVERINGTYPE('15Riw2WTrAUPwvfLyKXJ2g',$,'COV20',$,$,(#46),$,$,$,.NOTDEFINED.);
|
||||
#43=IFCMATERIALLAYERSET((#45),$,$);
|
||||
#44=IFCRELASSOCIATESMATERIAL('36rl5NddT1ux6ejkQAkT2z',$,$,$,(#42),#43);
|
||||
#44=IFCRELASSOCIATESMATERIAL('1iAOG6NK9FBxitFtzN9qP5',$,$,$,(#42),#43);
|
||||
#45=IFCMATERIALLAYER(#20,0.02,$,$,$,$,$);
|
||||
#46=IFCPROPERTYSET('2VZJ9xfdn1NeOY6uEmnnYW',$,'EPset_Parametric',$,(#47));
|
||||
#46=IFCPROPERTYSET('18ICCZ0fjDG9UXMyzrsFBA',$,'EPset_Parametric',$,(#47));
|
||||
#47=IFCPROPERTYSINGLEVALUE('LayerSetDirection',$,IFCLABEL('AXIS2'),$);
|
||||
#48=IFCCOVERINGTYPE('1uU6rxt95AC91lZ0jTRG6G',$,'COV30',$,$,(#52),$,$,$,.NOTDEFINED.);
|
||||
#48=IFCCOVERINGTYPE('0gEOtYULD0F9KD9zPoA6cJ',$,'COV30',$,$,(#52),$,$,$,.NOTDEFINED.);
|
||||
#49=IFCMATERIALLAYERSET((#51),$,$);
|
||||
#50=IFCRELASSOCIATESMATERIAL('2lFv4xQYr5bwtiSsMTVCgz',$,$,$,(#48),#49);
|
||||
#50=IFCRELASSOCIATESMATERIAL('0xd6dsjf13JA98q9fnAMpQ',$,$,$,(#48),#49);
|
||||
#51=IFCMATERIALLAYER(#20,0.03,$,$,$,$,$);
|
||||
#52=IFCPROPERTYSET('0HnrRhcd16Yfbgtci4sqCG',$,'EPset_Parametric',$,(#53));
|
||||
#52=IFCPROPERTYSET('3qCbs1tuDCvuOXam5RrA$a',$,'EPset_Parametric',$,(#53));
|
||||
#53=IFCPROPERTYSINGLEVALUE('LayerSetDirection',$,IFCLABEL('AXIS3'),$);
|
||||
#54=IFCRAMPTYPE('3nonXCVn58jhVV08N8c97B',$,'RAM200',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#54=IFCRAMPTYPE('2yW1ePlyb8cOR6HdEntpRh',$,'RAM200',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#55=IFCMATERIALLAYERSET((#57),$,$);
|
||||
#56=IFCRELASSOCIATESMATERIAL('2NdIrmbUP6bh4x$p_RY31C',$,$,$,(#54),#55);
|
||||
#56=IFCRELASSOCIATESMATERIAL('1x9ZyVim19AQtJhfEE06XR',$,$,$,(#54),#55);
|
||||
#57=IFCMATERIALLAYER(#20,0.2,$,$,$,$,$);
|
||||
#58=IFCCIRCLEPROFILEDEF(.AREA.,$,$,0.3);
|
||||
#59=IFCPILETYPE('2uuf5kq5TDDfO9jGN7XIHh',$,'P1',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#59=IFCPILETYPE('25MTkMtaH5QeCf$gFbvSSw',$,'P1',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#60=IFCMATERIALPROFILESET($,$,(#62),$);
|
||||
#61=IFCRELASSOCIATESMATERIAL('3hi821Ffj8YR9yFjp226LU',$,$,$,(#59),#60);
|
||||
#61=IFCRELASSOCIATESMATERIAL('09FXUZCnrFEffxBrtg16gx',$,$,$,(#59),#60);
|
||||
#62=IFCMATERIALPROFILE($,$,#20,#58,$,$);
|
||||
#63=IFCSLABTYPE('2janmz3nH4P9IHaOVvGY_B',$,'FLR150',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#63=IFCSLABTYPE('1Akbal7tP5DAPW0ti$Qp30',$,'FLR150',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#64=IFCMATERIALLAYERSET((#66),$,$);
|
||||
#65=IFCRELASSOCIATESMATERIAL('0og5Zg4ib67g6XWgOgqBWy',$,$,$,(#63),#64);
|
||||
#65=IFCRELASSOCIATESMATERIAL('0jzkXlkBL4HhmQ_FTWW6Cj',$,$,$,(#63),#64);
|
||||
#66=IFCMATERIALLAYER(#20,0.2,$,$,$,$,$);
|
||||
#67=IFCSLABTYPE('26UKpEaTb9fh4qrqi3Ymzj',$,'FLR250',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#67=IFCSLABTYPE('0$gpYNi3T08BrxaR67CxgI',$,'FLR250',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#68=IFCMATERIALLAYERSET((#70),$,$);
|
||||
#69=IFCRELASSOCIATESMATERIAL('1BeBXPqen9TQEE42SxzhjF',$,$,$,(#67),#68);
|
||||
#69=IFCRELASSOCIATESMATERIAL('3a6kjEM29DleYTlSkA8_DK',$,$,$,(#67),#68);
|
||||
#70=IFCMATERIALLAYER(#20,0.3,$,$,$,$,$);
|
||||
#71=IFCRECTANGLEPROFILEDEF(.AREA.,'500x600',$,0.5,0.6);
|
||||
#72=IFCCOLUMNTYPE('1KnWzBBoLCNfJ3_J79LT7d',$,'C1',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#72=IFCCOLUMNTYPE('278sjptdDDzgOZseERj390',$,'C1',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#73=IFCMATERIALPROFILESET($,$,(#75),$);
|
||||
#74=IFCRELASSOCIATESMATERIAL('1W8OWQjEL05gH5hEpve6Lh',$,$,$,(#72),#73);
|
||||
#74=IFCRELASSOCIATESMATERIAL('1ppYXw39v6kBwi3rrIm4ZE',$,$,$,(#72),#73);
|
||||
#75=IFCMATERIALPROFILE($,$,#20,#71,$,$);
|
||||
#76=IFCCIRCLEHOLLOWPROFILEDEF(.AREA.,'500.0x5.0 CHS',$,0.25,0.005);
|
||||
#77=IFCCOLUMNTYPE('33jPFERfL9bfTeWfTL5kZ7',$,'C2',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#77=IFCCOLUMNTYPE('3aSZOvGmr7jggSNwsq5PJE',$,'C2',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#78=IFCMATERIALPROFILESET($,$,(#80),$);
|
||||
#79=IFCRELASSOCIATESMATERIAL('3czOrQw2v9BfS0PeQCrzWq',$,$,$,(#77),#78);
|
||||
#79=IFCRELASSOCIATESMATERIAL('1AtNJPaD14DgINbAjXvbU4',$,$,$,(#77),#78);
|
||||
#80=IFCMATERIALPROFILE($,$,#20,#76,$,$);
|
||||
#81=IFCRECTANGLEHOLLOWPROFILEDEF(.AREA.,'150x75x2.0 RHS',$,0.075,0.15,0.002,0.005,0.005);
|
||||
#82=IFCCOLUMNTYPE('21YcNdKNj2$95U55yNx1Nw',$,'C3',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#82=IFCCOLUMNTYPE('28iv3Kru12yQ7R7RRwNjvD',$,'C3',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#83=IFCMATERIALPROFILESET($,$,(#85),$);
|
||||
#84=IFCRELASSOCIATESMATERIAL('3n6Cru3xr6jvDptU9u7QEF',$,$,$,(#82),#83);
|
||||
#84=IFCRELASSOCIATESMATERIAL('2M_oL$n3j6rBRnOW5RSk87',$,$,$,(#82),#83);
|
||||
#85=IFCMATERIALPROFILE($,$,#20,#81,$,$);
|
||||
#86=IFCISHAPEPROFILEDEF(.AREA.,'DEMO-I',$,0.1,0.2,0.005,0.01,0.005,$,$);
|
||||
#87=IFCBEAMTYPE('2CsGpD$6nDCxWv9jXTtvq9',$,'B1',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#87=IFCBEAMTYPE('2E$V5l4b54dxuToJ1A6IHp',$,'B1',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#88=IFCMATERIALPROFILESET($,$,(#90),$);
|
||||
#89=IFCRELASSOCIATESMATERIAL('2LJExc74j9rgBSBx51HDPK',$,$,$,(#87),#88);
|
||||
#89=IFCRELASSOCIATESMATERIAL('3PUNnY7cj8LhyXbHKVqZnf',$,$,$,(#87),#88);
|
||||
#90=IFCMATERIALPROFILE($,$,#20,#86,$,$);
|
||||
#91=IFCCSHAPEPROFILEDEF(.AREA.,'DEMO-C',$,0.2,0.1,0.0015,0.03,0.005);
|
||||
#92=IFCBEAMTYPE('32pHN2b0P0awGw$U8PpneO',$,'B2',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#92=IFCBEAMTYPE('3qutoZTvP0lvLWNXyjPhPm',$,'B2',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#93=IFCMATERIALPROFILESET($,$,(#95),$);
|
||||
#94=IFCRELASSOCIATESMATERIAL('2wg8R1Drb2xhQ2Y_pRSh3c',$,$,$,(#92),#93);
|
||||
#94=IFCRELASSOCIATESMATERIAL('3dGyQf42H2ahch2bSYPPHP',$,$,$,(#92),#93);
|
||||
#95=IFCMATERIALPROFILE($,$,#20,#91,$,$);
|
||||
#96=IFCWINDOWTYPE('1Gg9HfZEr69u8SdWfDs2J3',$,'WT01',$,$,$,(#135,#152),$,$,.NOTDEFINED.,.NOTDEFINED.,$,$);
|
||||
#96=IFCWINDOWTYPE('0bK3c4PWL3eOMNwkPN$rlg',$,'WT01',$,$,$,(#135,#152),$,$,.NOTDEFINED.,.NOTDEFINED.,$,$);
|
||||
#97=IFCINDEXEDPOLYGONALFACE((13,17,18,14));
|
||||
#98=IFCINDEXEDPOLYGONALFACE((5,6,3,4));
|
||||
#99=IFCINDEXEDPOLYGONALFACE((7,8,2,1));
|
||||
@@ -157,7 +157,7 @@ DATA;
|
||||
#150=IFCDIRECTION((0.,0.,1.));
|
||||
#151=IFCAXIS2PLACEMENT3D(#148,#150,#149);
|
||||
#152=IFCREPRESENTATIONMAP(#151,#147);
|
||||
#153=IFCDOORTYPE('0NBUmPKyT9WecsIeYJrEqg',$,'DT01',$,$,$,(#196,#209),$,$,.NOTDEFINED.,.NOTDEFINED.,$,$);
|
||||
#153=IFCDOORTYPE('2j0vIBgkH0ERnBCP0cpcCz',$,'DT01',$,$,$,(#196,#209),$,$,.NOTDEFINED.,.NOTDEFINED.,$,$);
|
||||
#154=IFCINDEXEDPOLYGONALFACE((17,16,15,14));
|
||||
#155=IFCINDEXEDPOLYGONALFACE((2,3,29,28));
|
||||
#156=IFCINDEXEDPOLYGONALFACE((27,28,29,30,32,31));
|
||||
@@ -214,7 +214,7 @@ DATA;
|
||||
#207=IFCDIRECTION((0.,0.,1.));
|
||||
#208=IFCAXIS2PLACEMENT3D(#205,#207,#206);
|
||||
#209=IFCREPRESENTATIONMAP(#208,#204);
|
||||
#210=IFCFURNITURETYPE('3Kyc6IyarAUw3_8fkNtXIg',$,'BUN01',$,$,$,(#931,#952),$,$,.NOTDEFINED.,.NOTDEFINED.);
|
||||
#210=IFCFURNITURETYPE('02XxQ_3oT0SPrmFPATrt7o',$,'BUN01',$,$,$,(#931,#952),$,$,.NOTDEFINED.,.NOTDEFINED.);
|
||||
#211=IFCINDEXEDPOLYGONALFACE((187,278,44));
|
||||
#212=IFCINDEXEDPOLYGONALFACE((21,52,60));
|
||||
#213=IFCINDEXEDPOLYGONALFACE((91,100,31));
|
||||
@@ -957,35 +957,35 @@ DATA;
|
||||
#950=IFCDIRECTION((0.,0.,1.));
|
||||
#951=IFCAXIS2PLACEMENT3D(#948,#950,#949);
|
||||
#952=IFCREPRESENTATIONMAP(#951,#947);
|
||||
#953=IFCTYPEPRODUCT('0TBMBnD_b66QLUWKD9HLsc',$,'SETOUT-POINT',$,'IfcAnnotation/SYMBOL',(#954),$,$);
|
||||
#954=IFCPROPERTYSET('2dQzX_K4r1Xet6dz9zBlgs',$,'EPset_Annotation',$,(#955));
|
||||
#953=IFCTYPEPRODUCT('0UrP0fLdD5OwzwD41aRKao',$,'SETOUT-POINT',$,'IfcAnnotation/SYMBOL',(#954),$,$);
|
||||
#954=IFCPROPERTYSET('27lmSbeAXC08EWkEq8XdUG',$,'EPset_Annotation',$,(#955));
|
||||
#955=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('setout-point'),$);
|
||||
#956=IFCTYPEPRODUCT('0vo_7PU3H9ygTnrIipqPRI',$,'CONTROL-POINT',$,'IfcAnnotation/SYMBOL',(#957),$,$);
|
||||
#957=IFCPROPERTYSET('3APQOw$FP1ivxp08UxHsl6',$,'EPset_Annotation',$,(#958));
|
||||
#956=IFCTYPEPRODUCT('3IQrQOSFP0WfkuLk0ak7_0',$,'CONTROL-POINT',$,'IfcAnnotation/SYMBOL',(#957),$,$);
|
||||
#957=IFCPROPERTYSET('23aN9DsdjFcfWq8KGSIVoN',$,'EPset_Annotation',$,(#958));
|
||||
#958=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('control-point'),$);
|
||||
#959=IFCTYPEPRODUCT('32V27G3$T1OO3TbXg6948F',$,'TRAVERSE-POINT',$,'IfcAnnotation/SYMBOL',(#960),$,$);
|
||||
#960=IFCPROPERTYSET('0cTSYXMc9B$PTXVDrhBYYW',$,'EPset_Annotation',$,(#961));
|
||||
#959=IFCTYPEPRODUCT('3wvbaiaIz8agQgDRzt01vz',$,'TRAVERSE-POINT',$,'IfcAnnotation/SYMBOL',(#960),$,$);
|
||||
#960=IFCPROPERTYSET('1j7aFM1Rb9BRuKgUEn1U10',$,'EPset_Annotation',$,(#961));
|
||||
#961=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('traverse-point'),$);
|
||||
#962=IFCTYPEPRODUCT('2PXIC7Bg914gJhRh2XeGnN',$,'DASHED',$,'IfcAnnotation/LINEWORK',(#963),$,$);
|
||||
#963=IFCPROPERTYSET('0RiYsOxp529gXnCkw44wF8',$,'EPset_Annotation',$,(#964));
|
||||
#962=IFCTYPEPRODUCT('0p5ZTfTnX9ZOywroa7Ffql',$,'DASHED',$,'IfcAnnotation/LINEWORK',(#963),$,$);
|
||||
#963=IFCPROPERTYSET('27smSHyiv39vhBRSwfDVCD',$,'EPset_Annotation',$,(#964));
|
||||
#964=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('dashed'),$);
|
||||
#965=IFCTYPEPRODUCT('1Ou1kA3Vb4HhuNBvM0uGe0',$,'FINE',$,'IfcAnnotation/LINEWORK',(#966),$,$);
|
||||
#966=IFCPROPERTYSET('2gzly9D0L1qPK2MExeXgRO',$,'EPset_Annotation',$,(#967));
|
||||
#965=IFCTYPEPRODUCT('2EvrG9Vuf6t9etgMeWFuJ2',$,'FINE',$,'IfcAnnotation/LINEWORK',(#966),$,$);
|
||||
#966=IFCPROPERTYSET('0gVTuDcZ5ByRdR3sZnEZUR',$,'EPset_Annotation',$,(#967));
|
||||
#967=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('fine'),$);
|
||||
#968=IFCTYPEPRODUCT('3lx$KQPRbEZwbQ7Xdfm5gw',$,'THIN',$,'IfcAnnotation/LINEWORK',(#969),$,$);
|
||||
#969=IFCPROPERTYSET('0vsVpqs6zArvA2bfBtvQew',$,'EPset_Annotation',$,(#970));
|
||||
#968=IFCTYPEPRODUCT('0IeM1ywXn1qhR_6NhB6N4s',$,'THIN',$,'IfcAnnotation/LINEWORK',(#969),$,$);
|
||||
#969=IFCPROPERTYSET('0t6c$uCeT9092GYkbm8hDS',$,'EPset_Annotation',$,(#970));
|
||||
#970=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('thin'),$);
|
||||
#971=IFCTYPEPRODUCT('00j$y97p903w2HOb35lAQ2',$,'MEDIUM',$,'IfcAnnotation/LINEWORK',(#972),$,$);
|
||||
#972=IFCPROPERTYSET('240rEBDOn8PAvfnm_43s55',$,'EPset_Annotation',$,(#973));
|
||||
#971=IFCTYPEPRODUCT('1jZVbCwrTCGhZdKbH4uqTP',$,'MEDIUM',$,'IfcAnnotation/LINEWORK',(#972),$,$);
|
||||
#972=IFCPROPERTYSET('2uTJ11lF98GeN8pPIf340N',$,'EPset_Annotation',$,(#973));
|
||||
#973=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('medium'),$);
|
||||
#974=IFCTYPEPRODUCT('2tVdFGorj6dfrL4uA1kyW8',$,'THICK',$,'IfcAnnotation/LINEWORK',(#975),$,$);
|
||||
#975=IFCPROPERTYSET('2IEGHncr1D$R8fKA9zCEJP',$,'EPset_Annotation',$,(#976));
|
||||
#974=IFCTYPEPRODUCT('38zW9E1uH2ae$zat9KrieS',$,'THICK',$,'IfcAnnotation/LINEWORK',(#975),$,$);
|
||||
#975=IFCPROPERTYSET('2XQxgO16v9vxjvuIJpaPpG',$,'EPset_Annotation',$,(#976));
|
||||
#976=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('thick'),$);
|
||||
#977=IFCTYPEPRODUCT('1T5C$$ONTBB8A9a7vv0Gn6',$,'STRONG',$,'IfcAnnotation/LINEWORK',(#978),$,$);
|
||||
#978=IFCPROPERTYSET('1gRomAS1L2WguA51ZI0E40',$,'EPset_Annotation',$,(#979));
|
||||
#977=IFCTYPEPRODUCT('3G2s7ZLzfDJh5J$2iIzHw9',$,'STRONG',$,'IfcAnnotation/LINEWORK',(#978),$,$);
|
||||
#978=IFCPROPERTYSET('0TcB8Gal96vebTrLWa5CEw',$,'EPset_Annotation',$,(#979));
|
||||
#979=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('strong'),$);
|
||||
#980=IFCTYPEPRODUCT('2LmKYrAEr2K8EwJyCAsyc4',$,'SETOUT-TAG',$,'IfcAnnotation/TEXT',(#981),(#1000),$);
|
||||
#981=IFCPROPERTYSET('0nF9du8qDAF9aldqjCUsbX',$,'EPset_Annotation',$,(#982));
|
||||
#980=IFCTYPEPRODUCT('3pq2B$0k52ZOv2bFuRYkxO',$,'SETOUT-TAG',$,'IfcAnnotation/TEXT',(#981),(#1000),$);
|
||||
#981=IFCPROPERTYSET('39$oNFI052cBhtpZCVVLfj',$,'EPset_Annotation',$,(#982));
|
||||
#982=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('setout-tag'),$);
|
||||
#983=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#984=IFCDIRECTION((0.,0.,1.));
|
||||
@@ -1005,8 +1005,8 @@ DATA;
|
||||
#998=IFCDIRECTION((0.,0.,1.));
|
||||
#999=IFCAXIS2PLACEMENT3D(#996,#998,#997);
|
||||
#1000=IFCREPRESENTATIONMAP(#999,#995);
|
||||
#1001=IFCTYPEPRODUCT('1cdhVmqEPF6e13_TFOdlQw',$,'DOOR-TAG',$,'IfcAnnotation/TEXT',(#1002),(#1021),$);
|
||||
#1002=IFCPROPERTYSET('1MUPGQolnDEgp0NxcMN56E',$,'EPset_Annotation',$,(#1003));
|
||||
#1001=IFCTYPEPRODUCT('11M3ahhrr9NBdB29YEazzw',$,'DOOR-TAG',$,'IfcAnnotation/TEXT',(#1002),(#1021),$);
|
||||
#1002=IFCPROPERTYSET('3xUNoLKPT9kB4LMdoctm5k',$,'EPset_Annotation',$,(#1003));
|
||||
#1003=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('door-tag'),$);
|
||||
#1004=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#1005=IFCDIRECTION((0.,0.,1.));
|
||||
@@ -1026,8 +1026,8 @@ DATA;
|
||||
#1019=IFCDIRECTION((0.,0.,1.));
|
||||
#1020=IFCAXIS2PLACEMENT3D(#1017,#1019,#1018);
|
||||
#1021=IFCREPRESENTATIONMAP(#1020,#1016);
|
||||
#1022=IFCTYPEPRODUCT('0OuDi3gRH2CxW9mrtE0vXw',$,'WINDOW-TAG',$,'IfcAnnotation/TEXT',(#1023),(#1036),$);
|
||||
#1023=IFCPROPERTYSET('2X7dAo_1P5ruRnlKA4kIl6',$,'EPset_Annotation',$,(#1024));
|
||||
#1022=IFCTYPEPRODUCT('1eE8Y$BVDFDgG8Fj6d9wiV',$,'WINDOW-TAG',$,'IfcAnnotation/TEXT',(#1023),(#1036),$);
|
||||
#1023=IFCPROPERTYSET('2MqzSGkXDBcPSolaAQvYO4',$,'EPset_Annotation',$,(#1024));
|
||||
#1024=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('window-tag'),$);
|
||||
#1025=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#1026=IFCDIRECTION((0.,0.,1.));
|
||||
@@ -1041,8 +1041,8 @@ DATA;
|
||||
#1034=IFCDIRECTION((0.,0.,1.));
|
||||
#1035=IFCAXIS2PLACEMENT3D(#1032,#1034,#1033);
|
||||
#1036=IFCREPRESENTATIONMAP(#1035,#1031);
|
||||
#1037=IFCTYPEPRODUCT('3WEV_9wQn6AQTESgjW36PH',$,'SPACE-TAG',$,'IfcAnnotation/TEXT',(#1038),(#1063),$);
|
||||
#1038=IFCPROPERTYSET('02FAX8dIzAlunWD5ak$6sU',$,'EPset_Annotation',$,(#1039));
|
||||
#1037=IFCTYPEPRODUCT('0vvfHSiaPBxewA$32C4LdW',$,'SPACE-TAG',$,'IfcAnnotation/TEXT',(#1038),(#1063),$);
|
||||
#1038=IFCPROPERTYSET('2zKuFdTPj3DhNdQ0U2kd5u',$,'EPset_Annotation',$,(#1039));
|
||||
#1039=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('space-tag'),$);
|
||||
#1040=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#1041=IFCDIRECTION((0.,0.,1.));
|
||||
@@ -1068,8 +1068,8 @@ DATA;
|
||||
#1061=IFCDIRECTION((0.,0.,1.));
|
||||
#1062=IFCAXIS2PLACEMENT3D(#1059,#1061,#1060);
|
||||
#1063=IFCREPRESENTATIONMAP(#1062,#1058);
|
||||
#1064=IFCTYPEPRODUCT('1evqvQLLL1zxdsIWqEn0lK',$,'MATERIAL-TAG',$,'IfcAnnotation/TEXT',(#1065),(#1078),$);
|
||||
#1065=IFCPROPERTYSET('2KPJlGyer0UBmphfR7952k',$,'EPset_Annotation',$,(#1066));
|
||||
#1064=IFCTYPEPRODUCT('36lTcd9yT8UBNDejQUNWrq',$,'MATERIAL-TAG',$,'IfcAnnotation/TEXT',(#1065),(#1078),$);
|
||||
#1065=IFCPROPERTYSET('1x86VNVk1Drwe5XOj$GZX3',$,'EPset_Annotation',$,(#1066));
|
||||
#1066=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('rectangle-tag'),$);
|
||||
#1067=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#1068=IFCDIRECTION((0.,0.,1.));
|
||||
@@ -1083,8 +1083,8 @@ DATA;
|
||||
#1076=IFCDIRECTION((0.,0.,1.));
|
||||
#1077=IFCAXIS2PLACEMENT3D(#1074,#1076,#1075);
|
||||
#1078=IFCREPRESENTATIONMAP(#1077,#1073);
|
||||
#1079=IFCTYPEPRODUCT('3Nem6d4xX87O3deyWDi3AW',$,'TYPE-TAG',$,'IfcAnnotation/TEXT',(#1080),(#1093),$);
|
||||
#1080=IFCPROPERTYSET('15bspgnA9CEuFox6xFjoTL',$,'EPset_Annotation',$,(#1081));
|
||||
#1079=IFCTYPEPRODUCT('1rxhEX6I16oPqglXaGZ7Sw',$,'TYPE-TAG',$,'IfcAnnotation/TEXT',(#1080),(#1093),$);
|
||||
#1080=IFCPROPERTYSET('3BeqEbDzX0xORamqqSx_kZ',$,'EPset_Annotation',$,(#1081));
|
||||
#1081=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('capsule-tag'),$);
|
||||
#1082=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#1083=IFCDIRECTION((0.,0.,1.));
|
||||
@@ -1098,8 +1098,8 @@ DATA;
|
||||
#1091=IFCDIRECTION((0.,0.,1.));
|
||||
#1092=IFCAXIS2PLACEMENT3D(#1089,#1091,#1090);
|
||||
#1093=IFCREPRESENTATIONMAP(#1092,#1088);
|
||||
#1094=IFCTYPEPRODUCT('0klFX9AjnEnPBkdIURv8XD',$,'NAME-TAG',$,'IfcAnnotation/TEXT',(#1095),(#1108),$);
|
||||
#1095=IFCPROPERTYSET('1d53tifbv2rwcoFFJosiHf',$,'EPset_Annotation',$,(#1096));
|
||||
#1094=IFCTYPEPRODUCT('0sws1hxNb2Og1etXqyoEh1',$,'NAME-TAG',$,'IfcAnnotation/TEXT',(#1095),(#1108),$);
|
||||
#1095=IFCPROPERTYSET('11L$PtdnX8LfYigzRZ$g0a',$,'EPset_Annotation',$,(#1096));
|
||||
#1096=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('capsule-tag'),$);
|
||||
#1097=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#1098=IFCDIRECTION((0.,0.,1.));
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -5,7 +5,7 @@ FILE_NAME('EPset_Drawing.ifc','2020-01-01T00:00:00',(),(),'EPset_Drawing','EPset
|
||||
FILE_SCHEMA(('IFC4'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#1=IFCPROPERTYSETTEMPLATE('2JhNIvqZrFnAgxfhK0XVQX',$,'EPset_Drawing','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation',(#2,#3,#4,#5,#6,#7,#8,#9,#10,#11,#12,#13,#14,#15,#16,#17,#18,#19,#20,#21,#22));
|
||||
#1=IFCPROPERTYSETTEMPLATE('2JhNIvqZrFnAgxfhK0XVQX',$,'EPset_Drawing','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation',(#2,#3,#4,#5,#6,#7,#8,#9,#10,#11,#12,#13,#14,#15,#16,#17,#18,#19,#20,#21,#22,#23));
|
||||
#2=IFCSIMPLEPROPERTYTEMPLATE('23JavTMk98ZxXhrUEnjAcf',$,'TargetView','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#3=IFCSIMPLEPROPERTYTEMPLATE('1yVWUt5H9DAOuu0OaMMLpe',$,'Scale','The scale of this drawing represented as a numerator and denominator, such as 1/100',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#4=IFCSIMPLEPROPERTYTEMPLATE('3gsuPBtU93b8f0gg1pjkq6',$,'HumanScale','The scale of this drawing in human readable format, such as 1:100',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
@@ -27,5 +27,6 @@ DATA;
|
||||
#20=IFCSIMPLEPROPERTYTEMPLATE('0joEq0Rd10cxweEh0NeHT6',$,'JoinCriteria','Comma separated selection keys which determine what cut objects are to be joined.',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#21=IFCSIMPLEPROPERTYTEMPLATE('0nYMT3OSj5gArVniCWZRtv',$,'ShadingStyles','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#22=IFCSIMPLEPROPERTYTEMPLATE('3VWG22eZXBdQwdKlzMeVQH',$,'CurrentShadingStyle','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#23=IFCSIMPLEPROPERTYTEMPLATE('28mJ$GDxb7kBbKFH_rACMQ',$,'AngleDecimalPlaces','',.P_SINGLEVALUE.,'IfcInteger',$,$,$,$,$,.READWRITE.);
|
||||
ENDSEC;
|
||||
END-ISO-10303-21;
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -27,6 +27,7 @@ import addon_utils
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.placement
|
||||
import ifcopenshell.util.unit
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.geometry
|
||||
import blenderbim.core.aggregate
|
||||
|
||||
@@ -216,39 +216,6 @@ def get_enum_items(data, prop_name, context=None):
|
||||
return items
|
||||
|
||||
|
||||
def get_obj_ifc_definition_id(context, obj, obj_type):
|
||||
if obj_type == "Object":
|
||||
return bpy.data.objects.get(obj).BIMObjectProperties.ifc_definition_id
|
||||
elif obj_type == "Material":
|
||||
return bpy.data.materials.get(obj).BIMObjectProperties.ifc_definition_id
|
||||
elif obj_type == "MaterialSet":
|
||||
return ifcopenshell.util.element.get_material(
|
||||
tool.Ifc.get_entity(bpy.data.objects.get(obj)), should_skip_usage=True
|
||||
).id()
|
||||
elif obj_type == "MaterialSetItem":
|
||||
return bpy.data.objects.get(obj).BIMObjectMaterialProperties.active_material_set_item_id
|
||||
elif obj_type == "Task":
|
||||
return context.scene.BIMTaskTreeProperties.tasks[
|
||||
context.scene.BIMWorkScheduleProperties.active_task_index
|
||||
].ifc_definition_id
|
||||
elif obj_type == "Cost":
|
||||
return context.scene.BIMCostProperties.cost_items[
|
||||
context.scene.BIMCostProperties.active_cost_item_index
|
||||
].ifc_definition_id
|
||||
elif obj_type == "Resource":
|
||||
return context.scene.BIMResourceTreeProperties.resources[
|
||||
context.scene.BIMResourceProperties.active_resource_index
|
||||
].ifc_definition_id
|
||||
elif obj_type == "Profile":
|
||||
return context.scene.BIMProfileProperties.profiles[
|
||||
context.scene.BIMProfileProperties.active_profile_index
|
||||
].ifc_definition_id
|
||||
elif obj_type == "WorkSchedule":
|
||||
return context.scene.BIMWorkScheduleProperties.active_work_schedule_id
|
||||
elif obj_type == "Group":
|
||||
prop = context.scene.BIMGroupProperties
|
||||
return prop.groups[prop.active_group_index].ifc_definition_id
|
||||
|
||||
# hack to close popup
|
||||
# https://blender.stackexchange.com/a/202576/130742
|
||||
def close_operator_panel(event):
|
||||
|
||||
@@ -23,33 +23,39 @@ import hashlib
|
||||
import zipfile
|
||||
import tempfile
|
||||
import ifcopenshell
|
||||
import ifcopenshell.geom
|
||||
import ifcopenshell.ifcopenshell_wrapper
|
||||
import blenderbim.bim.handler
|
||||
import blenderbim.tool as tool
|
||||
from pathlib import Path
|
||||
from blenderbim.tool.brick import BrickStore
|
||||
from typing import Set, Union, Optional
|
||||
|
||||
|
||||
IFC_CONNECTED_TYPE = Union[bpy.types.Material, bpy.types.Object]
|
||||
|
||||
|
||||
class IfcStore:
|
||||
path = ""
|
||||
file = None
|
||||
schema = None
|
||||
cache = None
|
||||
cache_path = None
|
||||
id_map = {}
|
||||
guid_map = {}
|
||||
edited_objs = set()
|
||||
pset_template_path = ""
|
||||
pset_template_file = None
|
||||
classification_path = ""
|
||||
classification_file = None
|
||||
library_path = ""
|
||||
library_file = None
|
||||
path: str = ""
|
||||
file: ifcopenshell.file = None
|
||||
schema: ifcopenshell.ifcopenshell_wrapper.schema_definition = None
|
||||
cache: ifcopenshell.ifcopenshell_wrapper.HdfSerializer = None
|
||||
cache_path: str = None
|
||||
id_map: dict[int, IFC_CONNECTED_TYPE] = {}
|
||||
guid_map: dict[str, IFC_CONNECTED_TYPE] = {}
|
||||
edited_objs: Set[bpy.types.Object] = set()
|
||||
pset_template_path: str = ""
|
||||
pset_template_file: ifcopenshell.file = None
|
||||
classification_path: str = ""
|
||||
classification_file: ifcopenshell.file = None
|
||||
library_path: str = ""
|
||||
library_file: ifcopenshell.file = None
|
||||
current_transaction = ""
|
||||
last_transaction = ""
|
||||
history = []
|
||||
future = []
|
||||
schema_identifiers = ["IFC4", "IFC2X3", "IFC4X3"]
|
||||
session_files = {}
|
||||
session_files: dict[str, ifcopenshell.file] = {}
|
||||
|
||||
@staticmethod
|
||||
def purge():
|
||||
@@ -80,8 +86,8 @@ class IfcStore:
|
||||
if IfcStore.path:
|
||||
try:
|
||||
IfcStore.load_file(IfcStore.path)
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
print(f"Failed to load file {IfcStore.path}. Error details: {e}")
|
||||
return IfcStore.file
|
||||
|
||||
@staticmethod
|
||||
@@ -117,7 +123,7 @@ class IfcStore:
|
||||
IfcStore.get_cache()
|
||||
|
||||
@staticmethod
|
||||
def load_file(path):
|
||||
def load_file(path) -> None:
|
||||
if not os.path.isfile(path):
|
||||
return
|
||||
extension = path.split(".")[-1]
|
||||
@@ -146,7 +152,7 @@ class IfcStore:
|
||||
return IfcStore.schema
|
||||
|
||||
@staticmethod
|
||||
def get_element(id_or_guid):
|
||||
def get_element(id_or_guid: Union[int, str]) -> IFC_CONNECTED_TYPE:
|
||||
if isinstance(id_or_guid, int):
|
||||
map_object = IfcStore.id_map
|
||||
else:
|
||||
@@ -159,7 +165,7 @@ class IfcStore:
|
||||
return obj
|
||||
|
||||
@staticmethod
|
||||
def relink_all_objects():
|
||||
def relink_all_objects() -> None:
|
||||
if not IfcStore.get_file():
|
||||
return
|
||||
for obj in bpy.data.objects:
|
||||
@@ -172,7 +178,7 @@ class IfcStore:
|
||||
IfcStore.relink_object(obj)
|
||||
|
||||
@staticmethod
|
||||
def relink_object(obj):
|
||||
def relink_object(obj: IFC_CONNECTED_TYPE) -> None:
|
||||
if not obj:
|
||||
return
|
||||
if obj.BIMObjectProperties.ifc_definition_id:
|
||||
@@ -193,7 +199,7 @@ class IfcStore:
|
||||
IfcStore.commit_link_element(data)
|
||||
|
||||
@staticmethod
|
||||
def link_element(element, obj):
|
||||
def link_element(element: ifcopenshell.entity_instance, obj: IFC_CONNECTED_TYPE) -> None:
|
||||
# Please use tool.Ifc.link() instead of this method. We want to
|
||||
# refactor this class and deprecate usage of IfcStore in favour of
|
||||
# tools.
|
||||
@@ -259,7 +265,7 @@ class IfcStore:
|
||||
# TODO We're handling id_map and guid_map, but what about edited_objs? This might cause big problems.
|
||||
|
||||
@staticmethod
|
||||
def rollback_unlink_element(data):
|
||||
def rollback_unlink_element(data) -> None:
|
||||
if "id" not in data or "obj" not in data:
|
||||
return
|
||||
obj = bpy.data.objects.get(data["obj"])
|
||||
@@ -268,13 +274,15 @@ class IfcStore:
|
||||
IfcStore.guid_map[data["guid"]] = obj
|
||||
|
||||
@staticmethod
|
||||
def commit_unlink_element(data):
|
||||
def commit_unlink_element(data) -> None:
|
||||
del IfcStore.id_map[data["id"]]
|
||||
if data["guid"]:
|
||||
del IfcStore.guid_map[data["guid"]]
|
||||
|
||||
@staticmethod
|
||||
def unlink_element(element=None, obj=None):
|
||||
def unlink_element(
|
||||
element: Optional[ifcopenshell.entity_instance] = None, obj: Optional[IFC_CONNECTED_TYPE] = None
|
||||
) -> None:
|
||||
if element is None:
|
||||
try:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
@@ -320,7 +328,8 @@ class IfcStore:
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def execute_ifc_operator(operator, context, is_invoke=False):
|
||||
def execute_ifc_operator(operator: bpy.types.Operator, context: bpy.types.Context, is_invoke=False):
|
||||
bpy.context.scene.BIMProperties.is_dirty = True
|
||||
is_top_level_operator = not bool(IfcStore.current_transaction)
|
||||
|
||||
if is_top_level_operator:
|
||||
@@ -353,17 +362,17 @@ class IfcStore:
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def begin_transaction(operator):
|
||||
def begin_transaction(operator: bpy.types.Operator) -> None:
|
||||
IfcStore.current_transaction = str(uuid.uuid4())
|
||||
operator.transaction_key = IfcStore.current_transaction
|
||||
|
||||
@staticmethod
|
||||
def end_transaction(operator):
|
||||
def end_transaction(operator: bpy.types.Operator) -> None:
|
||||
IfcStore.current_transaction = ""
|
||||
operator.transaction_key = ""
|
||||
|
||||
@staticmethod
|
||||
def add_transaction_operation(operator, rollback=None, commit=None):
|
||||
def add_transaction_operation(operator: bpy.types.Operator, rollback=None, commit=None) -> None:
|
||||
key = getattr(operator, "transaction_key", None)
|
||||
data = getattr(operator, "transaction_data", None)
|
||||
bpy.context.scene.BIMProperties.last_transaction = key
|
||||
@@ -379,7 +388,7 @@ class IfcStore:
|
||||
IfcStore.future = []
|
||||
|
||||
@staticmethod
|
||||
def undo(until_key=None):
|
||||
def undo(until_key=None) -> None:
|
||||
BrickStore.undo()
|
||||
if not IfcStore.history:
|
||||
return
|
||||
@@ -394,7 +403,7 @@ class IfcStore:
|
||||
IfcStore.future.append(event)
|
||||
|
||||
@staticmethod
|
||||
def redo(until_key=None):
|
||||
def redo(until_key=None) -> None:
|
||||
BrickStore.redo()
|
||||
|
||||
if not IfcStore.future:
|
||||
|
||||
@@ -32,23 +32,28 @@ import ifcopenshell.geom
|
||||
import ifcopenshell.util.unit
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.geolocation
|
||||
import ifcopenshell.util.placement
|
||||
import ifcopenshell.util.representation
|
||||
import blenderbim.tool as tool
|
||||
import ifcopenshell.ifcopenshell_wrapper as ifcopenshell_wrapper
|
||||
from itertools import chain, accumulate
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.ifc import IfcStore, IFC_CONNECTED_TYPE
|
||||
from blenderbim.tool.loader import OBJECT_DATA_TYPE
|
||||
from blenderbim.bim.module.drawing.prop import ANNOTATION_TYPES_DATA
|
||||
from typing import Dict, Union, Optional, Any
|
||||
|
||||
|
||||
class MaterialCreator:
|
||||
def __init__(self, ifc_import_settings: IfcImportSettings, ifc_importer: IfcImporter):
|
||||
self.mesh = None
|
||||
self.obj = None
|
||||
self.materials = {}
|
||||
self.styles = {}
|
||||
self.parsed_meshes = set()
|
||||
self.mesh: bpy.types.Mesh = None
|
||||
self.obj: bpy.types.Object = None
|
||||
self.materials: Dict[int, bpy.types.Material] = {}
|
||||
self.styles: Dict[int, bpy.types.Material] = {}
|
||||
self.parsed_meshes: set[str] = set()
|
||||
self.ifc_import_settings = ifc_import_settings
|
||||
self.ifc_importer = ifc_importer
|
||||
|
||||
def create(self, element, obj, mesh):
|
||||
def create(self, element: ifcopenshell.entity_instance, obj: bpy.types.Object, mesh: bpy.types.Mesh) -> None:
|
||||
self.mesh = mesh
|
||||
# as ifcopenshell triangulates the mesh, we need to merge it to quads again
|
||||
self.obj = obj
|
||||
@@ -64,7 +69,7 @@ class MaterialCreator:
|
||||
self.assign_material_slots_to_faces()
|
||||
tool.Geometry.record_object_materials(obj)
|
||||
|
||||
def add_default_material(self, element):
|
||||
def add_default_material(self, element: ifcopenshell.entity_instance) -> None:
|
||||
element_material = ifcopenshell.util.element.get_material(element)
|
||||
if not element_material:
|
||||
return
|
||||
@@ -82,14 +87,17 @@ class MaterialCreator:
|
||||
self.mesh.materials.append(self.materials[material.id()])
|
||||
return
|
||||
|
||||
def load_existing_materials(self):
|
||||
def load_existing_materials(self) -> None:
|
||||
for material in bpy.data.materials:
|
||||
if material.BIMObjectProperties.ifc_definition_id:
|
||||
self.materials[material.BIMObjectProperties.ifc_definition_id] = material
|
||||
if material.BIMMaterialProperties.ifc_style_id:
|
||||
self.styles[material.BIMMaterialProperties.ifc_style_id] = material
|
||||
|
||||
def parse_representations(self, element):
|
||||
def parse_representations(self, element: ifcopenshell.entity_instance) -> bool:
|
||||
"""Search for styles in in all `element`'s representation items
|
||||
and adds them to `self.mesh.materials`.\n
|
||||
Returns `True` if any styles were found and added, returns `False` otherwise."""
|
||||
has_parsed = False
|
||||
if hasattr(element, "Representation"):
|
||||
for representation in element.Representation.Representations:
|
||||
@@ -103,7 +111,7 @@ class MaterialCreator:
|
||||
has_parsed = True
|
||||
return has_parsed
|
||||
|
||||
def parse_representation(self, representation):
|
||||
def parse_representation(self, representation: ifcopenshell.entity_instance) -> bool:
|
||||
has_parsed = False
|
||||
representation_items = self.resolve_all_stylable_representation_items(representation)
|
||||
for item in representation_items:
|
||||
@@ -111,9 +119,9 @@ class MaterialCreator:
|
||||
has_parsed = True
|
||||
return has_parsed
|
||||
|
||||
def parse_representation_item(self, item):
|
||||
def parse_representation_item(self, item: ifcopenshell.entity_instance) -> bool:
|
||||
if not item.StyledByItem:
|
||||
return
|
||||
return False
|
||||
style_ids = []
|
||||
styles = list(item.StyledByItem[0].Styles)
|
||||
while styles:
|
||||
@@ -123,11 +131,12 @@ class MaterialCreator:
|
||||
elif style.is_a("IfcPresentationStyleAssignment"):
|
||||
styles.extend(style.Styles)
|
||||
if not style_ids:
|
||||
return
|
||||
return False
|
||||
for style_id in style_ids:
|
||||
material = self.styles[style_id]
|
||||
|
||||
def get_ifc_coordinate(material):
|
||||
def get_ifc_coordinate(material: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]:
|
||||
"""returns IfcTextureCoordinate"""
|
||||
texture_style = tool.Style.get_texture_style(material)
|
||||
if not texture_style:
|
||||
return
|
||||
@@ -148,7 +157,7 @@ class MaterialCreator:
|
||||
self.mesh.materials.append(material)
|
||||
return True
|
||||
|
||||
def assign_material_slots_to_faces(self):
|
||||
def assign_material_slots_to_faces(self) -> None:
|
||||
if "ios_materials" not in self.mesh or not self.mesh["ios_materials"]:
|
||||
return
|
||||
if len(self.mesh.materials) == 1:
|
||||
@@ -156,8 +165,12 @@ class MaterialCreator:
|
||||
material_to_slot = {}
|
||||
|
||||
if len(self.mesh.polygons) == len(self.mesh["ios_material_ids"]):
|
||||
for i, material in enumerate(self.mesh["ios_materials"]):
|
||||
slot_index = self.mesh.materials.find(self.styles[material].name)
|
||||
for i, style_or_material_id in enumerate(self.mesh["ios_materials"]):
|
||||
if style_or_material_id in self.styles:
|
||||
blender_material = self.styles[style_or_material_id]
|
||||
else:
|
||||
blender_material = self.materials[style_or_material_id]
|
||||
slot_index = self.mesh.materials.find(blender_material.name)
|
||||
material_to_slot[i] = slot_index
|
||||
|
||||
material_index = [
|
||||
@@ -165,7 +178,10 @@ class MaterialCreator:
|
||||
]
|
||||
self.mesh.polygons.foreach_set("material_index", material_index)
|
||||
|
||||
def resolve_all_stylable_representation_items(self, representation):
|
||||
def resolve_all_stylable_representation_items(
|
||||
self, representation: ifcopenshell.entity_instance
|
||||
) -> list[ifcopenshell.entity_instance]:
|
||||
"""returns list of resolved IfcRepresentationItems"""
|
||||
items = []
|
||||
for item in representation.Items:
|
||||
if item.is_a("IfcMappedItem"):
|
||||
@@ -186,20 +202,27 @@ class IfcImporter:
|
||||
def __init__(self, ifc_import_settings):
|
||||
self.ifc_import_settings = ifc_import_settings
|
||||
self.diff = None
|
||||
self.file = None
|
||||
self.context_settings = []
|
||||
self.file: ifcopenshell.file = None
|
||||
self.context_settings: list[ifcopenshell.geom.main.settings] = []
|
||||
self.gross_context_settings: list[ifcopenshell.geom.main.settings] = []
|
||||
self.contexts = []
|
||||
self.project = None
|
||||
self.has_existing_project = False
|
||||
self.collections = {}
|
||||
self.elements = set()
|
||||
self.type_collection = None
|
||||
# element guids to blender collections mapping
|
||||
self.collections: dict[str, bpy.types.Collection] = {}
|
||||
self.elements: set[ifcopenshell.entity_instance] = set()
|
||||
self.annotations: set[ifcopenshell.entity_instance] = set()
|
||||
self.gross_elements: set[ifcopenshell.entity_instance] = set()
|
||||
self.element_types: set[ifcopenshell.entity_instance] = set()
|
||||
self.spatial_elements: set[ifcopenshell.entity_instance] = set()
|
||||
self.type_collection: bpy.types.Collection = None
|
||||
self.type_products = {}
|
||||
self.meshes = {}
|
||||
self.mesh_shapes = {}
|
||||
self.time = 0
|
||||
self.unit_scale = 1
|
||||
self.added_data = {}
|
||||
# ifc definition ids to blender elements mapping
|
||||
self.added_data: dict[int, IFC_CONNECTED_TYPE] = {}
|
||||
self.native_elements = set()
|
||||
self.native_data = {}
|
||||
self.progress = 0
|
||||
@@ -375,6 +398,15 @@ class IfcImporter:
|
||||
settings.set_context_ids([context.id()])
|
||||
self.context_settings.append(settings)
|
||||
|
||||
settings = ifcopenshell.geom.settings()
|
||||
settings.set_deflection_tolerance(self.ifc_import_settings.deflection_tolerance)
|
||||
settings.set_angular_tolerance(self.ifc_import_settings.angular_tolerance)
|
||||
settings.set(settings.STRICT_TOLERANCE, True)
|
||||
settings.set(settings.INCLUDE_CURVES, True)
|
||||
settings.set(settings.DISABLE_OPENING_SUBTRACTIONS, True)
|
||||
settings.set_context_ids([context.id()])
|
||||
self.gross_context_settings.append(settings)
|
||||
|
||||
def process_element_filter(self):
|
||||
offset = self.ifc_import_settings.element_offset
|
||||
offset_limit = offset + self.ifc_import_settings.element_limit
|
||||
@@ -418,7 +450,20 @@ class IfcImporter:
|
||||
else:
|
||||
self.spatial_elements = set(self.file.by_type("IfcSpatialElement"))
|
||||
|
||||
def get_spatial_elements_filtered_by_elements(self, elements):
|
||||
# Detect excessive voids
|
||||
self.gross_elements = set(
|
||||
filter(lambda e: len(getattr(e, "HasOpenings", [])) > self.ifc_import_settings.void_limit, self.elements)
|
||||
)
|
||||
self.elements = self.elements.difference(self.gross_elements)
|
||||
|
||||
if self.gross_elements:
|
||||
print("Warning! Excessive voids were found and skipped for the following elements:")
|
||||
for element in self.gross_elements:
|
||||
print(element)
|
||||
|
||||
def get_spatial_elements_filtered_by_elements(
|
||||
self, elements: set[ifcopenshell.entity_instance]
|
||||
) -> set[ifcopenshell.entity_instance]:
|
||||
leaf_spatial_elements = set([ifcopenshell.util.element.get_container(e) for e in elements])
|
||||
results = set()
|
||||
for spatial_element in leaf_spatial_elements:
|
||||
@@ -754,7 +799,7 @@ class IfcImporter:
|
||||
continue
|
||||
self.create_element_type(element_type)
|
||||
|
||||
def create_element_type(self, element):
|
||||
def create_element_type(self, element: ifcopenshell.entity_instance) -> None:
|
||||
self.ifc_import_settings.logger.info("Creating object %s", element)
|
||||
mesh = None
|
||||
if self.ifc_import_settings.should_load_geometry:
|
||||
@@ -811,13 +856,19 @@ class IfcImporter:
|
||||
self.create_product(element, mesh=mesh)
|
||||
print("Done creating geometry")
|
||||
|
||||
def create_spatial_elements(self):
|
||||
self.create_generic_elements(self.spatial_elements)
|
||||
def create_spatial_elements(self) -> None:
|
||||
self.create_generic_elements(self.spatial_elements, unselectable=True)
|
||||
|
||||
def create_elements(self):
|
||||
def create_elements(self) -> None:
|
||||
self.create_generic_elements(self.elements)
|
||||
tmp = self.context_settings
|
||||
self.context_settings = self.gross_context_settings
|
||||
self.create_generic_elements(self.gross_elements)
|
||||
self.context_settings = tmp
|
||||
|
||||
def create_generic_shape(self, element):
|
||||
def create_generic_shape(
|
||||
self, element: ifcopenshell.entity_instance
|
||||
) -> Union[ifcopenshell_wrapper.TriangulationElement, None]:
|
||||
for settings in self.context_settings:
|
||||
try:
|
||||
result = ifcopenshell.geom.create_shape(settings, element)
|
||||
@@ -826,7 +877,7 @@ class IfcImporter:
|
||||
except:
|
||||
pass
|
||||
|
||||
def create_generic_elements(self, elements):
|
||||
def create_generic_elements(self, elements: set[ifcopenshell.entity_instance], unselectable=False) -> None:
|
||||
if isinstance(self.file, ifcopenshell.sqlite):
|
||||
return self.create_generic_sqlite_elements(elements)
|
||||
|
||||
@@ -840,12 +891,17 @@ class IfcImporter:
|
||||
elements -= products
|
||||
|
||||
total = len(elements)
|
||||
objects = set()
|
||||
for i, element in enumerate(elements):
|
||||
if i % 250 == 0:
|
||||
print("{} / {} elements processed ...".format(i, total))
|
||||
self.create_product(element)
|
||||
objects.add(self.create_product(element))
|
||||
|
||||
def create_generic_sqlite_elements(self, elements):
|
||||
if unselectable:
|
||||
for obj in objects:
|
||||
obj.hide_select = True
|
||||
|
||||
def create_generic_sqlite_elements(self, elements: set[ifcopenshell.entity_instance]) -> None:
|
||||
self.geometry_cache = self.file.get_geometry([e.id() for e in elements])
|
||||
for geometry_id, geometry in self.geometry_cache["geometry"].items():
|
||||
mesh_name = tool.Loader.get_mesh_name(type("Geometry", (), {"id": geometry_id}))
|
||||
@@ -892,7 +948,9 @@ class IfcImporter:
|
||||
mesh = self.meshes.get(mesh_name)
|
||||
self.create_product(element, mesh=mesh)
|
||||
|
||||
def create_products(self, products, settings=None):
|
||||
def create_products(
|
||||
self, products, settings: Optional[ifcopenshell.geom.main.settings] = None
|
||||
) -> set[ifcopenshell.entity_instance]:
|
||||
results = set()
|
||||
if not products:
|
||||
return results
|
||||
@@ -1015,7 +1073,7 @@ class IfcImporter:
|
||||
return mapped_representation
|
||||
return None
|
||||
|
||||
def create_pointclouds(self, products):
|
||||
def create_pointclouds(self, products: set[ifcopenshell.entity_instance]) -> set[ifcopenshell.entity_instance]:
|
||||
result = set()
|
||||
for product in products:
|
||||
representation = self.get_pointcloud_representation(product)
|
||||
@@ -1026,7 +1084,9 @@ class IfcImporter:
|
||||
|
||||
return result
|
||||
|
||||
def create_pointcloud(self, product, representation):
|
||||
def create_pointcloud(
|
||||
self, product: ifcopenshell.entity_instance, representation: ifcopenshell.entity_instance
|
||||
) -> Union[ifcopenshell.entity_instance, None]:
|
||||
placement_matrix = self.get_element_matrix(product)
|
||||
vertex_list = []
|
||||
for item in representation.Items:
|
||||
@@ -1054,7 +1114,12 @@ class IfcImporter:
|
||||
self.link_element(product, obj)
|
||||
return product
|
||||
|
||||
def create_product(self, element, shape=None, mesh=None):
|
||||
def create_product(
|
||||
self,
|
||||
element: ifcopenshell.entity_instance,
|
||||
shape: Optional[Any] = None,
|
||||
mesh: Optional[OBJECT_DATA_TYPE] = None,
|
||||
) -> Union[bpy.types.Object, None]:
|
||||
if element is None:
|
||||
return
|
||||
|
||||
@@ -1343,7 +1408,7 @@ class IfcImporter:
|
||||
element_type = ifcopenshell.util.element.get_type(element)
|
||||
if not element_type:
|
||||
continue
|
||||
merge_key = str(element_type.id()) + "-" + element_type.Name or "Unnamed"
|
||||
merge_key = str(element_type.id()) + "-" + (element_type.Name or "Unnamed")
|
||||
merge_set.setdefault(merge_key, []).append(obj)
|
||||
id_set.setdefault(merge_key, []).append(ifc_definition_id)
|
||||
self.merge_objects(merge_set, id_set)
|
||||
@@ -1505,18 +1570,19 @@ class IfcImporter:
|
||||
"{}/{}".format(self.project["ifc"].is_a(), self.project["ifc"].Name)
|
||||
)
|
||||
obj = self.create_product(self.project["ifc"])
|
||||
obj.hide_select = True
|
||||
self.project["blender"].objects.link(obj)
|
||||
self.project["blender"].BIMCollectionProperties.obj = obj
|
||||
obj.BIMObjectProperties.collection = self.collections[project.GlobalId] = self.project["blender"]
|
||||
|
||||
def create_collections(self):
|
||||
def create_collections(self) -> None:
|
||||
self.create_spatial_decomposition_collections()
|
||||
if self.ifc_import_settings.collection_mode == "DECOMPOSITION":
|
||||
self.create_aggregate_and_nest_collections()
|
||||
elif self.ifc_import_settings.collection_mode == "SPATIAL_DECOMPOSITION":
|
||||
pass
|
||||
|
||||
def create_spatial_decomposition_collections(self):
|
||||
def create_spatial_decomposition_collections(self) -> None:
|
||||
for rel_aggregate in self.project["ifc"].IsDecomposedBy or []:
|
||||
self.create_spatial_decomposition_collection(self.project["blender"], rel_aggregate.RelatedObjects)
|
||||
|
||||
@@ -1529,7 +1595,9 @@ class IfcImporter:
|
||||
tool.Loader.create_project_collection("Views")
|
||||
self.type_collection = tool.Loader.create_project_collection("Types")
|
||||
|
||||
def create_spatial_decomposition_collection(self, parent, related_objects):
|
||||
def create_spatial_decomposition_collection(
|
||||
self, parent: Union[bpy.types.Collection, bpy.types.Object], related_objects: list[ifcopenshell.entity_instance]
|
||||
) -> None:
|
||||
for element in related_objects:
|
||||
if element not in self.spatial_elements:
|
||||
continue
|
||||
@@ -1556,10 +1624,10 @@ class IfcImporter:
|
||||
rel_aggregates.add(element.IsDecomposedBy[0])
|
||||
elif element.Decomposes:
|
||||
rel_aggregates.add(element.Decomposes[0])
|
||||
elif element.IsNestedBy:
|
||||
elif getattr(element, "IsNestedBy", []): # IFC2X3 does not have IsNestedBy
|
||||
if [e for e in element.IsNestedBy[0].RelatedObjects if not e.is_a("IfcPort")]:
|
||||
rel_aggregates.add(element.IsNestedBy[0])
|
||||
elif element.Nests:
|
||||
elif getattr(element, "Nests", []):
|
||||
rel_aggregates.add(element.Nests[0])
|
||||
else:
|
||||
rel_aggregates = [
|
||||
@@ -1569,7 +1637,11 @@ class IfcImporter:
|
||||
] + [
|
||||
r
|
||||
for r in self.file.by_type("IfcRelNests")
|
||||
if (r.RelatingObject.is_a("IfcElement") or r.RelatingObject.is_a("IfcElementType"))
|
||||
if (
|
||||
r.RelatingObject.is_a("IfcElement")
|
||||
or r.RelatingObject.is_a("IfcElementType")
|
||||
or (r.RelatingObject.is_a("IfcPositioningElement") and not r.RelatingObject.is_a("IfcGrid"))
|
||||
)
|
||||
and [e for e in r.RelatedObjects if not e.is_a("IfcPort")]
|
||||
]
|
||||
|
||||
@@ -1579,9 +1651,9 @@ class IfcImporter:
|
||||
self.ifc_import_settings.collection_mode = "SPATIAL_DECOMPOSITION"
|
||||
return
|
||||
|
||||
aggregates = {}
|
||||
aggregates: dict[str, dict] = {}
|
||||
for rel_aggregate in rel_aggregates:
|
||||
element = rel_aggregate.RelatingObject
|
||||
element: ifcopenshell.entity_instance = rel_aggregate.RelatingObject
|
||||
collection = bpy.data.collections.new(tool.Loader.get_name(element))
|
||||
aggregates[element.GlobalId] = {"element": element, "collection": collection}
|
||||
self.collections[element.GlobalId] = collection
|
||||
@@ -1633,7 +1705,9 @@ class IfcImporter:
|
||||
continue
|
||||
self.create_style(style)
|
||||
|
||||
def create_style(self, style, blender_material=None):
|
||||
def create_style(
|
||||
self, style: ifcopenshell.entity_instance, blender_material: Optional[bpy.types.Material] = None
|
||||
) -> None:
|
||||
if not blender_material:
|
||||
name = style.Name or str(style.id())
|
||||
blender_material = bpy.data.materials.new(name)
|
||||
@@ -1650,18 +1724,20 @@ class IfcImporter:
|
||||
else:
|
||||
blender_material.BIMStyleProperties.active_style_type = "Shading"
|
||||
|
||||
def place_objects_in_collections(self):
|
||||
def place_objects_in_collections(self) -> None:
|
||||
for ifc_definition_id, obj in self.added_data.items():
|
||||
if isinstance(obj, bpy.types.Object):
|
||||
self.place_object_in_collection(self.file.by_id(ifc_definition_id), obj)
|
||||
|
||||
def place_object_in_collection(self, element, obj):
|
||||
def place_object_in_collection(self, element: ifcopenshell.entity_instance, obj: bpy.types.Object) -> None:
|
||||
if self.ifc_import_settings.collection_mode == "DECOMPOSITION":
|
||||
self.place_object_in_decomposition_collection(element, obj)
|
||||
elif self.ifc_import_settings.collection_mode == "SPATIAL_DECOMPOSITION":
|
||||
self.place_object_in_spatial_decomposition_collection(element, obj)
|
||||
|
||||
def place_object_in_decomposition_collection(self, element, obj):
|
||||
def place_object_in_decomposition_collection(
|
||||
self, element: ifcopenshell.entity_instance, obj: bpy.types.Object
|
||||
) -> None:
|
||||
if element.is_a("IfcProject"):
|
||||
return
|
||||
elif element.is_a("IfcGridAxis"):
|
||||
@@ -1678,10 +1754,12 @@ class IfcImporter:
|
||||
elif getattr(element, "Nests", None) and not element.is_a("IfcPort"):
|
||||
nest = ifcopenshell.util.element.get_nest(element)
|
||||
return self.collections[nest.GlobalId].objects.link(obj)
|
||||
else:
|
||||
return self.place_object_in_spatial_decomposition_collection(element, obj)
|
||||
|
||||
def place_object_in_spatial_decomposition_collection(self, element, obj):
|
||||
return self.place_object_in_spatial_decomposition_collection(element, obj)
|
||||
|
||||
def place_object_in_spatial_decomposition_collection(
|
||||
self, element: ifcopenshell.entity_instance, obj: bpy.types.Object
|
||||
) -> None:
|
||||
if element.is_a("IfcProject"):
|
||||
return
|
||||
elif element.is_a("IfcGridAxis"):
|
||||
@@ -1725,7 +1803,7 @@ class IfcImporter:
|
||||
if rel.is_a("IfcRelAssignsToGroup") and rel.RelatingGroup.ObjectType == "DRAWING":
|
||||
return rel.RelatingGroup
|
||||
|
||||
def get_element_matrix(self, element):
|
||||
def get_element_matrix(self, element: ifcopenshell.entity_instance) -> np.array:
|
||||
if isinstance(element, ifcopenshell.sqlite_entity):
|
||||
result = self.geometry_cache["shapes"][element.id()]["matrix"]
|
||||
else:
|
||||
@@ -1735,7 +1813,7 @@ class IfcImporter:
|
||||
result[2][3] *= self.unit_scale
|
||||
return result
|
||||
|
||||
def scale_matrix(self, matrix):
|
||||
def scale_matrix(self, matrix: np.array) -> np.array:
|
||||
matrix[0][3] *= self.unit_scale
|
||||
matrix[1][3] *= self.unit_scale
|
||||
matrix[2][3] *= self.unit_scale
|
||||
@@ -1767,7 +1845,7 @@ class IfcImporter:
|
||||
):
|
||||
return representation.Items[0].MappingTarget
|
||||
|
||||
def create_curve(self, element, shape):
|
||||
def create_curve(self, element: ifcopenshell.entity_instance, shape) -> bpy.types.Curve:
|
||||
if hasattr(shape, "geometry"):
|
||||
geometry = shape.geometry
|
||||
else:
|
||||
@@ -1792,12 +1870,13 @@ class IfcImporter:
|
||||
polyline.points[-1].co = mathutils.Vector(v2)
|
||||
return curve
|
||||
|
||||
def create_mesh(self, element, shape):
|
||||
def create_mesh(self, element: ifcopenshell.entity_instance, shape) -> bpy.types.Mesh:
|
||||
try:
|
||||
if hasattr(shape, "geometry"):
|
||||
geometry = shape.geometry
|
||||
# shape is ifcopenshell_wrapper.TriangulationElement
|
||||
geometry: ifcopenshell_wrapper.Triangulation = shape.geometry
|
||||
else:
|
||||
geometry = shape
|
||||
geometry: ifcopenshell_wrapper.Triangulation = shape
|
||||
|
||||
mesh = bpy.data.meshes.new(tool.Loader.get_mesh_name(geometry))
|
||||
|
||||
@@ -1955,6 +2034,7 @@ class IfcImportSettings:
|
||||
self.is_coordinating = True
|
||||
self.deflection_tolerance = 0.001
|
||||
self.angular_tolerance = 0.5
|
||||
self.void_limit = 30
|
||||
self.distance_limit = 1000
|
||||
self.false_origin = None
|
||||
self.element_offset = 0
|
||||
@@ -1962,7 +2042,7 @@ class IfcImportSettings:
|
||||
self.has_filter = None
|
||||
self.should_filter_spatial_elements = True
|
||||
self.should_setup_viewport_camera = True
|
||||
self.elements = set()
|
||||
self.elements: set[ifcopenshell.entity_instance] = set()
|
||||
self.collection_mode = "DECOMPOSITION"
|
||||
|
||||
@staticmethod
|
||||
@@ -1986,6 +2066,7 @@ class IfcImportSettings:
|
||||
settings.is_coordinating = props.is_coordinating
|
||||
settings.deflection_tolerance = props.deflection_tolerance
|
||||
settings.angular_tolerance = props.angular_tolerance
|
||||
settings.void_limit = props.void_limit
|
||||
settings.distance_limit = props.distance_limit
|
||||
settings.false_origin = [float(o) for o in props.false_origin.split(",")] if props.false_origin else None
|
||||
if settings.false_origin == [0, 0, 0]:
|
||||
|
||||
@@ -28,8 +28,11 @@ classes = (
|
||||
operator.BIM_OT_select_aggregate,
|
||||
operator.BIM_OT_select_parts,
|
||||
operator.BIM_OT_aggregate_unassign_object,
|
||||
operator.BIM_OT_break_link_to_other_aggregates,
|
||||
operator.BIM_OT_select_linked_aggregates,
|
||||
prop.BIMObjectAggregateProperties,
|
||||
ui.BIM_PT_aggregate,
|
||||
ui.BIM_PT_linked_aggregate,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ class AggregateData:
|
||||
"has_related_objects": cls.has_related_objects(),
|
||||
"total_parts": cls.total_parts(),
|
||||
"ifc_class": cls.ifc_class(),
|
||||
"total_linked_aggregate": cls.total_linked_aggregate(),
|
||||
}
|
||||
cls.is_loaded = True
|
||||
|
||||
@@ -72,3 +73,26 @@ class AggregateData:
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
if element:
|
||||
return element.is_a()
|
||||
|
||||
@classmethod
|
||||
def total_linked_aggregate(cls):
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
aggregate = ifcopenshell.util.element.get_aggregate(element)
|
||||
if not aggregate:
|
||||
return []
|
||||
if not element:
|
||||
return []
|
||||
|
||||
product_linked_agg_group = [
|
||||
r
|
||||
for r in getattr(aggregate, "HasAssignments", []) or []
|
||||
if r.is_a("IfcRelAssignsToGroup")
|
||||
if "BBIM_Linked_Aggregate" in r.RelatingGroup.Name
|
||||
]
|
||||
|
||||
if not product_linked_agg_group:
|
||||
return []
|
||||
|
||||
total = len(product_linked_agg_group[0].RelatedObjects)
|
||||
return total
|
||||
|
||||
|
||||
@@ -44,7 +44,12 @@ class BIM_OT_aggregate_assign_object(bpy.types.Operator, Operator):
|
||||
def _execute(self, context):
|
||||
relating_obj = None
|
||||
if self.relating_object:
|
||||
relating_obj = tool.Ifc.get_object(tool.Ifc.get().by_id(self.relating_object))
|
||||
element = tool.Ifc.get().by_id(self.relating_object)
|
||||
if element.IsDecomposedBy:
|
||||
relating_obj = tool.Ifc.get_object(element)
|
||||
else:
|
||||
assembly = element.Decomposes[0].RelatingObject
|
||||
relating_obj = tool.Ifc.get_object(assembly)
|
||||
elif context.active_object:
|
||||
relating_obj = context.active_object
|
||||
if not relating_obj:
|
||||
@@ -89,6 +94,12 @@ class BIM_OT_aggregate_unassign_object(bpy.types.Operator, Operator):
|
||||
relating_obj=tool.Ifc.get_object(aggregate),
|
||||
related_obj=tool.Ifc.get_object(element),
|
||||
)
|
||||
|
||||
# Removes Pset related to Linked Aggregates
|
||||
pset = ifcopenshell.util.element.get_pset(element, 'BBIM_Linked_Aggregate')
|
||||
if pset:
|
||||
pset = tool.Ifc.get().by_id(pset["id"])
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
|
||||
|
||||
|
||||
class BIM_OT_enable_editing_aggregate(bpy.types.Operator, Operator):
|
||||
@@ -230,3 +241,63 @@ class BIM_OT_add_part_to_object(bpy.types.Operator, Operator):
|
||||
part_class=self.part_class,
|
||||
part_name=self.part_name,
|
||||
)
|
||||
|
||||
class BIM_OT_break_link_to_other_aggregates(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.break_link_to_other_aggregates"
|
||||
bl_label = "Break link to other aggregates"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
aggregate = ifcopenshell.util.element.get_aggregate(element)
|
||||
if not aggregate:
|
||||
return []
|
||||
if not element:
|
||||
return []
|
||||
|
||||
parts = ifcopenshell.util.element.get_parts(aggregate)
|
||||
|
||||
for part in parts:
|
||||
pset = ifcopenshell.util.element.get_pset(part, 'BBIM_Linked_Aggregate')
|
||||
pset = tool.Ifc.get().by_id(pset["id"])
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
|
||||
|
||||
linked_aggregate_group = [
|
||||
r.RelatingGroup
|
||||
for r in getattr(aggregate, "HasAssignments", []) or []
|
||||
if r.is_a("IfcRelAssignsToGroup")
|
||||
if "BBIM_Linked_Aggregate" in r.RelatingGroup.Name
|
||||
]
|
||||
tool.Ifc.run("group.unassign_group", group=linked_aggregate_group[0], product=aggregate)
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
class BIM_OT_select_linked_aggregates(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.select_linked_aggregates"
|
||||
bl_label = "Select linked aggregates"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
aggregate = ifcopenshell.util.element.get_aggregate(element)
|
||||
if not aggregate:
|
||||
return []
|
||||
if not element:
|
||||
return []
|
||||
|
||||
linked_aggregate_group = [
|
||||
r.RelatingGroup
|
||||
for r in getattr(aggregate, "HasAssignments", []) or []
|
||||
if r.is_a("IfcRelAssignsToGroup")
|
||||
if "BBIM_Linked_Aggregate" in r.RelatingGroup.Name
|
||||
]
|
||||
|
||||
for obj in context.selected_objects:
|
||||
obj.select_set(False)
|
||||
|
||||
for rel in linked_aggregate_group[0].IsGroupedBy or []:
|
||||
for element in rel.RelatedObjects:
|
||||
obj = tool.Ifc.get_object(element)
|
||||
obj.select_set(True)
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
from bpy.types import Panel
|
||||
from blenderbim.bim.module.aggregate.data import AggregateData
|
||||
from blenderbim.bim.module.group.data import GroupsData, ObjectGroupsData
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
|
||||
@@ -96,3 +97,49 @@ class BIM_PT_aggregate(Panel):
|
||||
op = layout.operator("bim.add_part_to_object", text="Add " + part_class.lstrip("Ifc"))
|
||||
op.part_class = part_class
|
||||
op.obj = context.active_object.name
|
||||
|
||||
|
||||
class BIM_PT_linked_aggregate(Panel):
|
||||
bl_label = "Linked Aggregates"
|
||||
bl_idname = "BIM_PT_linked_aggregate"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
bl_order = 2
|
||||
bl_parent_id = "BIM_PT_aggregate"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if not context.active_object:
|
||||
return False
|
||||
props = context.active_object.BIMObjectProperties
|
||||
if not props.ifc_definition_id:
|
||||
return False
|
||||
if not IfcStore.get_element(props.ifc_definition_id):
|
||||
return False
|
||||
if not IfcStore.get_file().by_id(props.ifc_definition_id).is_a("IfcObjectDefinition"):
|
||||
return False
|
||||
return True
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
if not AggregateData.is_loaded:
|
||||
AggregateData.load()
|
||||
|
||||
props = context.active_object.BIMObjectAggregateProperties
|
||||
row = layout.row(align=True)
|
||||
row.label(text="Advanced Users Only", icon="ERROR")
|
||||
row = layout.row(align=True)
|
||||
|
||||
if type(AggregateData.data['total_linked_aggregate']) is int:
|
||||
if AggregateData.data['total_linked_aggregate'] > 0:
|
||||
row.label(text=f"{AggregateData.data['total_linked_aggregate']} Linked Aggregates")
|
||||
row.operator("bim.select_linked_aggregates", text="", icon="RESTRICT_SELECT_OFF")
|
||||
row.operator("bim.refresh_linked_aggregate", text="", icon="FILE_REFRESH")
|
||||
op = row.operator("bim.break_link_to_other_aggregates", text="", icon="X")
|
||||
else:
|
||||
row.label(text="No Linked Aggregates")
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ from . import ui, operator, prop
|
||||
classes = (
|
||||
operator.AddBoundary,
|
||||
operator.ColourByRelatedBuildingElement,
|
||||
operator.DecorateBoundaries,
|
||||
operator.DisableEditingBoundary,
|
||||
operator.DisableEditingBoundaryGeometry,
|
||||
operator.EditBoundaryAttributes,
|
||||
|
||||
@@ -24,6 +24,7 @@ import mathutils
|
||||
import numpy as np
|
||||
import multiprocessing
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.geom
|
||||
import ifcopenshell.util.unit
|
||||
import ifcopenshell.util.shape
|
||||
import ifcopenshell.util.element
|
||||
@@ -37,6 +38,7 @@ from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.model.decorator import ProfileDecorator
|
||||
from blenderbim.bim.module.boundary.decorator import BoundaryDecorator
|
||||
import blenderbim.core
|
||||
import blenderbim.core.geometry
|
||||
|
||||
|
||||
def get_boundaries_collection(blender_space):
|
||||
@@ -147,7 +149,9 @@ class Loader:
|
||||
self.ifc_importer = import_ifc.IfcImporter(ifc_import_settings)
|
||||
self.ifc_importer.file = self.ifc_file
|
||||
|
||||
def load_boundary(self, boundary, blender_space=None):
|
||||
def load_boundary(
|
||||
self, boundary: ifcopenshell.entity_instance, blender_space: bpy.types.Object = None
|
||||
) -> bpy.types.Object:
|
||||
if not blender_space:
|
||||
if not boundary.RelatingSpace:
|
||||
self.operator.report(
|
||||
@@ -509,10 +513,10 @@ class DisableEditingBoundaryGeometry(bpy.types.Operator, tool.Ifc.Operator):
|
||||
class ShowBoundaries(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.show_boundaries"
|
||||
bl_label = "Show Boundaries"
|
||||
bl_description = "Load selected objects boundaries if they are not loaded"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
props = bpy.context.scene.BIMBoundaryProperties
|
||||
loader = Loader(self)
|
||||
for obj in context.selected_objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
@@ -524,7 +528,7 @@ class ShowBoundaries(bpy.types.Operator, tool.Ifc.Operator):
|
||||
for rel in element.BoundedBy or []:
|
||||
boundary_obj = loader.load_boundary(rel, obj)
|
||||
tool.Boundary.decorate_boundary(boundary_obj)
|
||||
BoundaryDecorator.install(bpy.context)
|
||||
BoundaryDecorator.install(context)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -557,6 +561,36 @@ class HideBoundaries(bpy.types.Operator, tool.Ifc.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DecorateBoundaries(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.decorate_boundaries"
|
||||
bl_label = "Toggle Boundaries Decorations"
|
||||
bl_description = "Add special decorations for all boundaries in the scene or hide them if they are already added"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMBoundaryProperties
|
||||
# filter not decorated boundaries and add decorations for them
|
||||
decorated_boundaries = set([i.obj for i in props.boundaries])
|
||||
active_boundaries = set()
|
||||
for element in tool.Ifc.get().by_type("IfcRelSpaceBoundary"):
|
||||
obj = tool.Ifc.get_object(element)
|
||||
if obj is not None:
|
||||
active_boundaries.add(obj)
|
||||
|
||||
boundaries_to_decorate = active_boundaries - decorated_boundaries
|
||||
if boundaries_to_decorate:
|
||||
for obj in boundaries_to_decorate:
|
||||
tool.Boundary.decorate_boundary(obj)
|
||||
BoundaryDecorator.install(context)
|
||||
else:
|
||||
for obj in decorated_boundaries:
|
||||
tool.Boundary.undecorate_boundary(obj)
|
||||
props.boundaries.clear()
|
||||
BoundaryDecorator.uninstall()
|
||||
tool.Blender.update_viewport()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.add_boundary"
|
||||
bl_label = "Add Boundary"
|
||||
|
||||
@@ -37,7 +37,7 @@ classes = (
|
||||
|
||||
def register():
|
||||
if not bpy.app.background:
|
||||
bpy.utils.register_tool(workspace.CadTool, after={"builtin.transform"}, separator=True, group=True)
|
||||
bpy.utils.register_tool(workspace.CadTool, after={"builtin.transform"}, separator=True, group=False)
|
||||
bpy.types.Scene.BIMCadProperties = bpy.props.PointerProperty(type=prop.BIMCadProperties)
|
||||
|
||||
|
||||
|
||||
@@ -293,7 +293,7 @@ class RemoveClassificationReference(bpy.types.Operator, tool.Ifc.Operator):
|
||||
identification = active_reference[1]
|
||||
|
||||
for obj in objects:
|
||||
ifc_definition_id = blenderbim.bim.helper.get_obj_ifc_definition_id(context, obj, self.obj_type)
|
||||
ifc_definition_id = tool.Blender.get_obj_ifc_definition_id(obj, self.obj_type, context)
|
||||
element = tool.Ifc.get().by_id(ifc_definition_id)
|
||||
references = ifcopenshell.util.classification.get_references(element, should_inherit=False)
|
||||
for reference in references:
|
||||
@@ -358,7 +358,7 @@ class AddClassificationReference(bpy.types.Operator, tool.Ifc.Operator):
|
||||
break
|
||||
|
||||
for obj in objects:
|
||||
ifc_definition_id = blenderbim.bim.helper.get_obj_ifc_definition_id(context, obj, self.obj_type)
|
||||
ifc_definition_id = tool.Blender.get_obj_ifc_definition_id(obj, self.obj_type, context)
|
||||
if not ifc_definition_id:
|
||||
continue
|
||||
ifcopenshell.api.run(
|
||||
@@ -406,7 +406,7 @@ class AddClassificationReferenceFromBSDD(bpy.types.Operator, tool.Ifc.Operator):
|
||||
classification.Location = bsdd_classification.domain_namespace_uri
|
||||
|
||||
for obj in objects:
|
||||
ifc_definition_id = blenderbim.bim.helper.get_obj_ifc_definition_id(context, obj, self.obj_type)
|
||||
ifc_definition_id = tool.Blender.get_obj_ifc_definition_id(obj, self.obj_type, context)
|
||||
if not ifc_definition_id:
|
||||
continue
|
||||
element = tool.Ifc.get().by_id(ifc_definition_id)
|
||||
|
||||
@@ -191,11 +191,11 @@ class CoveringToolUI:
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="FILE_3D")
|
||||
prop_with_search(row, cls.props, "relating_type_id", text="")
|
||||
row.operator("bim.launch_type_manager", icon="LIGHTPROBE_GRID", text="")
|
||||
row.operator("bim.launch_type_manager", icon=tool.Blender.TYPE_MANAGER_ICON, text="")
|
||||
else:
|
||||
row.label(text=f"No {AuthoringData.data['ifc_element_type']} Found", icon="ERROR")
|
||||
row = cls.layout.row()
|
||||
row.operator("bim.launch_type_manager", icon="LIGHTPROBE_GRID", text="Launch Type Manager")
|
||||
row.operator("bim.launch_type_manager", icon=tool.Blender.TYPE_MANAGER_ICON, text="Launch Type Manager")
|
||||
|
||||
@classmethod
|
||||
def draw_basic_bim_tool_interface(cls):
|
||||
|
||||
@@ -28,18 +28,19 @@ classes = (
|
||||
operator.InspectFromStepId,
|
||||
operator.OverrideDisplayType,
|
||||
operator.ParseExpress,
|
||||
operator.PipInstall,
|
||||
operator.PrintIfcFile,
|
||||
operator.PrintObjectPlacement,
|
||||
operator.PrintUnusedElementStats,
|
||||
operator.ProfileImportIFC,
|
||||
operator.PurgeHdf5Cache,
|
||||
operator.PurgeIfcLinks,
|
||||
operator.PurgeUnusedElementsByClass,
|
||||
operator.RewindInspector,
|
||||
operator.SelectExpressFile,
|
||||
operator.SelectHighPolygonMeshes,
|
||||
operator.SelectHighestPolygonMeshes,
|
||||
operator.ValidateIfcFile,
|
||||
operator.PrintUnusedElementStats,
|
||||
operator.PurgeUnusedElementsByClass,
|
||||
prop.BIMDebugProperties,
|
||||
ui.BIM_PT_debug,
|
||||
)
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import sys
|
||||
import bpy
|
||||
import time
|
||||
import logging
|
||||
@@ -623,3 +624,39 @@ class PurgeUnusedElementsByClass(bpy.types.Operator, tool.Ifc.Operator):
|
||||
print("Finished 20 batches. Manually stopping in case of infinite loop.")
|
||||
self.report({"INFO"}, f"Auto purged {total_purged} orphaned elements")
|
||||
tool.Ifc.get().write(self.filepath)
|
||||
|
||||
|
||||
class PipInstall(bpy.types.Operator):
|
||||
bl_idname = "bim.pip_install"
|
||||
bl_label = "Pip Install"
|
||||
bl_description = "Installs a package from PyPI"
|
||||
name: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
import blenderbim.bim
|
||||
|
||||
target = [p for p in sys.path if p.endswith("packages") and "addons" in p][0]
|
||||
py_exec = str(sys.executable)
|
||||
|
||||
print("Detected executable:", py_exec)
|
||||
print("Installing to:", target)
|
||||
|
||||
subprocess.call([py_exec, "-m", "ensurepip", "--user"])
|
||||
subprocess.call([py_exec, "-m", "pip", "install", "--upgrade", "pip"])
|
||||
# Trusted host to make things simpler due to some enterprise network restrictions
|
||||
subprocess.call(
|
||||
[
|
||||
py_exec,
|
||||
"-m",
|
||||
"pip",
|
||||
"install",
|
||||
f"--target={target}",
|
||||
"--upgrade",
|
||||
self.name,
|
||||
"--trusted-host",
|
||||
"pypi.org",
|
||||
"--trusted-host",
|
||||
"files.pythonhosted.org",
|
||||
]
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -51,3 +51,4 @@ class BIMDebugProperties(PropertyGroup):
|
||||
default="BOUNDS",
|
||||
)
|
||||
ifc_class_purge: StringProperty(name="Unused Elements IFC Class", default="")
|
||||
package_name: StringProperty(name="Package Name", default="")
|
||||
|
||||
@@ -44,6 +44,10 @@ class BIM_PT_debug(Panel):
|
||||
row.operator("bim.parse_express", icon="IMPORT", text="")
|
||||
row.operator("bim.select_express_file", icon="FILE_FOLDER", text="")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "package_name", text="")
|
||||
row.operator("bim.pip_install", icon="EVENT_PAGEDOWN").name = props.package_name
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.reload_ifc_file", text="Incrementally Reload Changes")
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import ifcopenshell.util.element
|
||||
import ifcopenshell.util.representation
|
||||
import blenderbim.tool as tool
|
||||
from pathlib import Path
|
||||
from typing import Any, Union
|
||||
|
||||
|
||||
def refresh():
|
||||
@@ -234,9 +235,9 @@ class DecoratorData:
|
||||
cut_cache = {}
|
||||
layerset_cache = {}
|
||||
|
||||
# used by Ifc Annotations with ObjectType = "BATTING"
|
||||
@classmethod
|
||||
def get_batting_thickness(cls, obj):
|
||||
"""used by IfcAnnotations with ObjectType = "BATTING" """
|
||||
result = cls.data.get(obj.name, None)
|
||||
if result is not None:
|
||||
return result
|
||||
@@ -248,9 +249,9 @@ class DecoratorData:
|
||||
cls.data[obj.name] = thickness
|
||||
return thickness
|
||||
|
||||
# used by Ifc Annotations with ObjectType = "SECTION"
|
||||
@classmethod
|
||||
def get_section_markers_display_data(cls, obj):
|
||||
"""used by IfcAnnotations with ObjectType = "SECTION" """
|
||||
result = cls.data.get(obj.name, None)
|
||||
if result is not None:
|
||||
return result
|
||||
@@ -290,10 +291,10 @@ class DecoratorData:
|
||||
cls.data[obj.name] = display_data
|
||||
return display_data
|
||||
|
||||
# used by Ifc Annotations with ObjectType = "TEXT" / "TEXT_LEADER"
|
||||
@classmethod
|
||||
def get_ifc_text_data(cls, obj):
|
||||
"""returns font size in mm for current ifc text object"""
|
||||
def get_ifc_text_data(cls, obj: bpy.types.Object) -> dict[str, Any]:
|
||||
"""used by Ifc Annotations with ObjectType = "TEXT" / "TEXT_LEADER"\n
|
||||
returns font size in mm for current ifc text object"""
|
||||
result = cls.data.get(obj.name, None)
|
||||
if result is not None:
|
||||
return result
|
||||
@@ -317,7 +318,7 @@ class DecoratorData:
|
||||
font_size = FONT_SIZES[font_size_type]
|
||||
|
||||
# get symbol
|
||||
symbol = pset_data.get("Symbol", None)
|
||||
symbol = tool.Drawing.get_annotation_symbol(element)
|
||||
|
||||
# other attributes
|
||||
props_literals = props.literals
|
||||
@@ -340,6 +341,11 @@ class DecoratorData:
|
||||
cls.data[obj.name] = text_data
|
||||
return text_data
|
||||
|
||||
@classmethod
|
||||
def get_symbol(cls, obj: bpy.types.Object) -> Union[str, None]:
|
||||
"""used by IfcAnnotations with ObjectType MULTI_SYMBOL"""
|
||||
return tool.Drawing.get_annotation_symbol(tool.Ifc.get_entity(obj))
|
||||
|
||||
@classmethod
|
||||
def get_dimension_data(cls, obj):
|
||||
"""used by Ifc Annotations with ObjectType:
|
||||
@@ -412,10 +418,12 @@ class AnnotationData:
|
||||
relating_types = []
|
||||
for relating_type in tool.Ifc.get().by_type("IfcTypeProduct"):
|
||||
if tool.Drawing.is_annotation_object_type(relating_type, object_type):
|
||||
relating_types.append({
|
||||
"id": relating_type.id(),
|
||||
"name": relating_type.Name or "Unnamed",
|
||||
"description": relating_type.Description or "No Description",
|
||||
})
|
||||
relating_types.append(
|
||||
{
|
||||
"id": relating_type.id(),
|
||||
"name": relating_type.Name or "Unnamed",
|
||||
"description": relating_type.Description or "No Description",
|
||||
}
|
||||
)
|
||||
|
||||
return sorted(relating_types, key=lambda x: x["name"])
|
||||
|
||||
@@ -37,6 +37,7 @@ from blenderbim.bim.module.drawing.shaders import add_verts_sequence, add_offset
|
||||
from blenderbim.bim.module.drawing.helper import format_distance
|
||||
from timeit import default_timer as timer
|
||||
from functools import lru_cache
|
||||
from typing import Optional, Iterator, Type, Union
|
||||
|
||||
UNSPECIAL_ELEMENT_COLOR = (0.2, 0.2, 0.2, 1) # GREY
|
||||
|
||||
@@ -69,7 +70,7 @@ class profile_consequential:
|
||||
cls.lines = []
|
||||
|
||||
|
||||
def worldspace_to_winspace(verts, context):
|
||||
def worldspace_to_winspace(verts: list[Vector], context: bpy.types.Context) -> list[Vector]:
|
||||
"""Convert world space verts to window space"""
|
||||
region = context.region
|
||||
region3d = context.region_data
|
||||
@@ -80,7 +81,7 @@ def worldspace_to_winspace(verts, context):
|
||||
return winspace_verts
|
||||
|
||||
|
||||
def winspace_to_worldspace(verts, context):
|
||||
def winspace_to_worldspace(verts: list[Vector], context: bpy.types.Context) -> list[Vector]:
|
||||
"""Convert winspace verts to world space"""
|
||||
region = context.region
|
||||
region3d = context.region_data
|
||||
@@ -121,17 +122,18 @@ def get_callout_head(edge_dir, edge_side, callout_size, callout_gap):
|
||||
return head
|
||||
|
||||
|
||||
def get_circle_head(size, segments=20):
|
||||
def get_circle_head(size: float, segments: int = 20, rotation: float = 0.0) -> list[Vector]:
|
||||
"""`rotation` value in radians"""
|
||||
angle_d = 2 * pi / segments
|
||||
head = []
|
||||
for i in range(segments):
|
||||
angle = angle_d * i
|
||||
angle = angle_d * i + rotation
|
||||
head.append(Vector([cos(angle), sin(angle), 0]) * size)
|
||||
return head
|
||||
|
||||
|
||||
def get_circle_head_asterisk(size, segments=6):
|
||||
circle_head = get_circle_head(size, segments)
|
||||
def get_circle_head_asterisk(size: float, segments: int = 6, rotation: float = 0.0) -> Iterator[tuple[Vector, Vector]]:
|
||||
circle_head = get_circle_head(size, segments, rotation)
|
||||
middle = segments // 2
|
||||
return zip(circle_head[:middle], circle_head[middle:])
|
||||
|
||||
@@ -264,7 +266,7 @@ class BaseDecorator:
|
||||
vertices = [obj.matrix_world @ v.co for v in bm.verts]
|
||||
return vertices, indices
|
||||
|
||||
def decorate(self, context, obj):
|
||||
def decorate(self, context: bpy.types.Context, obj: bpy.types.Object):
|
||||
"""perform actual drawing stuff"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@@ -310,13 +312,28 @@ class BaseDecorator:
|
||||
|
||||
self.draw_lines(context, obj, output_verts, output_edges)
|
||||
|
||||
def draw_batch(self, shader_type, content_pos, color, indices=None):
|
||||
def draw_batch(
|
||||
self,
|
||||
shader_type: str,
|
||||
content_pos: list[Vector],
|
||||
color: tuple[float, float, float, float],
|
||||
indices: Optional[list[tuple[int, int]]] = None,
|
||||
) -> None:
|
||||
shader = self.line_shader if shader_type == "LINES" else self.base_shader
|
||||
batch = batch_for_shader(shader, shader_type, {"pos": content_pos}, indices=indices)
|
||||
shader.uniform_float("color", color)
|
||||
batch.draw(shader)
|
||||
|
||||
def draw_lines(self, context, obj, vertices, indices, color=None, line_width=1.0):
|
||||
def draw_lines(
|
||||
self,
|
||||
context: bpy.types.Context,
|
||||
obj: Optional[bpy.types.Object],
|
||||
vertices: list[Vector],
|
||||
indices: list[tuple[int, int]],
|
||||
color: Optional[tuple[float, float, float, float]] = None,
|
||||
line_width: float = 1.0,
|
||||
) -> None:
|
||||
# TODO: `obj` is actually never used, need to remove it
|
||||
"""`verts` should be in winspace with `(0,0,0)` in the screen left bottom corner, not in the center"""
|
||||
region = context.region
|
||||
if not color:
|
||||
@@ -329,7 +346,7 @@ class BaseDecorator:
|
||||
gpu.state.blend_set("ALPHA")
|
||||
self.draw_batch("LINES", vertices, color, indices)
|
||||
|
||||
def get_viewport_drawing_scale(self, context):
|
||||
def get_viewport_drawing_scale(self, context: bpy.types.Context) -> float:
|
||||
# Horrific prototype code
|
||||
factor = self.camera_zoom_to_factor(context.space_data.region_3d.view_camera_zoom)
|
||||
camera_width_px = factor * context.region.width
|
||||
@@ -485,14 +502,16 @@ class BaseDecorator:
|
||||
decimal_places = drawing_pset_data.get("DecimalPlaces", None)
|
||||
return format_distance(value, precision=precision, decimal_places=decimal_places)
|
||||
|
||||
def draw_asterisk(self, context, obj):
|
||||
def draw_asterisk(self, context: bpy.types.Context, pos: Vector, rotation: float = 0.0, scale: float = 1.0) -> None:
|
||||
"""`pos` is a world space position\n
|
||||
`rotation` value in radians"""
|
||||
# gather geometry data and convert to winspace
|
||||
verts = [obj.location]
|
||||
verts = [pos]
|
||||
viewportDrawingScale = self.get_viewport_drawing_scale(context)
|
||||
winspace_verts = worldspace_to_winspace(verts, context)
|
||||
|
||||
# setup geometry parameters
|
||||
circle_size = viewportDrawingScale * 4
|
||||
circle_size = viewportDrawingScale * 4 * scale
|
||||
|
||||
output_verts = []
|
||||
output_edges = []
|
||||
@@ -502,20 +521,13 @@ class BaseDecorator:
|
||||
}
|
||||
|
||||
v0 = winspace_verts[0]
|
||||
asterisk_head = get_circle_head_asterisk(circle_size)
|
||||
asterisk_head = get_circle_head_asterisk(circle_size, rotation=rotation)
|
||||
start_i = 0
|
||||
for segment in asterisk_head:
|
||||
start_i = add_verts_sequence(add_offsets(v0, segment), start_i, **out_kwargs)
|
||||
self.draw_lines(context, obj, output_verts, output_edges)
|
||||
self.draw_lines(context, None, output_verts, output_edges)
|
||||
|
||||
def draw_text(self, context, obj, text_world_position=None, reverse_lines_order=False):
|
||||
"""if `text_world_position` is not provided, the object's location will be used"""
|
||||
|
||||
if not text_world_position:
|
||||
text_world_position = obj.location
|
||||
|
||||
region = context.region
|
||||
region3d = context.region_data
|
||||
def get_annotation_direction(self, context: bpy.types.Context, obj: bpy.types.Object) -> Vector:
|
||||
camera = context.scene.camera
|
||||
|
||||
def get_basis_vector(matrix, i=0):
|
||||
@@ -523,10 +535,57 @@ class BaseDecorator:
|
||||
return matrix.inverted()[i].to_3d().normalized()
|
||||
|
||||
text_dir_world_x_axis = get_basis_vector(obj.matrix_world)
|
||||
|
||||
camera_matrix = tool.Drawing.get_camera_matrix(camera)
|
||||
|
||||
text_dir = (camera_matrix.inverted().to_quaternion() @ text_dir_world_x_axis).to_2d().normalized()
|
||||
return text_dir
|
||||
|
||||
def draw_symbol(self, context: bpy.types.Context, obj: bpy.types.Object, annotation_dir: Vector) -> None:
|
||||
if obj.type not in ("MESH", "EMPTY"):
|
||||
return
|
||||
|
||||
if obj.type == "MESH":
|
||||
mesh: bpy.types.Mesh = obj.data
|
||||
|
||||
if len(mesh.vertices) == 0:
|
||||
return
|
||||
|
||||
if mesh.edges:
|
||||
MiscDecorator.decorate(self, context, obj)
|
||||
return
|
||||
|
||||
symbol = DecoratorData.get_symbol(obj)
|
||||
if not symbol:
|
||||
return
|
||||
|
||||
for vert in mesh.vertices:
|
||||
self.draw_asterisk(context, obj.matrix_world @ vert.co)
|
||||
|
||||
return
|
||||
|
||||
# EMPTY objects
|
||||
symbol = DecoratorData.get_symbol(obj)
|
||||
if not symbol:
|
||||
return
|
||||
|
||||
rotation = -Vector((1, 0)).angle_signed(annotation_dir)
|
||||
# NOTE: for now we assume that scale is uniform
|
||||
self.draw_asterisk(context, obj.location, rotation, obj.scale.x)
|
||||
|
||||
def draw_text(
|
||||
self,
|
||||
context: bpy.types.Context,
|
||||
obj: bpy.types.Object,
|
||||
text_world_position: Optional[Vector] = None,
|
||||
reverse_lines_order=False,
|
||||
) -> None:
|
||||
"""if `text_world_position` is not provided, the object's location will be used"""
|
||||
|
||||
if not text_world_position:
|
||||
text_world_position = obj.location
|
||||
|
||||
region = context.region
|
||||
region3d = context.region_data
|
||||
text_dir = self.get_annotation_direction(context, obj)
|
||||
|
||||
pos = location_3d_to_region_2d(region, region3d, text_world_position)
|
||||
props = obj.BIMTextProperties
|
||||
@@ -539,8 +598,7 @@ class BaseDecorator:
|
||||
|
||||
# draw asterisk symbol to indicate that there is some symbol that's not shown in viewport
|
||||
if symbol:
|
||||
self.draw_asterisk(context, obj)
|
||||
# NOTE: for now we assume that scale is uniform
|
||||
self.draw_symbol(context, obj, text_dir)
|
||||
text_scale = obj.scale.x
|
||||
|
||||
line_i = 0
|
||||
@@ -812,8 +870,9 @@ class AngleDecorator(BaseDecorator):
|
||||
base_edge = edge0 if tool.Cad.is_counter_clockwise_order(p0, p1, p2) else edge1
|
||||
text_offset = (Matrix.Rotation(-angle_rad / 2, 2) @ base_edge).normalized() * (radius + ANGLE_LABEL_OFFSET)
|
||||
label_position = p1 + text_offset
|
||||
|
||||
text = f"{int(angle)}deg"
|
||||
drawing_pset_data = DrawingsData.data["active_drawing_pset_data"]
|
||||
decimal_places = drawing_pset_data.get("AngleDecimalPlaces", None)
|
||||
text = f"{round(angle, decimal_places)}°"
|
||||
label_dir = Vector((1, 0))
|
||||
self.draw_label(context, text, label_position, label_dir, box_alignment="center")
|
||||
|
||||
@@ -1003,7 +1062,7 @@ class StairDecorator(BaseDecorator):
|
||||
class MiscDecorator(BaseDecorator):
|
||||
objecttype = "MISC"
|
||||
|
||||
def decorate(self, context, obj):
|
||||
def decorate(self, context: bpy.types.Context, obj: bpy.types.Object):
|
||||
if obj.data.is_editmode:
|
||||
verts, idxs = self.get_editmesh_geom(obj)
|
||||
else:
|
||||
@@ -1505,6 +1564,14 @@ class TextDecorator(BaseDecorator):
|
||||
self.draw_text(context, obj)
|
||||
|
||||
|
||||
class SymbolDecorator(BaseDecorator):
|
||||
objecttype = "SYMBOL"
|
||||
|
||||
def decorate(self, context: bpy.types.Context, obj: bpy.types.Object) -> None:
|
||||
annotation_dir = self.get_annotation_direction(context, obj)
|
||||
self.draw_symbol(context, obj, annotation_dir)
|
||||
|
||||
|
||||
class CutDecorator:
|
||||
installed = None
|
||||
cache = {}
|
||||
@@ -1901,7 +1968,7 @@ class CutDecorator:
|
||||
|
||||
|
||||
class DecorationsHandler:
|
||||
decorators_classes = [
|
||||
decorators_classes: list[Type[BaseDecorator]] = [
|
||||
DimensionDecorator,
|
||||
AngleDecorator,
|
||||
GridDecorator,
|
||||
@@ -1918,6 +1985,7 @@ class DecorationsHandler:
|
||||
SectionDecorator,
|
||||
ElevationDecorator,
|
||||
TextDecorator,
|
||||
SymbolDecorator,
|
||||
BattingDecorator,
|
||||
FallDecorator,
|
||||
]
|
||||
@@ -1945,6 +2013,7 @@ class DecorationsHandler:
|
||||
self.decorators = {cls.objecttype: cls() for cls in self.decorators_classes}
|
||||
for object_type in ("SLOPE_ANGLE", "SLOPE_FRACTION", "SLOPE_PERCENT"):
|
||||
self.decorators[object_type] = self.decorators["FALL"]
|
||||
self.decorators["MULTI_SYMBOL"] = self.decorators["SYMBOL"]
|
||||
|
||||
def get_objects_and_decorators(self, collection):
|
||||
# TODO: do it in data instead of the handler for performance?
|
||||
@@ -1962,7 +2031,7 @@ class DecorationsHandler:
|
||||
if not element.is_a("IfcAnnotation"):
|
||||
continue
|
||||
|
||||
object_type = element.ObjectType
|
||||
object_type: Union[str, None] = element.ObjectType
|
||||
if object_type == "DRAWING":
|
||||
continue
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ import blenderbim.bim.module.drawing.helper as helper
|
||||
import blenderbim.bim.export_ifc
|
||||
from blenderbim.bim.module.drawing.decoration import CutDecorator
|
||||
from blenderbim.bim.module.drawing.data import DecoratorData, DrawingsData
|
||||
from typing import NamedTuple, List
|
||||
from lxml import etree
|
||||
from mathutils import Vector, Color, Matrix
|
||||
from timeit import default_timer as timer
|
||||
@@ -77,6 +78,11 @@ class Operator:
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class LineworkContexts(NamedTuple):
|
||||
body: List[List[int]]
|
||||
annotation: List[List[int]]
|
||||
|
||||
|
||||
class AddAnnotationType(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.add_annotation_type"
|
||||
bl_label = "Add Annotation Type"
|
||||
@@ -367,6 +373,105 @@ class CreateDrawing(bpy.types.Operator):
|
||||
self.svg_writer.create_blank_svg(svg_path).draw_underlay(context.scene.render.filepath).save()
|
||||
return svg_path
|
||||
|
||||
def get_linework_contexts(self, ifc, target_view) -> LineworkContexts:
|
||||
plan_body_target_contexts = []
|
||||
plan_body_model_contexts = []
|
||||
model_body_target_contexts = []
|
||||
model_body_model_contexts = []
|
||||
|
||||
plan_annotation_target_contexts = []
|
||||
plan_annotation_model_contexts = []
|
||||
model_annotation_target_contexts = []
|
||||
model_annotation_model_contexts = []
|
||||
|
||||
for rep_context in ifc.by_type("IfcGeometricRepresentationContext"):
|
||||
if rep_context.is_a("IfcGeometricRepresentationSubContext"):
|
||||
if rep_context.ContextType == "Plan":
|
||||
if rep_context.ContextIdentifier in ["Body", "Facetation"]:
|
||||
if rep_context.TargetView == target_view:
|
||||
plan_body_target_contexts.append(rep_context.id())
|
||||
elif rep_context.TargetView == "MODEL_VIEW":
|
||||
plan_body_model_contexts.append(rep_context.id())
|
||||
elif rep_context.ContextIdentifier == "Annotation":
|
||||
if rep_context.TargetView == target_view:
|
||||
plan_annotation_target_contexts.append(rep_context.id())
|
||||
elif rep_context.TargetView == "MODEL_VIEW":
|
||||
plan_annotation_model_contexts.append(rep_context.id())
|
||||
elif rep_context.ContextType == "Model":
|
||||
if rep_context.ContextIdentifier in ["Body", "Facetation"]:
|
||||
if rep_context.TargetView == target_view:
|
||||
model_body_target_contexts.append(rep_context.id())
|
||||
elif rep_context.TargetView == "MODEL_VIEW":
|
||||
model_body_model_contexts.append(rep_context.id())
|
||||
elif rep_context.ContextIdentifier == "Annotation":
|
||||
if rep_context.TargetView == target_view:
|
||||
model_annotation_target_contexts.append(rep_context.id())
|
||||
elif rep_context.TargetView == "MODEL_VIEW":
|
||||
model_annotation_model_contexts.append(rep_context.id())
|
||||
elif rep_context.ContextType == "Model":
|
||||
# You should never purely assign to a "Model" context, but
|
||||
# if you do, this is what we assume your intention is.
|
||||
model_body_model_contexts.append(rep_context.id())
|
||||
continue
|
||||
|
||||
body_contexts = (
|
||||
[
|
||||
plan_body_target_contexts,
|
||||
plan_body_model_contexts,
|
||||
model_body_target_contexts,
|
||||
model_body_model_contexts,
|
||||
]
|
||||
if target_view in ["PLAN_VIEW", "REFLECTED_PLAN_VIEW"]
|
||||
else [
|
||||
model_body_target_contexts,
|
||||
model_body_model_contexts,
|
||||
]
|
||||
)
|
||||
|
||||
annotation_contexts = (
|
||||
[
|
||||
plan_annotation_target_contexts,
|
||||
plan_annotation_model_contexts,
|
||||
model_annotation_target_contexts,
|
||||
model_annotation_model_contexts,
|
||||
]
|
||||
if target_view in ["PLAN_VIEW", "REFLECTED_PLAN_VIEW"]
|
||||
else [
|
||||
model_annotation_target_contexts,
|
||||
model_annotation_model_contexts,
|
||||
]
|
||||
)
|
||||
|
||||
return LineworkContexts(body_contexts, annotation_contexts)
|
||||
|
||||
def serialize_contexts_elements(
|
||||
self, ifc, tree: ifcopenshell.geom.tree, contexts: LineworkContexts, context_type, drawing_elements, target_view
|
||||
):
|
||||
drawing_elements = drawing_elements.copy()
|
||||
contexts = getattr(contexts, context_type)
|
||||
for context in contexts:
|
||||
with profile(f"Processing {context_type} context"):
|
||||
if not context or not drawing_elements:
|
||||
continue
|
||||
geom_settings = ifcopenshell.geom.settings(
|
||||
DISABLE_TRIANGULATION=True, STRICT_TOLERANCE=True, INCLUDE_CURVES=True
|
||||
)
|
||||
if ifc.by_id(context[0]).ContextType == "Plan" and "PLAN_VIEW" in target_view:
|
||||
offset = ifcopenshell.ifcopenshell_wrapper.float_array_3()
|
||||
# A 2mm Z offset to combat Z-fighting in plan or RCPs
|
||||
offset[2] = 0.002 if target_view == "PLAN_VIEW" else -0.002
|
||||
geom_settings.offset = offset
|
||||
geom_settings.set_context_ids(context)
|
||||
it = ifcopenshell.geom.iterator(
|
||||
geom_settings, ifc, multiprocessing.cpu_count(), include=drawing_elements
|
||||
)
|
||||
processed = set()
|
||||
for elem in it:
|
||||
processed.add(ifc.by_id(elem.id))
|
||||
self.serialiser.write(elem)
|
||||
tree.add_element(elem)
|
||||
drawing_elements -= processed
|
||||
|
||||
def generate_linework(self, context):
|
||||
if not ifcopenshell.util.element.get_pset(self.drawing, "EPset_Drawing", "HasLinework"):
|
||||
return
|
||||
@@ -408,77 +513,8 @@ class CreateDrawing(bpy.types.Operator):
|
||||
# Drawings only draw bodies and annotations (and facetation, due to a Revit bug).
|
||||
# A drawing prioritises a target view context first, followed by a model view context as a fallback.
|
||||
# Specifically for PLAN_VIEW and REFLECTED_PLAN_VIEW, any Plan context is also prioritised.
|
||||
|
||||
plan_body_target_contexts = []
|
||||
plan_body_model_contexts = []
|
||||
model_body_target_contexts = []
|
||||
model_body_model_contexts = []
|
||||
|
||||
plan_annotation_target_contexts = []
|
||||
plan_annotation_model_contexts = []
|
||||
model_annotation_target_contexts = []
|
||||
model_annotation_model_contexts = []
|
||||
|
||||
target_view = ifcopenshell.util.element.get_psets(self.camera_element)["EPset_Drawing"]["TargetView"]
|
||||
|
||||
for rep_context in ifc.by_type("IfcGeometricRepresentationContext"):
|
||||
if rep_context.is_a("IfcGeometricRepresentationSubContext"):
|
||||
if rep_context.ContextType == "Plan":
|
||||
if rep_context.ContextIdentifier in ["Body", "Facetation"]:
|
||||
if rep_context.TargetView == target_view:
|
||||
plan_body_target_contexts.append(rep_context.id())
|
||||
elif rep_context.TargetView == "MODEL_VIEW":
|
||||
plan_body_model_contexts.append(rep_context.id())
|
||||
elif rep_context.ContextIdentifier == "Annotation":
|
||||
if rep_context.TargetView == target_view:
|
||||
plan_annotation_target_contexts.append(rep_context.id())
|
||||
elif rep_context.TargetView == "MODEL_VIEW":
|
||||
plan_annotation_model_contexts.append(rep_context.id())
|
||||
elif rep_context.ContextType == "Model":
|
||||
if rep_context.ContextIdentifier in ["Body", "Facetation"]:
|
||||
if rep_context.TargetView == target_view:
|
||||
model_body_target_contexts.append(rep_context.id())
|
||||
elif rep_context.TargetView == "MODEL_VIEW":
|
||||
model_body_model_contexts.append(rep_context.id())
|
||||
elif rep_context.ContextIdentifier == "Annotation":
|
||||
if rep_context.TargetView == target_view:
|
||||
model_annotation_target_contexts.append(rep_context.id())
|
||||
elif rep_context.TargetView == "MODEL_VIEW":
|
||||
model_annotation_model_contexts.append(rep_context.id())
|
||||
elif rep_context.ContextType == "Model":
|
||||
# You should never purely assign to a "Model" context, but
|
||||
# if you do, this is what we assume your intention is.
|
||||
model_body_model_contexts.append(rep_context.id())
|
||||
continue
|
||||
|
||||
body_contexts = (
|
||||
[
|
||||
plan_body_target_contexts,
|
||||
plan_body_model_contexts,
|
||||
model_body_target_contexts,
|
||||
model_body_model_contexts,
|
||||
]
|
||||
if target_view in ["PLAN_VIEW", "REFLECTED_PLAN_VIEW"]
|
||||
else [
|
||||
model_body_target_contexts,
|
||||
model_body_model_contexts,
|
||||
]
|
||||
)
|
||||
|
||||
annotation_contexts = (
|
||||
[
|
||||
plan_annotation_target_contexts,
|
||||
plan_annotation_model_contexts,
|
||||
model_annotation_target_contexts,
|
||||
model_annotation_model_contexts,
|
||||
]
|
||||
if target_view in ["PLAN_VIEW", "REFLECTED_PLAN_VIEW"]
|
||||
else [
|
||||
model_annotation_target_contexts,
|
||||
model_annotation_model_contexts,
|
||||
]
|
||||
)
|
||||
|
||||
contexts = self.get_linework_contexts(ifc, target_view)
|
||||
drawing_elements = tool.Drawing.get_drawing_elements(self.camera_element)
|
||||
|
||||
self.setup_serialiser(ifc, target_view)
|
||||
@@ -487,60 +523,15 @@ class CreateDrawing(bpy.types.Operator):
|
||||
tree = ifcopenshell.geom.tree()
|
||||
tree.enable_face_styles(True)
|
||||
|
||||
elements = drawing_elements.copy()
|
||||
for body_context in body_contexts:
|
||||
with profile("Processing body context"):
|
||||
if body_context and elements:
|
||||
geom_settings = ifcopenshell.geom.settings(
|
||||
DISABLE_TRIANGULATION=True, STRICT_TOLERANCE=True, INCLUDE_CURVES=True
|
||||
)
|
||||
if ifc.by_id(body_context[0]).ContextType == "Plan" and "PLAN_VIEW" in target_view:
|
||||
offset = ifcopenshell.ifcopenshell_wrapper.float_array_3()
|
||||
# A 2mm Z offset to combat Z-fighting in plan or RCPs
|
||||
offset[2] = 0.002 if target_view == "PLAN_VIEW" else -0.002
|
||||
geom_settings.offset = offset
|
||||
geom_settings.set_context_ids(body_context)
|
||||
it = ifcopenshell.geom.iterator(
|
||||
geom_settings, ifc, multiprocessing.cpu_count(), include=elements
|
||||
)
|
||||
it.set_cache(cache)
|
||||
processed = set()
|
||||
for elem in self.yield_from_iterator(it):
|
||||
processed.add(ifc.by_id(elem.id))
|
||||
self.serialiser.write(elem)
|
||||
tree.add_element(elem)
|
||||
elements -= processed
|
||||
|
||||
elements = drawing_elements.copy()
|
||||
for annotation_context in annotation_contexts:
|
||||
with profile("Processing annotation context"):
|
||||
if annotation_context and elements:
|
||||
geom_settings = ifcopenshell.geom.settings(
|
||||
DISABLE_TRIANGULATION=True, STRICT_TOLERANCE=True, INCLUDE_CURVES=True
|
||||
)
|
||||
if ifc.by_id(annotation_context[0]).ContextType == "Plan" and "PLAN_VIEW" in target_view:
|
||||
offset = ifcopenshell.ifcopenshell_wrapper.float_array_3()
|
||||
# A 2mm Z offset to combat Z-fighting in plan or RCPs
|
||||
offset[2] = 0.002 if target_view == "PLAN_VIEW" else -0.002
|
||||
geom_settings.offset = offset
|
||||
geom_settings.set_context_ids(annotation_context)
|
||||
it = ifcopenshell.geom.iterator(
|
||||
geom_settings, ifc, multiprocessing.cpu_count(), include=elements
|
||||
)
|
||||
it.set_cache(cache)
|
||||
processed = set()
|
||||
for elem in self.yield_from_iterator(it):
|
||||
processed.add(ifc.by_id(elem.id))
|
||||
self.serialiser.write(elem)
|
||||
tree.add_element(elem)
|
||||
elements -= processed
|
||||
self.serialize_contexts_elements(ifc, tree, contexts, "body", drawing_elements, target_view)
|
||||
self.serialize_contexts_elements(ifc, tree, contexts, "annotation", drawing_elements, target_view)
|
||||
|
||||
if self.camera_element not in drawing_elements:
|
||||
with profile("Camera element"):
|
||||
# The camera must always be included, regardless of any include/exclude filters.
|
||||
geom_settings = ifcopenshell.geom.settings(DISABLE_TRIANGULATION=True, STRICT_TOLERANCE=True)
|
||||
it = ifcopenshell.geom.iterator(geom_settings, ifc, include=[self.camera_element])
|
||||
for elem in self.yield_from_iterator(it):
|
||||
for elem in it:
|
||||
self.serialiser.write(elem)
|
||||
|
||||
with profile("Finalizing"):
|
||||
@@ -588,8 +579,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
if global_id not in elements_with_faces:
|
||||
continue
|
||||
for path in projection.findall("./{http://www.w3.org/2000/svg}path"):
|
||||
# Rounding is necessary to ensure coincident points are coincident
|
||||
start, end = [[round(float(o), 1) for o in co[1:].split(",")] for co in path.attrib["d"].split()]
|
||||
start, end = [[float(o) for o in co[1:].split(",")] for co in path.attrib["d"].split()]
|
||||
if start == end:
|
||||
continue
|
||||
# Extension by 0.5mm is necessary to ensure lines overlap with other diagonal lines
|
||||
@@ -884,13 +874,6 @@ class CreateDrawing(bpy.types.Operator):
|
||||
# the style of the face when running tree.select_ray()
|
||||
# tree.enable_face_styles(True)
|
||||
|
||||
def yield_from_iterator(self, it):
|
||||
if it.initialize():
|
||||
while True:
|
||||
yield it.get()
|
||||
if not it.next():
|
||||
break
|
||||
|
||||
def get_svg_classes(self, element):
|
||||
classes = [element.is_a()]
|
||||
material = ifcopenshell.util.element.get_material(element, should_skip_usage=True)
|
||||
@@ -966,17 +949,21 @@ class CreateDrawing(bpy.types.Operator):
|
||||
# alone (e.g. dangles, cuts, invalids). See #3421.
|
||||
line_strings = []
|
||||
old_paths = []
|
||||
has_open_paths = False
|
||||
for path in el.findall("{http://www.w3.org/2000/svg}path"):
|
||||
for subpath in path.attrib["d"].split("M")[1:]:
|
||||
subpath = "M" + subpath.strip()
|
||||
coords = [[round(float(o), 1) for o in co[1:].split(",")] for co in subpath.split()]
|
||||
coords = [[float(o) for o in co[1:].split(",")] for co in subpath.split()]
|
||||
if coords[0] != coords[-1]:
|
||||
has_open_paths = True
|
||||
line_strings.append(shapely.LineString(coords))
|
||||
old_paths.append(path)
|
||||
unioned_line_strings = shapely.union_all(shapely.GeometryCollection(line_strings))
|
||||
if hasattr(unioned_line_strings, "geoms"):
|
||||
results = shapely.polygonize_full(unioned_line_strings.geoms)
|
||||
else:
|
||||
results = []
|
||||
|
||||
results = []
|
||||
if has_open_paths:
|
||||
unioned_line_strings = shapely.union_all(shapely.GeometryCollection(line_strings))
|
||||
if hasattr(unioned_line_strings, "geoms"):
|
||||
results = shapely.polygonize_full(unioned_line_strings.geoms)
|
||||
|
||||
# If we succeeded in generating new path geometry, remove all the
|
||||
# old paths and add new ones.
|
||||
@@ -1030,7 +1017,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
for path in el.findall("{http://www.w3.org/2000/svg}path"):
|
||||
for subpath in path.attrib["d"].split("M")[1:]:
|
||||
subpath_co = "M" + subpath.strip(" Z")
|
||||
coords = [[round(float(o), 1) for o in co[1:].split(",")] for co in subpath_co.split()]
|
||||
coords = [[float(o) for o in co[1:].split(",")] for co in subpath_co.split()]
|
||||
if subpath.strip().lower().endswith("z"):
|
||||
coords.append(coords[0])
|
||||
if len(coords) > 2 and coords[0] == coords[-1]:
|
||||
@@ -1358,7 +1345,7 @@ class SelectAllDrawings(bpy.types.Operator):
|
||||
bl_label = "Select All Drawings"
|
||||
view: bpy.props.StringProperty()
|
||||
bl_description = "Select all drawings in the drawing list.\n\n" + "SHIFT+CLICK to deselect all drawings"
|
||||
select_all: bpy.props.BoolProperty(name="Open All", default=False, options={"SKIP_SAVE"})
|
||||
select_all: bpy.props.BoolProperty(name="Open All", default=True, options={"SKIP_SAVE"})
|
||||
|
||||
def invoke(self, context, event):
|
||||
# deselect all drawings on shift+click
|
||||
@@ -2295,8 +2282,16 @@ class AssignSelectedObjectAsProduct(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
objs = context.selected_objects[:]
|
||||
objs.remove(context.active_object)
|
||||
other_selected_object = next(obj for obj in context.selected_objects if obj != context.active_object)
|
||||
obj1 = objs[0]
|
||||
element1 = tool.Ifc.get_entity(obj1)
|
||||
obj2 = objs[1]
|
||||
element2 = tool.Ifc.get_entity(obj2)
|
||||
if element1.is_a("IfcAnnotation"):
|
||||
other_selected_object = obj2
|
||||
bpy.context.view_layer.objects.active = obj1
|
||||
elif element2.is_a("IfcAnnotation"):
|
||||
other_selected_object = obj1
|
||||
bpy.context.view_layer.objects.active = obj2
|
||||
context.active_object.BIMAssignedProductProperties.relating_product = other_selected_object
|
||||
bpy.ops.bim.edit_assigned_product()
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -29,9 +29,11 @@ import svgwrite
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.representation
|
||||
import ifcopenshell.util.selector
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.bim.module.drawing.helper as helper
|
||||
import blenderbim.bim.module.drawing.annotation as annotation
|
||||
from blenderbim.bim.module.drawing.data import DrawingsData
|
||||
|
||||
from blenderbim.bim.module.drawing.data import DecoratorData
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
@@ -39,6 +41,7 @@ from blenderbim.bim.ifc import IfcStore
|
||||
from math import pi, ceil, atan, degrees, acos
|
||||
from mathutils import geometry, Vector
|
||||
from bpy_extras import view3d_utils
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class External(svgwrite.container.Group):
|
||||
@@ -322,7 +325,7 @@ class SvgWriter:
|
||||
)
|
||||
)
|
||||
|
||||
def draw_misc_annotation(self, obj):
|
||||
def draw_misc_annotation(self, obj: bpy.types.Object) -> None:
|
||||
# We have to decide whether this should come from Blender or from IFC.
|
||||
# For the moment, for convenience of experimenting with ideas, it comes
|
||||
# from Blender. In the future, it should probably come from IFC.
|
||||
@@ -356,14 +359,14 @@ class SvgWriter:
|
||||
d = "M{}".format(d[1:])
|
||||
path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes)))
|
||||
|
||||
def get_attribute_classes(self, obj):
|
||||
def get_attribute_classes(self, obj: bpy.types.Object) -> list[str]:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
global_id = "GlobalId-{}".format(element.GlobalId)
|
||||
predefined_type = "PredefinedType-" + tool.Drawing.canonicalise_class_name(
|
||||
str(ifcopenshell.util.element.get_predefined_type(element))
|
||||
)
|
||||
classes = [global_id, element.is_a(), predefined_type]
|
||||
custom_classes = ifcopenshell.util.element.get_pset(element, "EPset_Annotation", "Classes")
|
||||
custom_classes: str = ifcopenshell.util.element.get_pset(element, "EPset_Annotation", "Classes")
|
||||
if custom_classes:
|
||||
classes.extend(custom_classes.split())
|
||||
for key in self.metadata:
|
||||
@@ -787,7 +790,7 @@ class SvgWriter:
|
||||
attrib["filter"] = "url(#fill-background)"
|
||||
return element
|
||||
|
||||
def draw_text_annotation(self, text_obj, position):
|
||||
def draw_text_annotation(self, text_obj: bpy.types.Object, position: Vector) -> None:
|
||||
x_offset = self.raw_width / 2
|
||||
y_offset = self.raw_height / 2
|
||||
element = tool.Ifc.get_entity(text_obj)
|
||||
@@ -799,17 +802,7 @@ class SvgWriter:
|
||||
text_position_svg = text_position * self.svg_scale
|
||||
text_position_svg_str = ", ".join(map(str, text_position_svg))
|
||||
|
||||
def get_basis_vector(matrix, i=0):
|
||||
"""returns basis vector for i in world space, unaffected by object scale"""
|
||||
return matrix.inverted()[i].to_3d().normalized()
|
||||
|
||||
text_dir_world_x_axis = get_basis_vector(text_obj.matrix_world)
|
||||
|
||||
# RCP cameras may be scaled, so reset scales.
|
||||
camera_matrix = tool.Drawing.get_camera_matrix(self.camera)
|
||||
text_dir = (camera_matrix.inverted().to_quaternion() @ text_dir_world_x_axis).to_2d().normalized()
|
||||
angle = math.degrees(-text_dir.angle_signed(Vector((1, 0))))
|
||||
|
||||
angle = self.get_empty_object_angle(text_obj)
|
||||
classes = self.get_attribute_classes(text_obj)
|
||||
classes_str = " ".join(classes)
|
||||
fill_bg = "fill-bg" in classes
|
||||
@@ -817,8 +810,7 @@ class SvgWriter:
|
||||
symbol = tool.Drawing.get_annotation_symbol(element)
|
||||
template_text_fields = []
|
||||
if symbol:
|
||||
# NOTE: for now we assume that scale is uniform
|
||||
symbol_transform = f"translate({text_position_svg_str}) rotate({angle}) scale({text_obj.scale.x})"
|
||||
symbol_transform = self.get_symbol_transform(text_position_svg_str, angle, text_obj)
|
||||
|
||||
symbol_svg = self.find_xml_symbol_by_id(symbol)
|
||||
if symbol_svg:
|
||||
@@ -847,7 +839,7 @@ class SvgWriter:
|
||||
return None
|
||||
|
||||
if not symbol_svg or not template_text_fields:
|
||||
self.svg.add(self.svg.use(f"#{symbol}", transform=symbol_transform))
|
||||
self.draw_symbol(symbol, symbol_transform)
|
||||
|
||||
line_number = 0
|
||||
for text_literal in text_literals:
|
||||
@@ -865,17 +857,14 @@ class SvgWriter:
|
||||
self.svg.add(tag)
|
||||
line_number += len(tag.elements)
|
||||
|
||||
def draw_empty_annotation(self, obj, classes):
|
||||
def draw_empty_annotation(self, obj: bpy.types.Object, classes: list[str]) -> None:
|
||||
x_offset = self.raw_width / 2
|
||||
y_offset = self.raw_height / 2
|
||||
|
||||
point = self.project_point_onto_camera(obj.matrix_world.translation)
|
||||
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
svg_id = ifcopenshell.util.element.get_pset(element, "EPset_Annotation", "Symbol")
|
||||
if not svg_id:
|
||||
# EPset_AnnotationSurveyArea is not standard! See bSI-4.3 proposal #660.
|
||||
svg_id = ifcopenshell.util.element.get_pset(element, "EPset_AnnotationSurveyArea", "PointType")
|
||||
svg_id = tool.Drawing.get_annotation_symbol(element)
|
||||
if not svg_id:
|
||||
svg_id = str(ifcopenshell.util.element.get_predefined_type(element))
|
||||
if not svg_id:
|
||||
@@ -883,9 +872,37 @@ class SvgWriter:
|
||||
|
||||
point = Vector(((x_offset + point.x), (y_offset - point.y)))
|
||||
symbol_position_svg = point * self.svg_scale
|
||||
self.svg.add(self.svg.use(f"#{svg_id}", insert=symbol_position_svg))
|
||||
svg_position_str = ", ".join(map(str, symbol_position_svg))
|
||||
|
||||
def draw_point_annotation(self, obj, classes):
|
||||
angle = self.get_empty_object_angle(obj)
|
||||
symbol_transform = self.get_symbol_transform(svg_position_str, angle, obj)
|
||||
self.draw_symbol(svg_id, symbol_transform)
|
||||
|
||||
def get_empty_object_angle(self, obj: bpy.types.Object) -> float:
|
||||
def get_basis_vector(matrix: bpy.types.Object, i: int = 0) -> Vector:
|
||||
"""returns basis vector for i in world space, unaffected by object scale"""
|
||||
return matrix.inverted()[i].to_3d().normalized()
|
||||
|
||||
text_dir_world_x_axis = get_basis_vector(obj.matrix_world)
|
||||
|
||||
# RCP cameras may be scaled, so reset scales.
|
||||
camera_matrix = tool.Drawing.get_camera_matrix(self.camera)
|
||||
text_dir = (camera_matrix.inverted().to_quaternion() @ text_dir_world_x_axis).to_2d().normalized()
|
||||
return math.degrees(-text_dir.angle_signed(Vector((1, 0))))
|
||||
|
||||
def get_symbol_transform(
|
||||
self, svg_position_str: str, angle: float = 0.0, obj: Optional[bpy.types.Object] = None
|
||||
) -> str:
|
||||
"""`obj` will be used to identify symbol scale,
|
||||
ignore it if object scale doesn't match the symbol scale"""
|
||||
# NOTE: for now we assume that scale is uniform
|
||||
scale = 1.0 if not obj else obj.scale.x
|
||||
return f"translate({svg_position_str}) rotate({angle}) scale({scale})"
|
||||
|
||||
def draw_symbol(self, symbol_id: str, symbol_transform: str) -> None:
|
||||
self.svg.add(self.svg.use(f"#{symbol_id}", transform=symbol_transform))
|
||||
|
||||
def draw_point_annotation(self, obj: bpy.types.Object, classes: list[str]) -> None:
|
||||
x_offset = self.raw_width / 2
|
||||
y_offset = self.raw_height / 2
|
||||
|
||||
@@ -893,10 +910,7 @@ class SvgWriter:
|
||||
projected_points = [self.project_point_onto_camera(matrix_world @ v.co) for v in obj.data.vertices]
|
||||
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
svg_id = ifcopenshell.util.element.get_pset(element, "EPset_Annotation", "Symbol")
|
||||
if not svg_id:
|
||||
# EPset_AnnotationSurveyArea is not standard! See bSI-4.3 proposal #660.
|
||||
svg_id = ifcopenshell.util.element.get_pset(element, "EPset_AnnotationSurveyArea", "PointType")
|
||||
svg_id = tool.Drawing.get_annotation_symbol(element)
|
||||
if not svg_id:
|
||||
svg_id = str(ifcopenshell.util.element.get_predefined_type(element))
|
||||
if not svg_id:
|
||||
@@ -905,7 +919,10 @@ class SvgWriter:
|
||||
for symbol_position in projected_points:
|
||||
symbol_position = Vector(((x_offset + symbol_position.x), (y_offset - symbol_position.y)))
|
||||
symbol_position_svg = symbol_position * self.svg_scale
|
||||
self.svg.add(self.svg.use(f"#{svg_id}", insert=symbol_position_svg))
|
||||
svg_position_str = ", ".join(map(str, symbol_position_svg))
|
||||
|
||||
symbol_transform = self.get_symbol_transform(svg_position_str)
|
||||
self.draw_symbol(svg_id, symbol_transform)
|
||||
|
||||
def draw_break_annotations(self, obj):
|
||||
x_offset = self.raw_width / 2
|
||||
@@ -1066,7 +1083,9 @@ class SvgWriter:
|
||||
# calculating text parameters and adding text
|
||||
text_position = arc_mid_point + arc_mid_dir * 5
|
||||
text_style = SvgWriter.get_box_alignment_parameters("center")
|
||||
angle_text = f"{int(angle)}deg"
|
||||
drawing_pset_data = DrawingsData.data["active_drawing_pset_data"]
|
||||
decimal_places = drawing_pset_data.get("AngleDecimalPlaces", None)
|
||||
angle_text = f"{round(angle, decimal_places)}°"
|
||||
self.svg.add(self.svg.text(angle_text, insert=text_position, class_="ANGLE", **text_style))
|
||||
|
||||
# Draw SVG arc, see for details: http://xahlee.info/js/svg_circle_arc.html
|
||||
@@ -1402,7 +1421,7 @@ class SvgWriter:
|
||||
|
||||
return text_tags
|
||||
|
||||
def project_point_onto_camera(self, point):
|
||||
def project_point_onto_camera(self, point: Vector) -> Vector:
|
||||
# TODO is this needlessly complex?
|
||||
return self.camera.matrix_world.inverted() @ geometry.intersect_line_plane(
|
||||
point.xyz,
|
||||
|
||||
@@ -250,7 +250,7 @@ class AnnotationToolUI:
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="FILE_3D")
|
||||
prop_with_search(row, cls.props, "relating_type_id", text="")
|
||||
row.operator("bim.launch_annotation_type_manager", icon="LIGHTPROBE_GRID", text="")
|
||||
row.operator("bim.launch_annotation_type_manager", icon=tool.Blender.TYPE_MANAGER_ICON, text="")
|
||||
|
||||
add_layout_hotkey_operator(cls.layout, "Add", "S_A", "Create a new annotation")
|
||||
|
||||
|
||||
@@ -49,7 +49,17 @@ classes = (
|
||||
operator.SwitchRepresentation,
|
||||
operator.UpdateParametricRepresentation,
|
||||
operator.UpdateRepresentation,
|
||||
operator.RemoveRepresentationItem,
|
||||
operator.EnableEditingRepresentationItemStyle,
|
||||
operator.EditRepresentationItemStyle,
|
||||
operator.DisableEditingRepresentationItemStyle,
|
||||
operator.UnassignRepresentationItemStyle,
|
||||
operator.EnableEditingRepresentationItemShapeAspect,
|
||||
operator.EditRepresentationItemShapeAspect,
|
||||
operator.DisableEditingRepresentationItemShapeAspect,
|
||||
operator.RemoveRepresentationItemFromShapeAspect,
|
||||
prop.RepresentationItem,
|
||||
prop.ShapeAspect,
|
||||
prop.BIMObjectGeometryProperties,
|
||||
prop.BIMGeometryProperties,
|
||||
ui.BIM_PT_placement,
|
||||
@@ -93,7 +103,9 @@ def register():
|
||||
addon_keymaps.append((km, kmi))
|
||||
kmi = km.keymap_items.new("bim.override_object_duplicate_move_linked_macro", "D", "PRESS", alt=True)
|
||||
addon_keymaps.append((km, kmi))
|
||||
kmi = km.keymap_items.new("bim.object_duplicate_move_linked_aggregate_macro", "D", "PRESS", ctrl=True, shift=True)
|
||||
kmi = km.keymap_items.new(
|
||||
"bim.object_duplicate_move_linked_aggregate_macro", "D", "PRESS", ctrl=True, shift=True
|
||||
)
|
||||
addon_keymaps.append((km, kmi))
|
||||
kmi = km.keymap_items.new("bim.override_paste_buffer", "V", "PRESS", ctrl=True)
|
||||
addon_keymaps.append((km, kmi))
|
||||
|
||||
@@ -38,6 +38,7 @@ class RepresentationsData:
|
||||
def load(cls):
|
||||
cls.data = {"representations": cls.representations()}
|
||||
cls.data["contexts"] = cls.contexts()
|
||||
cls.data["shape_aspects"] = cls.shape_aspects()
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
@@ -92,6 +93,26 @@ class RepresentationsData:
|
||||
)
|
||||
return results
|
||||
|
||||
@classmethod
|
||||
def shape_aspects(cls):
|
||||
obj = bpy.context.active_object
|
||||
if not obj.data:
|
||||
return []
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
active_representation_id = obj.data.BIMMeshProperties.ifc_definition_id
|
||||
base_representation = tool.Ifc.get().by_id(active_representation_id)
|
||||
|
||||
# shape aspects matching context of the active representation
|
||||
matching_shape_aspects = []
|
||||
for shape_aspect in ifcopenshell.util.element.get_shape_aspects(element):
|
||||
matching_representation = tool.Geometry.get_shape_aspect_representation(shape_aspect, base_representation)
|
||||
if matching_representation:
|
||||
matching_shape_aspects.append(shape_aspect)
|
||||
|
||||
# blender enum items
|
||||
new_shape_aspect = [("NEW", "Create A New Shape Aspect", "")]
|
||||
return new_shape_aspect + [(str(s.id()), s.Name or "Unnamed", "") for s in matching_shape_aspects]
|
||||
|
||||
|
||||
class RepresentationItemsData:
|
||||
data = {}
|
||||
|
||||
@@ -21,10 +21,12 @@ import bmesh
|
||||
import logging
|
||||
import numpy as np
|
||||
import json
|
||||
import re
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.unit
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.representation
|
||||
import ifcopenshell.util.placement
|
||||
import ifcopenshell.api
|
||||
import blenderbim.core.geometry as core
|
||||
import blenderbim.core.aggregate
|
||||
@@ -36,6 +38,7 @@ import blenderbim.bim.handler
|
||||
from mathutils import Vector, Matrix
|
||||
from time import time
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.util.shape_builder import ShapeBuilder
|
||||
|
||||
|
||||
class Operator:
|
||||
@@ -767,8 +770,14 @@ class OverrideDuplicateMove(bpy.types.Operator):
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element:
|
||||
if element.is_a("IfcAnnotation") and element.ObjectType == "DRAWING":
|
||||
self.report(
|
||||
{"INFO"}, "Did not duplicate. Duplicate drawings through the Drawings and Documents UI."
|
||||
)
|
||||
obj.select_set(False)
|
||||
continue # For now, don't copy drawings until we stabilise a bit more. It's tricky.
|
||||
elif element.is_a("IfcProject"):
|
||||
self.report({"INFO"}, "Did not duplicate. IFC can only have one project.")
|
||||
obj.select_set(False)
|
||||
continue # Only one IfcProject is allowed.
|
||||
|
||||
linked_non_ifc_object = linked and not element
|
||||
@@ -834,6 +843,7 @@ class OverrideDuplicateMove(bpy.types.Operator):
|
||||
|
||||
# Recreate decompositions
|
||||
tool.Root.recreate_decompositions(decomposition_relationships, old_to_new)
|
||||
OverrideDuplicateMove.handle_linked_aggregates(old_to_new)
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
return old_to_new
|
||||
|
||||
@@ -886,6 +896,26 @@ class OverrideDuplicateMove(bpy.types.Operator):
|
||||
if entity in old_to_new.keys():
|
||||
core.remove_connection(tool.Geometry, connection=connection)
|
||||
|
||||
@staticmethod
|
||||
def handle_linked_aggregates(old_to_new):
|
||||
for old, new in old_to_new.items():
|
||||
pset = ifcopenshell.util.element.get_pset(new[0], "BBIM_Linked_Aggregate")
|
||||
if pset:
|
||||
old_aggregate = ifcopenshell.util.element.get_aggregate(old)
|
||||
new_aggregate = ifcopenshell.util.element.get_aggregate(new[0])
|
||||
if old_aggregate == new_aggregate:
|
||||
parts = ifcopenshell.util.element.get_parts(new_aggregate)
|
||||
if parts:
|
||||
index = DuplicateMoveLinkedAggregate.get_max_index(parts)
|
||||
index += 1
|
||||
pset = tool.Ifc.get().by_id(pset['id'])
|
||||
ifcopenshell.api.run(
|
||||
"pset.edit_pset",
|
||||
tool.Ifc.get(),
|
||||
pset=pset,
|
||||
properties={"Index": index},
|
||||
)
|
||||
|
||||
|
||||
class OverrideDuplicateMoveLinkedMacro(bpy.types.Macro):
|
||||
bl_idname = "bim.override_object_duplicate_move_linked_macro"
|
||||
@@ -944,16 +974,38 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator):
|
||||
obj.select_set(True)
|
||||
parts = ifcopenshell.util.element.get_parts(element)
|
||||
if parts:
|
||||
index = 0
|
||||
index = DuplicateMoveLinkedAggregate.get_max_index(parts)
|
||||
add_linked_aggregate_pset(element, index)
|
||||
index +=1
|
||||
for part in parts:
|
||||
if part.is_a("IfcElementAssembly"):
|
||||
select_objects_and_add_data(part)
|
||||
|
||||
add_linked_aggregate_pset(part, index)
|
||||
index += 1
|
||||
|
||||
else:
|
||||
add_linked_aggregate_pset(part, index)
|
||||
index += 1
|
||||
|
||||
obj = tool.Ifc.get_object(part)
|
||||
obj.select_set(True)
|
||||
|
||||
|
||||
def add_linked_aggregate_pset(part, index):
|
||||
pset = ifcopenshell.util.element.get_pset(part, self.pset_name)
|
||||
|
||||
if not pset:
|
||||
pset = ifcopenshell.api.run(
|
||||
"pset.add_pset", tool.Ifc.get(), product=part, name=self.pset_name
|
||||
)
|
||||
|
||||
ifcopenshell.api.run(
|
||||
"pset.edit_pset",
|
||||
tool.Ifc.get(),
|
||||
pset=pset,
|
||||
properties={"Index": index},
|
||||
)
|
||||
else:
|
||||
pass
|
||||
|
||||
return index
|
||||
|
||||
def add_linked_aggregate_group(element):
|
||||
linked_aggregate_group = None
|
||||
@@ -968,23 +1020,30 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator):
|
||||
linked_aggregate_group = ifcopenshell.api.run("group.add_group", tool.Ifc.get(), Name=self.group_name)
|
||||
ifcopenshell.api.run("group.assign_group", tool.Ifc.get(), products=[element], group=linked_aggregate_group)
|
||||
|
||||
def add_linked_aggregate_pset(part, index):
|
||||
pset = ifcopenshell.util.element.get_pset(part, self.pset_name)
|
||||
|
||||
if not pset:
|
||||
pset = ifcopenshell.api.run("pset.add_pset", tool.Ifc.get(), product=part, name=self.pset_name)
|
||||
else:
|
||||
pset = tool.Ifc.get().by_id(pset["id"])
|
||||
|
||||
ifcopenshell.api.run(
|
||||
"pset.edit_pset",
|
||||
tool.Ifc.get(),
|
||||
pset=pset,
|
||||
properties={"Index": index},
|
||||
)
|
||||
|
||||
return index
|
||||
def custom_incremental_naming_for_element_assembly(old_to_new):
|
||||
for new in old_to_new.values():
|
||||
if new[0].is_a("IfcElementAssembly"):
|
||||
group_elements = [
|
||||
r.RelatedObjects
|
||||
for r in getattr(new[0], "HasAssignments", []) or []
|
||||
if r.is_a("IfcRelAssignsToGroup")
|
||||
if "BBIM_Linked_Aggregate" in r.RelatingGroup.Name
|
||||
][0]
|
||||
|
||||
number = len(group_elements) - 1
|
||||
number = f"{number:02d}"
|
||||
new_obj = tool.Ifc.get_object(new[0])
|
||||
pattern1 = r'_\d'
|
||||
if re.findall(pattern1, new_obj.name):
|
||||
split_name = new_obj.name.split("_")
|
||||
new_obj.name = split_name[0] + "_" + number
|
||||
continue
|
||||
pattern2 = r'\.\d{3}'
|
||||
if re.findall(pattern2, new_obj.name):
|
||||
split_name = new_obj.name.split(".")
|
||||
new_obj.name = split_name[0] + "_" + number
|
||||
|
||||
|
||||
if len(context.selected_objects) != 1:
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -1005,6 +1064,8 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator):
|
||||
|
||||
old_to_new = OverrideDuplicateMove.execute_ifc_duplicate_operator(self, context, linked=True)
|
||||
|
||||
custom_incremental_naming_for_element_assembly(old_to_new)
|
||||
|
||||
# Recreate aggregate relationship
|
||||
for old in old_to_new.keys():
|
||||
if old.is_a("IfcElementAssembly"):
|
||||
@@ -1014,6 +1075,18 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator):
|
||||
|
||||
return old_to_new
|
||||
|
||||
|
||||
@staticmethod
|
||||
def get_max_index(parts):
|
||||
psets = [ifcopenshell.util.element.get_pset(p, "BBIM_Linked_Aggregate") for p in parts]
|
||||
index = [i['Index'] for i in psets if i]
|
||||
if len(index) > 0:
|
||||
index = max(index)
|
||||
return index
|
||||
else:
|
||||
return 0
|
||||
|
||||
|
||||
|
||||
class RefreshLinkedAggregate(bpy.types.Operator):
|
||||
bl_idname = "bim.refresh_linked_aggregate"
|
||||
@@ -1038,6 +1111,7 @@ class RefreshLinkedAggregate(bpy.types.Operator):
|
||||
self.pset_name = "BBIM_Linked_Aggregate"
|
||||
refresh_start_time = time()
|
||||
old_to_new = {}
|
||||
original_names = {}
|
||||
|
||||
def delete_objects(element):
|
||||
parts = ifcopenshell.util.element.get_parts(element)
|
||||
@@ -1050,6 +1124,61 @@ class RefreshLinkedAggregate(bpy.types.Operator):
|
||||
|
||||
tool.Geometry.delete_ifc_object(tool.Ifc.get_object(element))
|
||||
|
||||
def get_original_names(element):
|
||||
group = [
|
||||
r.RelatingGroup
|
||||
for r in getattr(element, "HasAssignments", []) or []
|
||||
if r.is_a("IfcRelAssignsToGroup")
|
||||
if self.group_name in r.RelatingGroup.Name
|
||||
][0].id()
|
||||
original_names[group] = {}
|
||||
|
||||
pset = ifcopenshell.util.element.get_pset(element, self.pset_name)
|
||||
index = pset['Index']
|
||||
original_names[group][index] = tool.Ifc.get_object(element).name
|
||||
|
||||
parts = ifcopenshell.util.element.get_parts(element)
|
||||
if parts:
|
||||
for part in parts:
|
||||
if part.is_a("IfcElementAssembly"):
|
||||
original_names | get_original_names(part)
|
||||
else:
|
||||
try:
|
||||
pset = ifcopenshell.util.element.get_pset(part, self.pset_name)
|
||||
except:
|
||||
continue
|
||||
index = pset['Index']
|
||||
original_names[group][index] = tool.Ifc.get_object(part).name
|
||||
|
||||
return original_names
|
||||
|
||||
def set_original_name(obj, original_names):
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
aggregate = ifcopenshell.util.element.get_aggregate(element)
|
||||
if ifcopenshell.util.element.get_parts(element): # if element has parts it means it is the base of and aggregate or sub-aggregate
|
||||
aggregate = element
|
||||
|
||||
group = [
|
||||
r.RelatingGroup
|
||||
for r in getattr(aggregate, "HasAssignments", []) or []
|
||||
if r.is_a("IfcRelAssignsToGroup")
|
||||
if self.group_name in r.RelatingGroup.Name
|
||||
]
|
||||
if not group:
|
||||
return
|
||||
|
||||
group = group[0].id()
|
||||
|
||||
pset = ifcopenshell.util.element.get_pset(element, self.pset_name)
|
||||
index = pset['Index']
|
||||
|
||||
try:
|
||||
obj.name = original_names[group][index]
|
||||
except:
|
||||
return
|
||||
|
||||
|
||||
|
||||
def get_element_assembly(element):
|
||||
if element.is_a("IfcElementAssembly"):
|
||||
return element
|
||||
@@ -1134,7 +1263,9 @@ class RefreshLinkedAggregate(bpy.types.Operator):
|
||||
selected_matrix = selected_obj.matrix_world
|
||||
object_duplicate = tool.Ifc.get_object(element)
|
||||
duplicate_matrix = object_duplicate.matrix_world.decompose()
|
||||
|
||||
|
||||
original_names = get_original_names(element)
|
||||
|
||||
delete_objects(element)
|
||||
|
||||
for obj in context.selected_objects:
|
||||
@@ -1149,19 +1280,24 @@ class RefreshLinkedAggregate(bpy.types.Operator):
|
||||
matrix_diff = Matrix.inverted(selected_matrix) @ new_obj.matrix_world
|
||||
new_obj_matrix = new_base_matrix @ matrix_diff
|
||||
new_obj.matrix_world = new_obj_matrix
|
||||
|
||||
|
||||
for old, new in old_to_new.items():
|
||||
if element_aggregate and new[0].is_a("IfcElementAssembly"):
|
||||
new_aggregate = ifcopenshell.util.element.get_aggregate(new[0])
|
||||
|
||||
if not new_aggregate:
|
||||
blenderbim.core.aggregate.assign_object(
|
||||
tool.Ifc,
|
||||
tool.Aggregate,
|
||||
tool.Collector,
|
||||
relating_obj=tool.Ifc.get_object(element_aggregate),
|
||||
related_obj=tool.Ifc.get_object(new[0]),
|
||||
)
|
||||
|
||||
tool.Ifc,
|
||||
tool.Aggregate,
|
||||
tool.Collector,
|
||||
relating_obj=tool.Ifc.get_object(element_aggregate),
|
||||
related_obj=tool.Ifc.get_object(new[0]),
|
||||
)
|
||||
|
||||
for old, new in old_to_new.items():
|
||||
new_obj = tool.Ifc.get_object(new[0])
|
||||
set_original_name(new_obj, original_names)
|
||||
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
|
||||
operator_time = time() - refresh_start_time
|
||||
@@ -1194,18 +1330,23 @@ class OverrideJoin(bpy.types.Operator, Operator):
|
||||
return self.join_blender_obj()
|
||||
|
||||
def join_ifc_obj(self):
|
||||
representation = tool.Ifc.get().by_id(self.target.data.BIMMeshProperties.ifc_definition_id)
|
||||
ifc_file = tool.Ifc.get()
|
||||
builder = ShapeBuilder(ifc_file)
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
|
||||
representation = ifc_file.by_id(self.target.data.BIMMeshProperties.ifc_definition_id)
|
||||
if representation.RepresentationType in ("Tessellation", "Brep"):
|
||||
for obj in bpy.context.selected_objects:
|
||||
if obj == self.target:
|
||||
continue
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element:
|
||||
ifcopenshell.api.run("root.remove_product", tool.Ifc.get(), product=element)
|
||||
ifcopenshell.api.run("root.remove_product", ifc_file, product=element)
|
||||
bpy.ops.object.join()
|
||||
bpy.ops.bim.update_representation(obj=self.target.name, ifc_representation_class="")
|
||||
elif representation.RepresentationType == "SweptSolid":
|
||||
target_placement = np.array(self.target.matrix_world)
|
||||
target_placement[:, 3][:3] /= si_conversion
|
||||
|
||||
items = list(representation.Items)
|
||||
for obj in bpy.context.selected_objects:
|
||||
if obj == self.target:
|
||||
@@ -1218,31 +1359,28 @@ class OverrideJoin(bpy.types.Operator, Operator):
|
||||
continue
|
||||
|
||||
# Only objects of the same representation type can be joined
|
||||
obj_rep = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
|
||||
obj_rep = ifc_file.by_id(obj.data.BIMMeshProperties.ifc_definition_id)
|
||||
if obj_rep.RepresentationType != "SweptSolid":
|
||||
obj.select_set(False)
|
||||
continue
|
||||
|
||||
placement = np.array(obj.matrix_world)
|
||||
placement[:, 3][:3] /= si_conversion
|
||||
|
||||
for item in obj_rep.Items:
|
||||
copied_item = ifcopenshell.util.element.copy_deep(tool.Ifc.get(), item)
|
||||
copied_item = ifcopenshell.util.element.copy_deep(ifc_file, item)
|
||||
for style in item.StyledByItem:
|
||||
copied_style = ifcopenshell.util.element.copy(tool.Ifc.get(), style)
|
||||
copied_style = ifcopenshell.util.element.copy(ifc_file, style)
|
||||
copied_style.Item = copied_item
|
||||
if copied_item.Position:
|
||||
position = ifcopenshell.util.placement.get_axis2placement(copied_item.Position)
|
||||
else:
|
||||
position = np.eye(4)
|
||||
position = np.eye(4, dtype=float)
|
||||
position = placement @ position
|
||||
position = np.linalg.inv(target_placement) @ position
|
||||
copied_item.Position = tool.Ifc.get().createIfcAxis2Placement3D(
|
||||
tool.Ifc.get().createIfcCartesianPoint([float(n) for n in position[:, 3][:3]]),
|
||||
tool.Ifc.get().createIfcDirection([float(n) for n in position[:, 2][:3]]),
|
||||
tool.Ifc.get().createIfcDirection([float(n) for n in position[:, 0][:3]]),
|
||||
)
|
||||
copied_item.Position = builder.create_axis2_placement_3d_from_matrix(position)
|
||||
items.append(copied_item)
|
||||
ifcopenshell.api.run("root.remove_product", tool.Ifc.get(), product=element)
|
||||
ifcopenshell.api.run("root.remove_product", ifc_file, product=element)
|
||||
representation.Items = items
|
||||
bpy.ops.object.join()
|
||||
core.switch_representation(
|
||||
@@ -1586,6 +1724,16 @@ class EnableEditingRepresentationItems(bpy.types.Operator, Operator):
|
||||
new.surface_style = style.Name or "Unnamed"
|
||||
elif inverse.is_a("IfcPresentationLayerAssignment"):
|
||||
new.layer = inverse.Name or "Unnamed"
|
||||
elif inverse.is_a("IfcShapeRepresentation"):
|
||||
if inverse.OfShapeAspect:
|
||||
shape_aspect = inverse.OfShapeAspect[0]
|
||||
new.shape_aspect = shape_aspect.Name
|
||||
new.shape_aspect_id = shape_aspect.id()
|
||||
|
||||
# sort created items
|
||||
sorted_items = sorted(props.items[:], key=lambda i: (not i.shape_aspect, i.shape_aspect))
|
||||
for i, item in enumerate(sorted_items[:-1]): # last item is sorted automatically
|
||||
props.items.move(props.items[:].index(item), i)
|
||||
|
||||
|
||||
class DisableEditingRepresentationItems(bpy.types.Operator, Operator):
|
||||
@@ -1596,3 +1744,270 @@ class DisableEditingRepresentationItems(bpy.types.Operator, Operator):
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
obj.BIMGeometryProperties.is_editing = False
|
||||
|
||||
|
||||
class RemoveRepresentationItem(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.remove_representation_item"
|
||||
bl_label = "Remove Representation Item"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
representation_item_id: bpy.props.IntProperty()
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if context.active_object is None or len(context.active_object.BIMGeometryProperties.items) <= 1:
|
||||
cls.poll_message_set(
|
||||
"Active object need to have more than 1 representation items to keep representation valid"
|
||||
)
|
||||
return False
|
||||
return True
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
props = obj.BIMGeometryProperties
|
||||
ifc_file = tool.Ifc.get()
|
||||
|
||||
representation_item = ifc_file.by_id(self.representation_item_id)
|
||||
tool.Geometry.remove_representation_item(representation_item)
|
||||
tool.Geometry.reload_representation(obj)
|
||||
|
||||
# reload items ui
|
||||
bpy.ops.bim.disable_editing_representation_items()
|
||||
bpy.ops.bim.enable_editing_representation_items()
|
||||
|
||||
|
||||
def poll_editing_representaiton_item_style(cls, context):
|
||||
if not (obj := getattr(context, "active_object", None)):
|
||||
return False
|
||||
props = obj.BIMGeometryProperties
|
||||
if not props.is_editing:
|
||||
return False
|
||||
if not 0 <= props.active_item_index < len(props.items):
|
||||
return False
|
||||
item = props.items[props.active_item_index]
|
||||
shape_aspect = item.shape_aspect
|
||||
if shape_aspect == "":
|
||||
return True
|
||||
|
||||
from blenderbim.bim.module.material.data import ObjectMaterialData
|
||||
|
||||
if not ObjectMaterialData.is_loaded:
|
||||
ObjectMaterialData.load()
|
||||
constituents = ObjectMaterialData.data["active_material_constituents"]
|
||||
if any(c for c in constituents if c == shape_aspect):
|
||||
cls.poll_message_set(
|
||||
"Style comes from item's shape aspect related to the material constituent "
|
||||
"with the same name and cannot be edited on representation item directly - "
|
||||
"you can change it from the material constituent"
|
||||
)
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
class EnableEditingRepresentationItemStyle(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.enable_editing_representation_item_style"
|
||||
bl_label = "Enable Editing Representation Item Style"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return poll_editing_representaiton_item_style(cls, context)
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.active_object.BIMGeometryProperties
|
||||
props.is_editing_item_style = True
|
||||
ifc_file = tool.Ifc.get()
|
||||
|
||||
# set dropdown to currently active style
|
||||
representation_item_id = props.items[props.active_item_index].ifc_definition_id
|
||||
representation_item = ifc_file.by_id(representation_item_id)
|
||||
style = tool.Style.get_representation_item_style(representation_item)
|
||||
if style:
|
||||
props.representation_item_style = str(style.id())
|
||||
|
||||
|
||||
class EditRepresentationItemStyle(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.edit_representation_item_style"
|
||||
bl_label = "Edit Representation Item Style"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
props = obj.BIMGeometryProperties
|
||||
props.is_editing_item_style = False
|
||||
ifc_file = tool.Ifc.get()
|
||||
|
||||
surface_style = ifc_file.by_id(int(props.representation_item_style))
|
||||
representation_item_id = props.items[props.active_item_index].ifc_definition_id
|
||||
representation_item = ifc_file.by_id(representation_item_id)
|
||||
|
||||
tool.Style.assign_style_to_representation_item(representation_item, surface_style)
|
||||
tool.Geometry.reload_representation(obj)
|
||||
# reload items ui
|
||||
bpy.ops.bim.disable_editing_representation_items()
|
||||
bpy.ops.bim.enable_editing_representation_items()
|
||||
|
||||
|
||||
class DisableEditingRepresentationItemStyle(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.disable_editing_representation_item_style"
|
||||
bl_label = "Disable Editing Representation Item Style"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.active_object.BIMGeometryProperties
|
||||
props.is_editing_item_style = False
|
||||
|
||||
|
||||
class UnassignRepresentationItemStyle(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.unassign_representation_item_style"
|
||||
bl_label = "Unassign Representation Item Style"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return poll_editing_representaiton_item_style(cls, context)
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
props = obj.BIMGeometryProperties
|
||||
props.is_editing_item_style = False
|
||||
|
||||
# get active representation item
|
||||
representation_item_id = props.items[props.active_item_index].ifc_definition_id
|
||||
representation_item = tool.Ifc.get_entity_by_id(representation_item_id)
|
||||
if not representation_item:
|
||||
self.report({"ERROR"}, f"Couldn't find representation item by id {representation_item_id}.")
|
||||
return {"CANCELLED"}
|
||||
|
||||
tool.Style.assign_style_to_representation_item(representation_item, None)
|
||||
tool.Geometry.reload_representation(obj)
|
||||
# reload items ui
|
||||
bpy.ops.bim.disable_editing_representation_items()
|
||||
bpy.ops.bim.enable_editing_representation_items()
|
||||
|
||||
|
||||
class EnableEditingRepresentationItemShapeAspect(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.enable_editing_representation_item_shape_aspect"
|
||||
bl_label = "Enable Editing Representation Item Shape Aspect"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.active_object.BIMGeometryProperties
|
||||
props.is_editing_item_shape_aspect = True
|
||||
|
||||
# set dropdown to currently active shape aspect
|
||||
shape_aspect_id = props.items[props.active_item_index].shape_aspect_id
|
||||
if shape_aspect_id != 0:
|
||||
props.representation_item_shape_aspect = str(shape_aspect_id)
|
||||
|
||||
|
||||
class EditRepresentationItemShapeAspect(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.edit_representation_item_shape_aspect"
|
||||
bl_label = "Edit Representation Item Shape Aspect"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
props = obj.BIMGeometryProperties
|
||||
props.is_editing_item_shape_aspect = False
|
||||
ifc_file = tool.Ifc.get()
|
||||
|
||||
representation_item_id = props.items[props.active_item_index].ifc_definition_id
|
||||
representation_item = ifc_file.by_id(representation_item_id)
|
||||
|
||||
if props.representation_item_shape_aspect == "NEW":
|
||||
active_representation = tool.Geometry.get_active_representation(obj)
|
||||
# find IfcProductRepresentationSelect based on current representation
|
||||
if hasattr(element, "Representation"): # IfcProduct
|
||||
product_shape = element.Representation
|
||||
else: # IfcTypeProduct
|
||||
for representation_map in element.RepresentationMaps:
|
||||
if representation_map.MappedRepresentation == active_representation:
|
||||
product_shape = representation_map
|
||||
previous_shape_aspect_id = props.items[props.active_item_index].shape_aspect_id
|
||||
# will be None if item didn't had a shape aspect
|
||||
previous_shape_aspect = tool.Ifc.get_entity_by_id(previous_shape_aspect_id)
|
||||
shape_aspect = tool.Geometry.create_shape_aspect(
|
||||
product_shape, active_representation, [representation_item], previous_shape_aspect
|
||||
)
|
||||
else:
|
||||
shape_aspect = ifc_file.by_id(int(props.representation_item_shape_aspect))
|
||||
tool.Geometry.add_representation_item_to_shape_aspect([representation_item], shape_aspect)
|
||||
|
||||
# set attributes from UI
|
||||
shape_aspect_attrs = props.shape_aspect_attrs
|
||||
shape_aspect.Name = shape_aspect_attrs.name
|
||||
shape_aspect.Description = shape_aspect_attrs.description
|
||||
|
||||
shape_aspect_representation = tool.Geometry.get_shape_aspect_representation_for_item(
|
||||
shape_aspect, representation_item
|
||||
)
|
||||
styles = tool.Geometry.get_shape_aspect_styles(element, shape_aspect, representation_item)
|
||||
tool.Ifc.run(
|
||||
"style.assign_representation_styles",
|
||||
shape_representation=shape_aspect_representation,
|
||||
styles=styles,
|
||||
)
|
||||
tool.Geometry.get_shape_aspect_styles(element, shape_aspect, representation_item)
|
||||
tool.Geometry.reload_representation(obj)
|
||||
|
||||
# reload items ui
|
||||
bpy.ops.bim.disable_editing_representation_items()
|
||||
bpy.ops.bim.enable_editing_representation_items()
|
||||
|
||||
|
||||
class DisableEditingRepresentationItemShapeAspect(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.disable_editing_representation_item_shape_aspect"
|
||||
bl_label = "Disable Editing Representation Item Shape Aspect"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.active_object.BIMGeometryProperties
|
||||
props.is_editing_item_shape_aspect = False
|
||||
|
||||
|
||||
class RemoveRepresentationItemFromShapeAspect(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.remove_representation_item_from_shape_aspect"
|
||||
bl_label = "Remove Representation Item From Shape Aspect"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
props = obj.BIMGeometryProperties
|
||||
ifc_file = tool.Ifc.get()
|
||||
|
||||
representation_item_id = props.items[props.active_item_index].ifc_definition_id
|
||||
representation_item = ifc_file.by_id(representation_item_id)
|
||||
shape_aspect = ifc_file.by_id(props.items[props.active_item_index].shape_aspect_id)
|
||||
|
||||
# unassign items before removing items as removing items
|
||||
# might remove shape aspect
|
||||
if representation_item.StyledByItem:
|
||||
styles = tool.Geometry.get_shape_aspect_styles(element, shape_aspect, representation_item)
|
||||
self.remove_styles_from_item(representation_item, styles)
|
||||
tool.Geometry.reload_representation(obj)
|
||||
|
||||
tool.Geometry.remove_representation_items_from_shape_aspect([representation_item], shape_aspect)
|
||||
|
||||
# reload items ui
|
||||
bpy.ops.bim.disable_editing_representation_items()
|
||||
bpy.ops.bim.enable_editing_representation_items()
|
||||
|
||||
def remove_styles_from_item(self, representation_item, styles):
|
||||
ifc_file = tool.Ifc.get()
|
||||
styled_item = representation_item.StyledByItem[0]
|
||||
new_styles = [s for s in styled_item.Styles if s not in styles]
|
||||
styled_item.Styles = new_styles
|
||||
|
||||
for style_ in new_styles:
|
||||
if style_.is_a("IfcPresentationStyleAssignment"):
|
||||
new_assignment_styles = [s for s in styled_item.Styles if s not in styles]
|
||||
if not new_assignment_styles:
|
||||
ifc_file.remove(style_)
|
||||
else:
|
||||
style_.Styles = new_assignment_styles
|
||||
|
||||
if not styled_item.Styles:
|
||||
ifc_file.remove(styled_item)
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim.prop import StrProperty, Attribute
|
||||
from blenderbim.bim.module.geometry.data import RepresentationsData
|
||||
from bpy.types import PropertyGroup
|
||||
@@ -47,11 +48,63 @@ def update_mode(self, context):
|
||||
bpy.ops.bim.override_mode_set_edit("INVOKE_DEFAULT")
|
||||
|
||||
|
||||
def get_styles(self, context):
|
||||
# postponed import to avoid circular import
|
||||
from blenderbim.bim.module.material.data import MaterialsData
|
||||
|
||||
if not MaterialsData.is_loaded:
|
||||
MaterialsData.load()
|
||||
return MaterialsData.data["styles"]
|
||||
|
||||
|
||||
def get_shape_aspects(self, context):
|
||||
if not RepresentationsData.is_loaded:
|
||||
RepresentationsData.load()
|
||||
return RepresentationsData.data["shape_aspects"]
|
||||
|
||||
|
||||
def get_material_constituents(self, context, edit_text):
|
||||
from blenderbim.bim.module.material.data import ObjectMaterialData
|
||||
|
||||
if not ObjectMaterialData.is_loaded:
|
||||
ObjectMaterialData.load()
|
||||
return ObjectMaterialData.data["active_material_constituents"]
|
||||
|
||||
|
||||
def update_shape_aspect(self, context):
|
||||
shape_aspect_id = self.representation_item_shape_aspect
|
||||
attrs = self.shape_aspect_attrs
|
||||
|
||||
if shape_aspect_id == "NEW": # new shape aspect
|
||||
attrs.name = tool.Blender.get_blender_prop_default_value(attrs, "name")
|
||||
attrs.description = tool.Blender.get_blender_prop_default_value(attrs, "description")
|
||||
else:
|
||||
shape_aspect = tool.Ifc.get().by_id(int(shape_aspect_id))
|
||||
attrs.name = shape_aspect.Name or ""
|
||||
attrs.description = shape_aspect.Description or ""
|
||||
|
||||
|
||||
class RepresentationItem(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
surface_style: StringProperty(name="Surface Style")
|
||||
layer: StringProperty(name="Layer")
|
||||
ifc_definition_id: IntProperty(name="IFC Definition ID")
|
||||
shape_aspect: StringProperty(name="Shape Aspect")
|
||||
shape_aspect_id: IntProperty(name="Shape Aspect IFC ID")
|
||||
|
||||
|
||||
class ShapeAspect(PropertyGroup):
|
||||
name: StringProperty(
|
||||
name="Name",
|
||||
description=(
|
||||
"Note that IfcMaterialConstituent is applied based on shape aspects using the same name as material constituent.\n"
|
||||
"In dropdown suggestions you can see names of existing material constituents."
|
||||
),
|
||||
**({} if bpy.app.version < (3, 3, 0) else {"search": get_material_constituents}),
|
||||
)
|
||||
description: StringProperty(
|
||||
name="Description",
|
||||
)
|
||||
|
||||
|
||||
class BIMObjectGeometryProperties(PropertyGroup):
|
||||
@@ -59,6 +112,13 @@ class BIMObjectGeometryProperties(PropertyGroup):
|
||||
is_editing: BoolProperty(name="Is Editing", default=False)
|
||||
items: CollectionProperty(name="Representation Items", type=RepresentationItem)
|
||||
active_item_index: IntProperty(name="Active Representation Item Index")
|
||||
is_editing_item_style: BoolProperty(name="Is Editing Item's Style", default=False)
|
||||
representation_item_style: EnumProperty(items=get_styles, name="Representation Item Style")
|
||||
is_editing_item_shape_aspect: BoolProperty(name="Is Editing Item's Shape Aspect", default=False)
|
||||
representation_item_shape_aspect: EnumProperty(
|
||||
items=get_shape_aspects, name="Representation Item Shape Aspect", update=update_shape_aspect
|
||||
)
|
||||
shape_aspect_attrs: PointerProperty(type=ShapeAspect)
|
||||
|
||||
|
||||
class BIMGeometryProperties(PropertyGroup):
|
||||
|
||||
@@ -29,6 +29,7 @@ from blenderbim.bim.module.geometry.data import (
|
||||
PlacementData,
|
||||
DerivedCoordinatesData,
|
||||
)
|
||||
from blenderbim.bim.module.layer.data import LayersData
|
||||
|
||||
|
||||
def mode_menu(self, context):
|
||||
@@ -133,6 +134,7 @@ class BIM_PT_representations(Panel):
|
||||
|
||||
if not RepresentationsData.data["representations"]:
|
||||
layout.label(text="No Representations Found")
|
||||
return
|
||||
|
||||
for representation in RepresentationsData.data["representations"]:
|
||||
row = self.layout.row(align=True)
|
||||
@@ -150,6 +152,18 @@ class BIM_PT_representations(Panel):
|
||||
op.ifc_definition_id = representation["id"]
|
||||
op.disable_opening_subtractions = False
|
||||
row.operator("bim.remove_representation", icon="X", text="").representation_id = representation["id"]
|
||||
if representation["is_active"]:
|
||||
active_representation = representation
|
||||
|
||||
layout.separator()
|
||||
if not LayersData.is_loaded:
|
||||
LayersData.load()
|
||||
if LayersData.data["active_layers"]:
|
||||
layout.label(text="Representation Presentation Layers:")
|
||||
for layer_name in LayersData.data["active_layers"].values():
|
||||
layout.label(text=layer_name, icon="STICKY_UVS_LOC")
|
||||
else:
|
||||
layout.label(text="Representation Has No Presentation Layers", icon="STICKY_UVS_LOC")
|
||||
|
||||
|
||||
class BIM_PT_representation_items(Panel):
|
||||
@@ -187,20 +201,47 @@ class BIM_PT_representation_items(Panel):
|
||||
self.layout.template_list("BIM_UL_representation_items", "", props, "items", props, "active_item_index")
|
||||
|
||||
item_is_active = props.active_item_index < len(props.items)
|
||||
if item_is_active:
|
||||
surface_style = props.items[props.active_item_index].surface_style
|
||||
presentation_layer = props.items[props.active_item_index].layer
|
||||
if not item_is_active:
|
||||
return
|
||||
|
||||
active_item = props.items[props.active_item_index]
|
||||
surface_style = active_item.surface_style
|
||||
shape_aspect = active_item.shape_aspect
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
if props.is_editing_item_style:
|
||||
# NOTE: we currently support 1 item having just 1 style
|
||||
# when IfcStyledItem can actually have multiple styles
|
||||
row.prop(props, "representation_item_style", icon="SHADING_RENDERED", text="")
|
||||
row.operator("bim.edit_representation_item_style", icon="CHECKMARK", text="")
|
||||
row.operator("bim.disable_editing_representation_item_style", icon="CANCEL", text="")
|
||||
else:
|
||||
surface_style, presentation_layer = None, None
|
||||
if surface_style:
|
||||
row.label(text=surface_style, icon="SHADING_RENDERED")
|
||||
else:
|
||||
row.label(text="No Surface Style", icon="MESH_UVSPHERE")
|
||||
row.operator("bim.enable_editing_representation_item_style", icon="GREASEPENCIL", text="")
|
||||
if surface_style:
|
||||
row.operator("bim.unassign_representation_item_style", icon="X", text="")
|
||||
|
||||
row = self.layout.row()
|
||||
if surface_style:
|
||||
row.label(text=surface_style, icon="SHADING_RENDERED")
|
||||
else:
|
||||
row.label(text="No Surface Style", icon="MESH_UVSPHERE")
|
||||
row.label(text=active_item.layer or "No Presentation Layer", icon="STICKY_UVS_LOC")
|
||||
|
||||
row = self.layout.row()
|
||||
row.label(text=presentation_layer or "No Presentation Layer", icon="STICKY_UVS_LOC")
|
||||
row = self.layout.row(align=True)
|
||||
if props.is_editing_item_shape_aspect:
|
||||
row.prop(props, "representation_item_shape_aspect", icon="SHAPEKEY_DATA", text="")
|
||||
row.operator("bim.edit_representation_item_shape_aspect", icon="CHECKMARK", text="")
|
||||
row.operator("bim.disable_editing_representation_item_shape_aspect", icon="CANCEL", text="")
|
||||
|
||||
shape_aspect_attrs = props.shape_aspect_attrs
|
||||
self.layout.label(text="Shape Aspect Attributes:")
|
||||
self.layout.prop(shape_aspect_attrs, "name")
|
||||
self.layout.prop(shape_aspect_attrs, "description")
|
||||
else:
|
||||
row.label(text=shape_aspect or "No Shape Aspect", icon="SHAPEKEY_DATA")
|
||||
row.operator("bim.enable_editing_representation_item_shape_aspect", icon="GREASEPENCIL", text="")
|
||||
if shape_aspect:
|
||||
row.operator("bim.remove_representation_item_from_shape_aspect", icon="X", text="")
|
||||
|
||||
|
||||
class BIM_PT_connections(Panel):
|
||||
@@ -466,6 +507,11 @@ class BIM_UL_representation_items(UIList):
|
||||
if item:
|
||||
icon = "MATERIAL" if item.surface_style else "MESH_UVSPHERE"
|
||||
row = layout.row(align=True)
|
||||
row.label(text=item.name, icon=icon)
|
||||
item_name = item.name
|
||||
if item.shape_aspect:
|
||||
item_name = f"{item.shape_aspect} {item_name}"
|
||||
row.label(text=item_name, icon=icon)
|
||||
if item.layer:
|
||||
row.label(text="", icon="STICKY_UVS_LOC")
|
||||
op = row.operator("bim.remove_representation_item", icon="X", text="")
|
||||
op.representation_item_id = item.ifc_definition_id
|
||||
|
||||
@@ -40,14 +40,15 @@ class LayersData:
|
||||
|
||||
@classmethod
|
||||
def active_layers(cls):
|
||||
if not bpy.context.active_object:
|
||||
return []
|
||||
data = bpy.context.active_object.data
|
||||
if not data:
|
||||
return []
|
||||
if not isinstance(data, bpy.types.Mesh) or not data.BIMMeshProperties.ifc_definition_id:
|
||||
return []
|
||||
results = []
|
||||
results = dict()
|
||||
shape = tool.Ifc.get().by_id(data.BIMMeshProperties.ifc_definition_id)
|
||||
for inverse in tool.Ifc.get().get_inverse(shape):
|
||||
if inverse.is_a("IfcPresentationLayerAssignment"):
|
||||
results.append(inverse.id())
|
||||
for inverse in shape.LayerAssignments:
|
||||
results[inverse.id()] = inverse.Name or "Unnamed"
|
||||
return results
|
||||
|
||||
@@ -137,6 +137,7 @@ class RemovePresentationLayer(bpy.types.Operator):
|
||||
class AssignPresentationLayer(bpy.types.Operator):
|
||||
bl_idname = "bim.assign_presentation_layer"
|
||||
bl_label = "Assign Presentation Layer"
|
||||
bl_description = "Assign presentation layer to the active representation of the active object"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
item: bpy.props.StringProperty()
|
||||
layer: bpy.props.IntProperty()
|
||||
@@ -158,6 +159,7 @@ class AssignPresentationLayer(bpy.types.Operator):
|
||||
class UnassignPresentationLayer(bpy.types.Operator):
|
||||
bl_idname = "bim.unassign_presentation_layer"
|
||||
bl_label = "Unassign Presentation Layer"
|
||||
bl_description = "Unassign presentation layer from the active representation of the active object"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
item: bpy.props.StringProperty()
|
||||
layer: bpy.props.IntProperty()
|
||||
|
||||
@@ -84,6 +84,7 @@ class BIM_UL_layers(UIList):
|
||||
op = row.operator("bim.assign_presentation_layer", text="", icon="KEYFRAME", emboss=False)
|
||||
op.layer = item.ifc_definition_id
|
||||
|
||||
# TODO: replace placeholder UI for hiding presentation layers
|
||||
row.operator("bim.disable_editing_layer", text="", icon="HIDE_OFF", emboss=False)
|
||||
row.operator("bim.disable_editing_layer", text="", icon="FREEZE", emboss=False)
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
import os
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.doc
|
||||
import ifcopenshell.util.schema
|
||||
import blenderbim.tool as tool
|
||||
|
||||
@@ -109,29 +110,41 @@ class MaterialsData:
|
||||
def active_styles(cls):
|
||||
props = bpy.context.scene.BIMMaterialProperties
|
||||
results = []
|
||||
if props.materials and props.active_material_index < len(props.materials):
|
||||
material = props.materials[props.active_material_index].ifc_definition_id
|
||||
if not material:
|
||||
return results
|
||||
material = tool.Ifc.get().by_id(material)
|
||||
for definition in material.HasRepresentation:
|
||||
for representation in definition.Representations:
|
||||
if not representation.is_a("IfcStyledRepresentation"):
|
||||
if not props.materials or props.active_material_index >= len(props.materials):
|
||||
return results
|
||||
|
||||
material = props.materials[props.active_material_index].ifc_definition_id
|
||||
if not material:
|
||||
return results
|
||||
|
||||
material = tool.Ifc.get_entity_by_id(material)
|
||||
|
||||
# NOTE: it's possible that data will be refreshed during material removal
|
||||
# (when it will try to fetch active material type and will reach for material_types)
|
||||
# and props.materials[i].ifc_definition_id will contain already removed id
|
||||
if not material or not material.is_a("IfcMaterial"):
|
||||
return results
|
||||
|
||||
for definition in material.HasRepresentation:
|
||||
for representation in definition.Representations:
|
||||
if not representation.is_a("IfcStyledRepresentation"):
|
||||
continue
|
||||
context = representation.ContextOfItems
|
||||
for item in representation.Items:
|
||||
if not item.is_a("IfcStyledItem"):
|
||||
continue
|
||||
context = representation.ContextOfItems
|
||||
for item in representation.Items:
|
||||
if not item.is_a("IfcStyledItem"):
|
||||
continue
|
||||
for style in item.Styles:
|
||||
if style.is_a("IfcSurfaceStyle"):
|
||||
results.append({
|
||||
for style in item.Styles:
|
||||
if style.is_a("IfcSurfaceStyle"):
|
||||
results.append(
|
||||
{
|
||||
"context_type": context.ContextType,
|
||||
"context_identifier": getattr(context, "ContextIdentifier", ""),
|
||||
"target_view": getattr(context, "TargetView", ""),
|
||||
"name": style.Name or "Unnamed",
|
||||
"id": style.id(),
|
||||
"context_id": context.id(),
|
||||
})
|
||||
}
|
||||
)
|
||||
return results
|
||||
|
||||
|
||||
@@ -152,6 +165,7 @@ class ObjectMaterialData:
|
||||
cls.data["materials"] = cls.materials()
|
||||
cls.data["type_material"] = cls.type_material()
|
||||
cls.data["material_type"] = cls.material_type()
|
||||
cls.data["active_material_constituents"] = cls.active_material_constituents()
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
@@ -318,3 +332,10 @@ class ObjectMaterialData:
|
||||
if version == "IFC2X3":
|
||||
material_types = ["IfcMaterial", "IfcMaterialLayerSet", "IfcMaterialList"]
|
||||
return [(m, m, ifcopenshell.util.doc.get_entity_doc(version, m).get("description", "")) for m in material_types]
|
||||
|
||||
@classmethod
|
||||
def active_material_constituents(cls):
|
||||
material = cls.material
|
||||
if not cls.material or not material.is_a("IfcMaterialConstituentSet"):
|
||||
return []
|
||||
return [m.Name for m in material.MaterialConstituents if m.Name]
|
||||
|
||||
@@ -34,6 +34,7 @@ from blenderbim.bim.ifc import IfcStore
|
||||
class LoadMaterials(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.load_materials"
|
||||
bl_label = "Load Materials"
|
||||
bl_description = "Display list of named materials"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
@@ -151,7 +152,10 @@ class RemoveMaterial(bpy.types.Operator, tool.Ifc.Operator):
|
||||
material: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.remove_material(tool.Ifc, tool.Material, tool.Style, material=tool.Ifc.get().by_id(self.material))
|
||||
res = core.remove_material(tool.Ifc, tool.Material, tool.Style, material=tool.Ifc.get().by_id(self.material))
|
||||
if not res:
|
||||
self.report({"ERROR"}, "Material is used in material sets and cannot be removed.")
|
||||
return {"CANCELLED"}
|
||||
|
||||
|
||||
class RemoveMaterialSet(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -270,6 +274,7 @@ class RemoveProfile(bpy.types.Operator, tool.Ifc.Operator):
|
||||
class AddLayer(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.add_layer"
|
||||
bl_label = "Add Layer"
|
||||
bl_description = "The List is Ordered From Origin Point"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
layer_set: bpy.props.IntProperty()
|
||||
@@ -294,6 +299,7 @@ class AddLayer(bpy.types.Operator, tool.Ifc.Operator):
|
||||
class ReorderMaterialSetItem(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.reorder_material_set_item"
|
||||
bl_label = "Reorder Material Set Item"
|
||||
bl_description = "The List is Ordered From Origin Point"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
old_index: bpy.props.IntProperty()
|
||||
@@ -728,6 +734,20 @@ class EnableEditingMaterialStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
props.active_material_id = self.material
|
||||
props.editing_material_type = "STYLE"
|
||||
|
||||
# set already applied style and context if possible
|
||||
if not 0 <= props.active_material_index < len(props.materials):
|
||||
return
|
||||
|
||||
material = tool.Ifc.get().by_id(props.materials[props.active_material_index].ifc_definition_id)
|
||||
if not material.HasRepresentation:
|
||||
return
|
||||
|
||||
rep = material.HasRepresentation[0].Representations[0] # IfcStyledRepresentation
|
||||
props.contexts = str(rep.ContextOfItems.id())
|
||||
style = rep.Items[0].Styles[0]
|
||||
if style.Name: # props.styles only has named styles
|
||||
props.styles = str(rep.Items[0].Styles[0].id())
|
||||
|
||||
|
||||
class EditMaterialStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.edit_material_style"
|
||||
@@ -736,14 +756,19 @@ class EditMaterialStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
def _execute(self, context):
|
||||
props = bpy.context.scene.BIMMaterialProperties
|
||||
material = tool.Ifc.get().by_id(props.active_material_id)
|
||||
style = tool.Ifc.get().by_id(int(props.styles))
|
||||
context = tool.Ifc.get().by_id(int(props.contexts))
|
||||
ifcopenshell.api.run(
|
||||
"style.assign_material_style", tool.Ifc.get(), material=material, style=style, context=context
|
||||
)
|
||||
ifc_file = tool.Ifc.get()
|
||||
material = ifc_file.by_id(props.active_material_id)
|
||||
style = ifc_file.by_id(int(props.styles))
|
||||
context = ifc_file.by_id(int(props.contexts))
|
||||
ifcopenshell.api.run("style.assign_material_style", ifc_file, material=material, style=style, context=context)
|
||||
tool.Material.disable_editing_material()
|
||||
core.load_materials(tool.Material, props.material_type)
|
||||
# NOTE: Update all elements that has this material and
|
||||
# not just the ones that are using it in IfcMaterialConstituent,
|
||||
# to handle cases where Style is connected to geometry implicitly:
|
||||
# e.g. RepItem->IfcElement->IfcMaterial->Style
|
||||
# instead of RepItem->ShapeAspect->Constituent->Style.
|
||||
tool.Material.update_elements_using_material(material)
|
||||
|
||||
|
||||
class UnassignMaterialStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -762,3 +787,4 @@ class UnassignMaterialStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
"style.unassign_material_style", tool.Ifc.get(), material=material, style=style, context=context
|
||||
)
|
||||
core.load_materials(tool.Material, props.material_type)
|
||||
tool.Material.update_elements_using_material(material)
|
||||
|
||||
@@ -81,6 +81,8 @@ class BIM_PT_materials(Panel):
|
||||
|
||||
self.layout.template_list("BIM_UL_materials", "", self.props, "materials", self.props, "active_material_index")
|
||||
|
||||
# TODO: data.py is not updated on changing active_material_index
|
||||
# so the active material styles go out of sync with the active material
|
||||
for style in MaterialsData.data["active_styles"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="", icon="SHADING_RENDERED")
|
||||
@@ -228,7 +230,7 @@ class BIM_PT_object_material(Panel):
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="No Profiles Available")
|
||||
row.operator("bim.add_profile_def", icon="ADD", text="")
|
||||
else:
|
||||
else:
|
||||
row = self.layout.row(align=True)
|
||||
if ObjectMaterialData.data["set_item_name"] == "profile":
|
||||
prop_with_search(row, self.mprops, "profiles", icon="ITALIC", text="")
|
||||
|
||||
@@ -113,6 +113,7 @@ classes = (
|
||||
covering.AddInstanceCeilingCoveringFromCursor,
|
||||
covering.RegenSelectedCoveringObject,
|
||||
space.ToggleSpaceVisibility,
|
||||
space.ToggleHideSpaces,
|
||||
mep.FitFlowSegments,
|
||||
mep.RegenerateDistributionElement,
|
||||
prop.BIMModelProperties,
|
||||
@@ -193,15 +194,18 @@ addon_keymaps = []
|
||||
|
||||
def register():
|
||||
if not bpy.app.background:
|
||||
bpy.utils.register_tool(workspace.WallTool, after={"builtin.transform"}, separator=True, group=False)
|
||||
bpy.utils.register_tool(workspace.WallTool, after={"bim.explore_tool"}, separator=True, group=False)
|
||||
bpy.utils.register_tool(workspace.SlabTool, after={"bim.wall_tool"}, separator=False, group=False)
|
||||
bpy.utils.register_tool(workspace.DoorTool, after={"bim.slab_tool"}, separator=False, group=False)
|
||||
bpy.utils.register_tool(workspace.WindowTool, after={"bim.door_tool"}, separator=False, group=False)
|
||||
bpy.utils.register_tool(workspace.ColumnTool, after={"bim.window_tool"}, separator=False, group=False)
|
||||
bpy.utils.register_tool(workspace.BeamTool, after={"bim.column_tool"}, separator=False, group=False)
|
||||
bpy.utils.register_tool(workspace.DuctTool, after={"bim.beam_tool"}, separator=False, group=False)
|
||||
bpy.utils.register_tool(workspace.BimTool, after={"bim.beam_tool"}, separator=False, group=False)
|
||||
bpy.utils.register_tool(workspace.DuctTool, after={"bim.beam_tool"}, separator=False, group=True)
|
||||
bpy.utils.register_tool(workspace.PipeTool, after={"bim.duct_tool"}, separator=False, group=False)
|
||||
bpy.utils.register_tool(workspace.BimTool, after={"bim.pipe_tool"}, separator=False, group=False)
|
||||
bpy.utils.register_tool(workspace.CableCarrierTool, after={"bim.pipe_tool"}, separator=False, group=False)
|
||||
bpy.utils.register_tool(workspace.CableTool, after={"bim.cable_carrier_tool"}, separator=False, group=False)
|
||||
|
||||
bpy.types.Scene.BIMModelProperties = bpy.props.PointerProperty(type=prop.BIMModelProperties)
|
||||
bpy.types.Object.BIMArrayProperties = bpy.props.PointerProperty(type=prop.BIMArrayProperties)
|
||||
bpy.types.Object.BIMStairProperties = bpy.props.PointerProperty(type=prop.BIMStairProperties)
|
||||
@@ -210,6 +214,7 @@ def register():
|
||||
bpy.types.Object.BIMDoorProperties = bpy.props.PointerProperty(type=prop.BIMDoorProperties)
|
||||
bpy.types.Object.BIMRailingProperties = bpy.props.PointerProperty(type=prop.BIMRailingProperties)
|
||||
bpy.types.Object.BIMRoofProperties = bpy.props.PointerProperty(type=prop.BIMRoofProperties)
|
||||
|
||||
bpy.types.VIEW3D_MT_mesh_add.append(ui.add_mesh_object_menu)
|
||||
bpy.types.VIEW3D_MT_add.append(ui.add_menu)
|
||||
bpy.app.handlers.load_post.append(handler.load_post)
|
||||
@@ -231,6 +236,8 @@ def unregister():
|
||||
bpy.utils.unregister_tool(workspace.BeamTool)
|
||||
bpy.utils.unregister_tool(workspace.DuctTool)
|
||||
bpy.utils.unregister_tool(workspace.PipeTool)
|
||||
bpy.utils.unregister_tool(workspace.CableCarrierTool)
|
||||
bpy.utils.unregister_tool(workspace.CableTool)
|
||||
bpy.utils.unregister_tool(workspace.BimTool)
|
||||
del bpy.types.Scene.BIMModelProperties
|
||||
del bpy.types.Object.BIMArrayProperties
|
||||
|
||||
@@ -33,7 +33,7 @@ import blenderbim.core.geometry
|
||||
import blenderbim.bim.import_ifc as import_ifc
|
||||
import blenderbim.bim.handler
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from math import pi
|
||||
from math import pi, radians
|
||||
from mathutils import Vector, Matrix
|
||||
from bpy.types import Operator
|
||||
from bpy.types import SpaceView3D
|
||||
@@ -87,20 +87,28 @@ class FilledOpeningGenerator:
|
||||
|
||||
# In this prototype, we assume openings are only added to axis-based elements
|
||||
layers = tool.Model.get_material_layer_parameters(element)
|
||||
axis = tool.Model.get_wall_axis(voided_obj, layers=layers)["base"]
|
||||
if layers["layer_set_direction"] == "AXIS2":
|
||||
axis = tool.Model.get_wall_axis(voided_obj, layers=layers)["base"]
|
||||
new_matrix = voided_obj.matrix_world.copy()
|
||||
point_on_axis = tool.Cad.point_on_edge(target, axis)
|
||||
new_matrix.translation.x = point_on_axis.x
|
||||
new_matrix.translation.y = point_on_axis.y
|
||||
|
||||
new_matrix = voided_obj.matrix_world.copy()
|
||||
point_on_axis = tool.Cad.point_on_edge(target, axis)
|
||||
new_matrix.translation.x = point_on_axis.x
|
||||
new_matrix.translation.y = point_on_axis.y
|
||||
|
||||
if should_set_z_level:
|
||||
if filling.is_a("IfcDoor"):
|
||||
new_matrix.translation.z = voided_obj.matrix_world.translation.z
|
||||
if should_set_z_level:
|
||||
if filling.is_a("IfcDoor"):
|
||||
new_matrix.translation.z = voided_obj.matrix_world.translation.z
|
||||
else:
|
||||
new_matrix.translation.z = voided_obj.matrix_world.translation.z + props.rl2
|
||||
else:
|
||||
new_matrix.translation.z = voided_obj.matrix_world.translation.z + props.rl2
|
||||
else:
|
||||
new_matrix.translation.z = filling_obj.matrix_world.copy().translation.z
|
||||
new_matrix.translation.z = filling_obj.matrix_world.copy().translation.z
|
||||
elif layers["layer_set_direction"] == "AXIS3":
|
||||
new_matrix = voided_obj.matrix_world.copy()
|
||||
local_position_on_voided_obj = raycast[1]
|
||||
# Equivalent to "side Z" for a wall axis, so that stuff like skylights appear on the top.
|
||||
local_position_on_voided_obj.z = layers["offset"] + layers["thickness"]
|
||||
new_matrix.translation.xyz = voided_obj.matrix_world @ local_position_on_voided_obj
|
||||
rotation_matrix = Matrix.Rotation(radians(-90), 4, 'X')
|
||||
new_matrix @= rotation_matrix
|
||||
|
||||
filling_obj.matrix_world = new_matrix
|
||||
bpy.context.view_layer.update()
|
||||
@@ -521,6 +529,7 @@ class AddBoolean(Operator, tool.Ifc.Operator):
|
||||
return {"FINISHED"}
|
||||
if element2 and not element1:
|
||||
obj1, obj2 = obj2, obj1
|
||||
element1, element2 = element2, element1
|
||||
if not obj1.data or not hasattr(obj1.data, "BIMMeshProperties"):
|
||||
return {"FINISHED"}
|
||||
representation = tool.Ifc.get().by_id(obj1.data.BIMMeshProperties.ifc_definition_id)
|
||||
@@ -668,17 +677,17 @@ class RemoveBooleans(Operator, tool.Ifc.Operator, AddObjectHelper):
|
||||
except:
|
||||
continue
|
||||
|
||||
boolean = None
|
||||
boolean_id = None
|
||||
for inverse in tool.Ifc.get().get_inverse(item):
|
||||
if inverse.is_a("IfcBooleanResult"):
|
||||
boolean = inverse
|
||||
boolean_id = inverse.id()
|
||||
break
|
||||
ifcopenshell.api.run("geometry.remove_boolean", tool.Ifc.get(), item=item)
|
||||
|
||||
if obj.data.BIMMeshProperties.obj:
|
||||
upstream_obj = obj.data.BIMMeshProperties.obj
|
||||
element = tool.Ifc.get_entity(upstream_obj)
|
||||
bbim_boolean_updates.setdefault(element, []).append(boolean)
|
||||
bbim_boolean_updates.setdefault(element, []).append(boolean_id)
|
||||
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||
if body:
|
||||
blenderbim.core.geometry.switch_representation(
|
||||
@@ -692,8 +701,8 @@ class RemoveBooleans(Operator, tool.Ifc.Operator, AddObjectHelper):
|
||||
)
|
||||
bpy.data.objects.remove(obj)
|
||||
|
||||
for element, booleans in bbim_boolean_updates.items():
|
||||
tool.Model.unmark_manual_booleans(element, booleans)
|
||||
for element, boolean_ids in bbim_boolean_updates.items():
|
||||
tool.Model.unmark_manual_booleans(element, boolean_ids)
|
||||
|
||||
tool.Blender.set_active_object(upstream_obj)
|
||||
return {"FINISHED"}
|
||||
@@ -761,8 +770,6 @@ class EditOpenings(Operator, tool.Ifc.Operator):
|
||||
def _execute(self, context):
|
||||
props = bpy.context.scene.BIMModelProperties
|
||||
building_objs = set()
|
||||
model = tool.Ifc.get()
|
||||
all_openings = model.by_type("IfcOpeningElement")
|
||||
|
||||
for obj in context.selected_objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
@@ -773,23 +780,32 @@ class EditOpenings(Operator, tool.Ifc.Operator):
|
||||
obj = tool.Ifc.get_object(element)
|
||||
openings = [r.RelatedOpeningElement for r in element.HasOpenings]
|
||||
for opening in openings:
|
||||
similar_openings = [o for o in all_openings if o.ObjectPlacement == opening.ObjectPlacement]
|
||||
opening_obj = tool.Ifc.get_object(opening)
|
||||
building_objs.add(obj)
|
||||
|
||||
similar_openings = blenderbim.core.geometry.get_similar_openings(tool.Ifc, opening)
|
||||
similar_openings_building_objs = blenderbim.core.geometry.get_similar_openings_building_objs(
|
||||
tool.Ifc, similar_openings
|
||||
)
|
||||
building_objs.update(similar_openings_building_objs)
|
||||
|
||||
if opening_obj:
|
||||
if tool.Ifc.is_edited(opening_obj):
|
||||
tool.Geometry.run_geometry_update_representation(obj=opening_obj)
|
||||
blenderbim.core.geometry.edit_similar_opening_placement(
|
||||
tool.Geometry, opening, similar_openings
|
||||
)
|
||||
elif tool.Ifc.is_moved(opening_obj):
|
||||
blenderbim.core.geometry.edit_object_placement(
|
||||
tool.Ifc, tool.Geometry, tool.Surveyor, obj=opening_obj
|
||||
)
|
||||
for similar_opening in similar_openings:
|
||||
similar_opening.ObjectPlacement = opening.ObjectPlacement
|
||||
element = similar_opening.VoidsElements[0].RelatingBuildingElement
|
||||
obj = tool.Ifc.get_object(element)
|
||||
building_objs.add(obj)
|
||||
blenderbim.core.geometry.edit_similar_opening_placement(
|
||||
tool.Geometry, opening, similar_openings
|
||||
)
|
||||
|
||||
building_objs.update(self.get_all_building_objects_of_similar_openings(opening))
|
||||
building_objs.update(
|
||||
self.get_all_building_objects_of_similar_openings(opening)
|
||||
) # NB this has nothing to do with clone similar_opening
|
||||
tool.Ifc.unlink(element=opening, obj=opening_obj)
|
||||
bpy.data.objects.remove(opening_obj)
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -1013,17 +1013,30 @@ class EditExtrusionAxis(bpy.types.Operator, tool.Ifc.Operator):
|
||||
obj = context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
|
||||
start = obj.matrix_world @ obj.data.vertices[0].co.copy()
|
||||
end = obj.matrix_world @ obj.data.vertices[1].co.copy()
|
||||
matrix = obj.matrix_world
|
||||
previous_z_axis = matrix.col[2].to_3d().normalized()
|
||||
|
||||
start = matrix @ obj.data.vertices[0].co
|
||||
end = matrix @ obj.data.vertices[1].co
|
||||
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()
|
||||
# if z-axis didn't changed we can just reuse the previous rotation
|
||||
if not tool.Cad.are_vectors_equal(previous_z_axis, z_axis):
|
||||
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()
|
||||
|
||||
# update basises
|
||||
matrix.col[0].xyz = x_axis
|
||||
matrix.col[1].xyz = y_axis
|
||||
matrix.col[2].xyz = z_axis
|
||||
|
||||
matrix.translation = start
|
||||
|
||||
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||
blenderbim.core.geometry.switch_representation(
|
||||
@@ -1036,16 +1049,6 @@ class EditExtrusionAxis(bpy.types.Operator, tool.Ifc.Operator):
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
matrix = Matrix(
|
||||
(
|
||||
[x_axis[0], y_axis[0], z_axis[0], start.x],
|
||||
[x_axis[1], y_axis[1], z_axis[1], start.y],
|
||||
[x_axis[2], y_axis[2], z_axis[2], start.z],
|
||||
[0, 0, 0, 1],
|
||||
)
|
||||
)
|
||||
|
||||
obj.matrix_world = matrix
|
||||
bpy.context.view_layer.update()
|
||||
|
||||
joiner = DumbProfileJoiner()
|
||||
|
||||
@@ -99,7 +99,10 @@ class BIMModelProperties(PropertyGroup):
|
||||
items=[("CLASS", "By Class", ""), ("TYPE", "By Type", ""), ("CUSTOM", "Custom", "")],
|
||||
name="Occurrence Name Style",
|
||||
)
|
||||
occurrence_name_function: bpy.props.StringProperty(name="Occurrence Name Function")
|
||||
occurrence_name_function: bpy.props.StringProperty(
|
||||
name="Occurrence Name Function",
|
||||
description="Code that will be evaluated to generate occurrence name for CUSTOM occurrence name style",
|
||||
)
|
||||
getter_enum = {"ifc_class": get_ifc_class, "relating_type": get_relating_type_id}
|
||||
extrusion_depth: bpy.props.FloatProperty(min=0.001, default=42.0, subtype="DISTANCE")
|
||||
cardinal_point: bpy.props.EnumProperty(
|
||||
@@ -234,7 +237,6 @@ class BIMStairProperties(PropertyGroup):
|
||||
unit="LENGTH",
|
||||
size=2,
|
||||
)
|
||||
# TODO: need to clamp at zero for non WOOD/STEEL
|
||||
nosing_length: bpy.props.FloatProperty(
|
||||
name="Nosing Length",
|
||||
description=(
|
||||
@@ -712,8 +714,15 @@ class BIMRoofProperties(PropertyGroup):
|
||||
height: bpy.props.FloatProperty(
|
||||
name="Height", default=1.0, description="Maximum height of the roof to be generated.", subtype="DISTANCE"
|
||||
)
|
||||
angle: bpy.props.FloatProperty(name="Slope Angle", default=pi / 18, subtype="ANGLE", update=lambda self, context: self.update_percentage())
|
||||
percentage: bpy.props.FloatProperty(name="Slope %", default=17.63269754733197, subtype="PERCENTAGE", update=lambda self, context: self.update_angle())
|
||||
angle: bpy.props.FloatProperty(
|
||||
name="Slope Angle", default=pi / 18, subtype="ANGLE", update=lambda self, context: self.update_percentage()
|
||||
)
|
||||
percentage: bpy.props.FloatProperty(
|
||||
name="Slope %",
|
||||
default=math.tan(pi / 18) * 100,
|
||||
subtype="PERCENTAGE",
|
||||
update=lambda self, context: self.update_angle(),
|
||||
)
|
||||
roof_thickness: bpy.props.FloatProperty(name="Roof Thickness", default=0.1, subtype="DISTANCE")
|
||||
rafter_edge_angle: bpy.props.FloatProperty(name="Rafter Edge Angle", min=0, max=pi, default=pi / 2, subtype="ANGLE")
|
||||
|
||||
@@ -745,4 +754,4 @@ class BIMRoofProperties(PropertyGroup):
|
||||
self.angle = math.atan(self.percentage / 100)
|
||||
|
||||
def update_percentage(self):
|
||||
self.percentage = math.tan(self.angle)*100
|
||||
self.percentage = math.tan(self.angle) * 100
|
||||
|
||||
@@ -29,13 +29,19 @@ class GenerateSpace(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.generate_space"
|
||||
bl_label = "Generate Space"
|
||||
bl_options = {"REGISTER"}
|
||||
bl_description = "Create a space from the cursor position. Move the cursor position into the desired position, select the right space collection and run the operator"
|
||||
bl_description = (
|
||||
"Create a space from the cursor position. "
|
||||
"Move the cursor position into the desired position, "
|
||||
"select the right space collection and run the operator"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
collection = context.view_layer.active_layer_collection.collection
|
||||
collection_obj = collection.BIMCollectionProperties.obj
|
||||
return tool.Ifc.get_entity(collection_obj)
|
||||
active_obj = context.active_object
|
||||
element = tool.Ifc.get_entity(active_obj)
|
||||
return tool.Ifc.get_entity(collection_obj) and not element.is_a("IfcWall")
|
||||
|
||||
def _execute(self, context):
|
||||
# This works as a 2.5 extruded polygon based on a cutting plane. Note
|
||||
@@ -45,16 +51,14 @@ class GenerateSpace(bpy.types.Operator, tool.Ifc.Operator):
|
||||
def msg(self, context):
|
||||
self.layout.label(text="NO ACTIVE STOREY")
|
||||
|
||||
# props = context.scene.BIMModelProperties
|
||||
|
||||
collection = context.view_layer.active_layer_collection.collection
|
||||
collection_obj = collection.BIMCollectionProperties.obj
|
||||
if not collection_obj:
|
||||
bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
|
||||
context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
|
||||
return
|
||||
spatial_element = tool.Ifc.get_entity(collection_obj)
|
||||
if not spatial_element:
|
||||
bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
|
||||
context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
|
||||
return
|
||||
|
||||
core.generate_space(tool.Ifc, tool.Spatial, tool.Model, tool.Type)
|
||||
@@ -68,7 +72,7 @@ class GenerateSpacesFromWalls(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
active_obj = bpy.context.active_object
|
||||
active_obj = context.active_object
|
||||
element = tool.Ifc.get_entity(active_obj)
|
||||
if element:
|
||||
return context.selected_objects and element.is_a("IfcWall")
|
||||
@@ -79,7 +83,7 @@ class GenerateSpacesFromWalls(bpy.types.Operator, tool.Ifc.Operator):
|
||||
# In order to run, the active object must be a wall and
|
||||
# there must be selected walls
|
||||
|
||||
active_obj = bpy.context.active_object
|
||||
active_obj = context.active_object
|
||||
element = tool.Ifc.get_entity(active_obj)
|
||||
container = tool.Spatial.get_container(element)
|
||||
|
||||
@@ -94,19 +98,30 @@ class GenerateSpacesFromWalls(bpy.types.Operator, tool.Ifc.Operator):
|
||||
if not container:
|
||||
self.report({"ERROR"}, "The wall is not contained.")
|
||||
|
||||
if not bpy.context.selected_objects:
|
||||
if not context.selected_objects:
|
||||
self.report({"ERROR"}, "No selected objects found. Please select walls.")
|
||||
return
|
||||
|
||||
core.generate_spaces_from_walls(tool.Ifc, tool.Spatial, tool.Collector)
|
||||
|
||||
|
||||
class ToggleSpaceVisibility(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.toggle_space_visibility"
|
||||
bl_label = "Toggle Space Visibility"
|
||||
bl_options = {"REGISTER"}
|
||||
bl_description = "Change the space visibility"
|
||||
|
||||
def execute(cls, context):
|
||||
def execute(self, context):
|
||||
core.toggle_space_visibility(tool.Ifc, tool.Spatial)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ToggleHideSpaces(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.toggle_hide_spaces"
|
||||
bl_label = "Toggle Hide Spaces"
|
||||
bl_options = {"REGISTER"}
|
||||
bl_description = "Hide or Unhide all spaces"
|
||||
|
||||
def execute(self, context):
|
||||
core.toggle_hide_spaces(tool.Ifc, tool.Spatial)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -1120,11 +1120,11 @@ class DumbWallJoiner:
|
||||
other = tool.Ifc.get_object(rel.RelatedElement)
|
||||
if connection not in ["ATPATH", "NOTDEFINED"]:
|
||||
self.join(
|
||||
obj,
|
||||
other,
|
||||
connection,
|
||||
rel.RelatedConnectionType,
|
||||
is_relating=True,
|
||||
obj,
|
||||
other,
|
||||
connection,
|
||||
rel.RelatedConnectionType,
|
||||
is_relating=True,
|
||||
description=rel.Description
|
||||
)
|
||||
for rel in element.ConnectedFrom:
|
||||
@@ -1226,6 +1226,7 @@ class DumbWallJoiner:
|
||||
# If opening has filling then stick to the filling's position
|
||||
# We're applying new openings position only after wall position is applied
|
||||
for opening in [r.RelatedOpeningElement for r in element.HasOpenings if r.RelatedOpeningElement.HasFillings]:
|
||||
similar_openings = blenderbim.core.geometry.get_similar_openings(tool.Ifc, opening)
|
||||
filling_obj = tool.Ifc.get_object(opening.HasFillings[0].RelatedBuildingElement)
|
||||
filling_moved = tool.Ifc.is_moved(filling_obj)
|
||||
if filling_moved:
|
||||
@@ -1234,6 +1235,7 @@ class DumbWallJoiner:
|
||||
ifcopenshell.api.run(
|
||||
"geometry.edit_object_placement", tool.Ifc.get(), product=opening, matrix=filling_obj.matrix_world
|
||||
)
|
||||
blenderbim.core.geometry.edit_similar_opening_placement(tool.Geometry, opening, similar_openings)
|
||||
|
||||
blenderbim.core.geometry.switch_representation(
|
||||
tool.Ifc,
|
||||
|
||||
@@ -176,6 +176,21 @@ class DuctTool(BimTool):
|
||||
BimToolUI.draw(context, layout, ifc_element_type=cls.ifc_element_type)
|
||||
|
||||
|
||||
class CableCarrierTool(BimTool):
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_context_mode = "OBJECT"
|
||||
bl_idname = "bim.cable_carrier_tool"
|
||||
bl_label = "Create Cable Carrier"
|
||||
bl_description = "Create and edit cable carriers"
|
||||
bl_icon = os.path.join(os.path.dirname(__file__), "ops.authoring.cablecarrier")
|
||||
bl_widget = None
|
||||
ifc_element_type = "IfcCableCarrierSegmentType"
|
||||
|
||||
@classmethod
|
||||
def draw_settings(cls, context, layout, ws_tool):
|
||||
BimToolUI.draw(context, layout, ifc_element_type=cls.ifc_element_type)
|
||||
|
||||
|
||||
class PipeTool(BimTool):
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_context_mode = "OBJECT"
|
||||
@@ -191,6 +206,21 @@ class PipeTool(BimTool):
|
||||
BimToolUI.draw(context, layout, ifc_element_type=cls.ifc_element_type)
|
||||
|
||||
|
||||
class CableTool(BimTool):
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_context_mode = "OBJECT"
|
||||
bl_idname = "bim.cable_tool"
|
||||
bl_label = "Create Cable"
|
||||
bl_description = "Create and edit cables"
|
||||
bl_icon = os.path.join(os.path.dirname(__file__), "ops.authoring.cable")
|
||||
bl_widget = None
|
||||
ifc_element_type = "IfcCableSegmentType"
|
||||
|
||||
@classmethod
|
||||
def draw_settings(cls, context, layout, ws_tool):
|
||||
BimToolUI.draw(context, layout, ifc_element_type=cls.ifc_element_type)
|
||||
|
||||
|
||||
def add_layout_hotkey_operator(layout, text, hotkey, description):
|
||||
modifiers = {
|
||||
"A": "EVENT_ALT",
|
||||
@@ -486,7 +516,7 @@ class BimToolUI:
|
||||
prop_with_search(row, cls.props, "relating_type_id", text="")
|
||||
else:
|
||||
row.label(text="No Construction Type", icon="FILE_3D")
|
||||
row.operator("bim.launch_type_manager", icon="LIGHTPROBE_GRID", text="")
|
||||
row.operator("bim.launch_type_manager", icon=tool.Blender.TYPE_MANAGER_ICON, text="")
|
||||
else:
|
||||
if AuthoringData.data["ifc_element_type"]:
|
||||
row.label(text=f"No {AuthoringData.data['ifc_element_type']} Found", icon="ERROR")
|
||||
@@ -499,7 +529,7 @@ class BimToolUI:
|
||||
op.ifc_element_type = AuthoringData.data["ifc_element_type"]
|
||||
else:
|
||||
row.label(text="No Element Types Found", icon="ERROR")
|
||||
row.operator("bim.launch_type_manager", icon="LIGHTPROBE_GRID", text="Launch Type Manager")
|
||||
row.operator("bim.launch_type_manager", icon=tool.Blender.TYPE_MANAGER_ICON, text="Launch Type Manager")
|
||||
|
||||
@classmethod
|
||||
def draw_basic_bim_tool_interface(cls):
|
||||
|
||||
@@ -100,6 +100,7 @@ class ExecuteIfcPatch(bpy.types.Operator):
|
||||
}
|
||||
)
|
||||
ifcpatch.write(output, ifc_patch_output)
|
||||
self.report({"INFO"}, f"{props.ifc_patch_recipes} patch executed successfully")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
import bpy
|
||||
import importlib
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
import ifcpatch
|
||||
from blenderbim.bim.prop import StrProperty, Attribute
|
||||
|
||||
@@ -17,26 +17,35 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
from . import ui, prop, operator
|
||||
from . import ui, prop, operator, workspace, gizmo, decorator
|
||||
|
||||
classes = (
|
||||
operator.AppendEntireLibrary,
|
||||
operator.AppendInspectedLinkedElement,
|
||||
operator.AppendLibraryElement,
|
||||
operator.AppendLibraryElementByQuery,
|
||||
operator.AssignLibraryDeclaration,
|
||||
operator.ChangeLibraryElement,
|
||||
operator.CreateClippingPlane,
|
||||
operator.CreateProject,
|
||||
operator.DisableCulling,
|
||||
operator.DisableEditingHeader,
|
||||
operator.EditHeader,
|
||||
operator.EnableCulling,
|
||||
operator.EnableEditingHeader,
|
||||
operator.ExportIFC,
|
||||
operator.FlipClippingPlane,
|
||||
operator.ImportIFC,
|
||||
operator.LinkIfc,
|
||||
operator.LoadLink,
|
||||
operator.LoadLinkedProject,
|
||||
operator.LoadProject,
|
||||
operator.LoadProjectElements,
|
||||
operator.NewProject,
|
||||
operator.QueryLinkedElement,
|
||||
operator.RefreshClippingPlanes,
|
||||
operator.RefreshLibrary,
|
||||
operator.ReloadLink,
|
||||
operator.RevertProject,
|
||||
operator.RewindLibrary,
|
||||
operator.SaveLibraryFile,
|
||||
@@ -48,7 +57,7 @@ classes = (
|
||||
operator.UnlinkIfc,
|
||||
operator.UnloadLink,
|
||||
operator.UnloadProject,
|
||||
operator.ReloadLink,
|
||||
workspace.ExploreHotkey,
|
||||
prop.LibraryElement,
|
||||
prop.FilterCategory,
|
||||
prop.Link,
|
||||
@@ -62,6 +71,7 @@ classes = (
|
||||
ui.BIM_UL_library,
|
||||
ui.BIM_UL_filter_categories,
|
||||
ui.BIM_UL_links,
|
||||
gizmo.ClippingPlane,
|
||||
)
|
||||
|
||||
|
||||
@@ -69,7 +79,10 @@ addon_keymaps = []
|
||||
|
||||
|
||||
def register():
|
||||
if not bpy.app.background:
|
||||
bpy.utils.register_tool(workspace.ExploreTool, after={"builtin.transform"}, separator=True, group=False)
|
||||
bpy.types.Scene.BIMProjectProperties = bpy.props.PointerProperty(type=prop.BIMProjectProperties)
|
||||
bpy.app.handlers.load_post.append(decorator.toggle_decorations_on_load)
|
||||
bpy.types.TOPBAR_MT_file.prepend(ui.file_menu)
|
||||
bpy.types.TOPBAR_MT_file_context_menu.prepend(ui.file_menu)
|
||||
wm = bpy.context.window_manager
|
||||
@@ -88,9 +101,12 @@ def register():
|
||||
|
||||
|
||||
def unregister():
|
||||
if not bpy.app.background:
|
||||
bpy.utils.unregister_tool(workspace.ExploreTool)
|
||||
del bpy.types.Scene.BIMProjectProperties
|
||||
bpy.app.handlers.load_post.remove(decorator.toggle_decorations_on_load)
|
||||
bpy.types.TOPBAR_MT_file.remove(ui.file_menu)
|
||||
bpy.types.TOPBAR_MT_file_context_menu.remove(ui.file_menu)
|
||||
del bpy.types.Scene.BIMProjectProperties
|
||||
|
||||
wm = bpy.context.window_manager
|
||||
kc = wm.keyconfigs.addon
|
||||
|
||||
@@ -24,6 +24,7 @@ from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
def refresh():
|
||||
ProjectData.is_loaded = False
|
||||
LinksData.is_loaded = False
|
||||
|
||||
|
||||
class ProjectData:
|
||||
@@ -34,6 +35,7 @@ class ProjectData:
|
||||
def load(cls):
|
||||
cls.data = {
|
||||
"export_schema": cls.get_export_schema(),
|
||||
"library_file": cls.library_file(),
|
||||
"template_file": cls.template_file(),
|
||||
"last_saved": cls.last_saved(),
|
||||
}
|
||||
@@ -44,10 +46,17 @@ class ProjectData:
|
||||
return [(s, s, "") for s in IfcStore.schema_identifiers]
|
||||
|
||||
@classmethod
|
||||
def template_file(cls):
|
||||
def library_file(cls):
|
||||
files = os.listdir(os.path.join(bpy.context.scene.BIMProperties.data_dir, "libraries"))
|
||||
results = [("0", "Custom Library", "")]
|
||||
results.extend([(f, os.path.splitext(f)[0], "") for f in files if ".ifc" in f])
|
||||
return results
|
||||
|
||||
@classmethod
|
||||
def template_file(cls):
|
||||
files = os.listdir(os.path.join(bpy.context.scene.BIMProperties.data_dir, "templates", "projects"))
|
||||
results = [("0", "Blank Project", "")]
|
||||
results.extend([(f, f.replace(".ifc", ""), "") for f in files if ".ifc" in f])
|
||||
results.extend([(f, os.path.splitext(f)[0], "") for f in files if ".ifc" in f])
|
||||
return results
|
||||
|
||||
@classmethod
|
||||
@@ -61,3 +70,9 @@ class ProjectData:
|
||||
return f"{save_date} {':'.join(save_time.split(':')[0:2])}"
|
||||
except:
|
||||
return ""
|
||||
|
||||
|
||||
class LinksData:
|
||||
linked_data = {}
|
||||
enable_culling = False
|
||||
is_loaded = False
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2024 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import gpu
|
||||
import bpy
|
||||
import bmesh
|
||||
import blenderbim.tool as tool
|
||||
from bpy.types import SpaceView3D
|
||||
from mathutils import Vector
|
||||
from gpu_extras.batch import batch_for_shader
|
||||
from bpy.app.handlers import persistent
|
||||
|
||||
|
||||
@persistent
|
||||
def toggle_decorations_on_load(*args):
|
||||
if bpy.context.scene.BIMProjectProperties.clipping_planes:
|
||||
ClippingPlaneDecorator.install(bpy.context)
|
||||
else:
|
||||
ClippingPlaneDecorator.uninstall()
|
||||
|
||||
# NOTE: ProjectDecorator cannot be loaded at reopening .blend file
|
||||
# since selected_vertices and other data is stored in queried object's
|
||||
# custom attributes and they get purged after Blender session is closed
|
||||
# as queried object is linked from separate .blend file.
|
||||
|
||||
|
||||
class ProjectDecorator:
|
||||
installed = None
|
||||
|
||||
@classmethod
|
||||
def install(cls, context):
|
||||
if cls.installed:
|
||||
cls.uninstall()
|
||||
handler = cls()
|
||||
cls.installed = SpaceView3D.draw_handler_add(handler, (context,), "WINDOW", "POST_VIEW")
|
||||
|
||||
@classmethod
|
||||
def uninstall(cls):
|
||||
try:
|
||||
SpaceView3D.draw_handler_remove(cls.installed, "WINDOW")
|
||||
except ValueError:
|
||||
pass
|
||||
cls.installed = None
|
||||
|
||||
def draw_batch(self, shader_type, content_pos, color, indices=None):
|
||||
shader = self.line_shader if shader_type == "LINES" else self.shader
|
||||
batch = batch_for_shader(shader, shader_type, {"pos": content_pos}, indices=indices)
|
||||
shader.uniform_float("color", color)
|
||||
batch.draw(shader)
|
||||
|
||||
def __call__(self, context):
|
||||
self.addon_prefs = context.preferences.addons["blenderbim"].preferences
|
||||
selected_elements_color = self.addon_prefs.decorator_color_selected
|
||||
unselected_elements_color = self.addon_prefs.decorator_color_unselected
|
||||
special_elements_color = self.addon_prefs.decorator_color_special
|
||||
|
||||
def transparent_color(color, alpha=0.1):
|
||||
color = [i for i in color]
|
||||
color[3] = alpha
|
||||
return color
|
||||
|
||||
gpu.state.point_size_set(6)
|
||||
gpu.state.blend_set("ALPHA")
|
||||
|
||||
self.line_shader = gpu.shader.from_builtin("POLYLINE_UNIFORM_COLOR")
|
||||
self.line_shader.bind() # required to be able to change uniforms of the shader
|
||||
# POLYLINE_UNIFORM_COLOR specific uniforms
|
||||
self.line_shader.uniform_float("viewportSize", (context.region.width, context.region.height))
|
||||
self.line_shader.uniform_float("lineWidth", 2.0)
|
||||
|
||||
# general shader
|
||||
self.shader = gpu.shader.from_builtin("UNIFORM_COLOR")
|
||||
|
||||
selected_vertices = []
|
||||
selected_edges = []
|
||||
selected_tris = []
|
||||
|
||||
try:
|
||||
obj = context.scene.BIMProjectProperties.queried_obj
|
||||
selected_vertices = obj["selected_vertices"]
|
||||
selected_edges = obj["selected_edges"]
|
||||
selected_tris = obj["selected_tris"]
|
||||
except:
|
||||
return
|
||||
|
||||
if selected_edges:
|
||||
self.draw_batch("LINES", selected_vertices, selected_elements_color, selected_edges)
|
||||
self.draw_batch("TRIS", selected_vertices, transparent_color(selected_elements_color), selected_tris)
|
||||
|
||||
|
||||
class ClippingPlaneDecorator:
|
||||
installed = None
|
||||
|
||||
@classmethod
|
||||
def install(cls, context):
|
||||
if cls.installed:
|
||||
cls.uninstall()
|
||||
handler = cls()
|
||||
cls.installed = SpaceView3D.draw_handler_add(handler, (context,), "WINDOW", "POST_VIEW")
|
||||
|
||||
@classmethod
|
||||
def uninstall(cls):
|
||||
try:
|
||||
SpaceView3D.draw_handler_remove(cls.installed, "WINDOW")
|
||||
except ValueError:
|
||||
pass
|
||||
cls.installed = None
|
||||
|
||||
def draw_batch(self, shader_type, content_pos, color, indices=None):
|
||||
shader = self.line_shader if shader_type == "LINES" else self.shader
|
||||
batch = batch_for_shader(shader, shader_type, {"pos": content_pos}, indices=indices)
|
||||
shader.uniform_float("color", color)
|
||||
batch.draw(shader)
|
||||
|
||||
def __call__(self, context):
|
||||
self.addon_prefs = context.preferences.addons["blenderbim"].preferences
|
||||
selected_elements_color = self.addon_prefs.decorator_color_selected
|
||||
unselected_elements_color = self.addon_prefs.decorator_color_unselected
|
||||
special_elements_color = self.addon_prefs.decorator_color_special
|
||||
|
||||
def transparent_color(color, alpha=0.1):
|
||||
color = [i for i in color]
|
||||
color[3] = alpha
|
||||
return color
|
||||
|
||||
gpu.state.point_size_set(6)
|
||||
gpu.state.blend_set("ALPHA")
|
||||
|
||||
self.line_shader = gpu.shader.from_builtin("POLYLINE_UNIFORM_COLOR")
|
||||
self.line_shader.bind() # required to be able to change uniforms of the shader
|
||||
# POLYLINE_UNIFORM_COLOR specific uniforms
|
||||
self.line_shader.uniform_float("viewportSize", (context.region.width, context.region.height))
|
||||
self.line_shader.uniform_float("lineWidth", 2.0)
|
||||
|
||||
# general shader
|
||||
self.shader = gpu.shader.from_builtin("UNIFORM_COLOR")
|
||||
|
||||
selected_vertices = []
|
||||
selected_edges = []
|
||||
selected_tris = []
|
||||
unselected_vertices = []
|
||||
unselected_edges = []
|
||||
unselected_tris = []
|
||||
|
||||
for clipping_plane in context.scene.BIMProjectProperties.clipping_planes:
|
||||
obj = clipping_plane.obj
|
||||
if not obj or not obj.data:
|
||||
continue
|
||||
|
||||
if obj.mode == "EDIT":
|
||||
continue # A profile decorator or something else is used here.
|
||||
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(obj.data)
|
||||
obj.data.calc_loop_triangles()
|
||||
|
||||
if obj.select_get():
|
||||
offset = len(selected_vertices)
|
||||
selected_vertices.extend([tuple(obj.matrix_world @ v.co) for v in bm.verts])
|
||||
selected_edges.extend([tuple([v.index + offset for v in e.verts]) for e in bm.edges])
|
||||
selected_tris.extend([tuple([i + offset for i in t.vertices]) for t in obj.data.loop_triangles])
|
||||
else:
|
||||
offset = len(unselected_vertices)
|
||||
unselected_vertices.extend([tuple(obj.matrix_world @ v.co) for v in bm.verts])
|
||||
unselected_edges.extend([tuple([v.index + offset for v in e.verts]) for e in bm.edges])
|
||||
unselected_tris.extend([tuple([i + offset for i in t.vertices]) for t in obj.data.loop_triangles])
|
||||
|
||||
verts = [
|
||||
tuple(obj.matrix_world @ Vector((0, 0, 0))),
|
||||
tuple(obj.matrix_world @ Vector((0, 0, -0.5))),
|
||||
tuple(obj.matrix_world @ Vector((-0.05, 0, -0.45))),
|
||||
tuple(obj.matrix_world @ Vector((0.05, 0, -0.45))),
|
||||
tuple(obj.matrix_world @ Vector((0, -0.05, -0.45))),
|
||||
tuple(obj.matrix_world @ Vector((0, 0.05, -0.45))),
|
||||
]
|
||||
edges = [(0, 1), (1, 2), (1, 3), (1, 4), (1, 5)]
|
||||
color = selected_elements_color if obj in context.selected_objects else special_elements_color
|
||||
self.draw_batch("LINES", verts, color, edges)
|
||||
|
||||
if obj.mode != "EDIT":
|
||||
bm.free()
|
||||
|
||||
if unselected_edges:
|
||||
self.draw_batch("LINES", unselected_vertices, special_elements_color, unselected_edges)
|
||||
self.draw_batch("TRIS", unselected_vertices, transparent_color(special_elements_color), unselected_tris)
|
||||
if selected_edges:
|
||||
self.draw_batch("LINES", selected_vertices, selected_elements_color, selected_edges)
|
||||
self.draw_batch("TRIS", selected_vertices, transparent_color(selected_elements_color), selected_tris)
|
||||
@@ -0,0 +1,68 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2024 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import bpy
|
||||
from bpy.types import GizmoGroup
|
||||
from mathutils import Matrix
|
||||
|
||||
|
||||
class ClippingPlane(GizmoGroup):
|
||||
bl_idname = "OBJECT_GGT_bim_clipping_plane"
|
||||
bl_label = "Clipping Plane"
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_options = {"3D", "PERSISTENT"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
obj = context.object
|
||||
return (
|
||||
context.selected_objects
|
||||
and obj
|
||||
and obj.name.startswith("ClippingPlane")
|
||||
and obj in [sp.obj for sp in context.scene.BIMProjectProperties.clipping_planes]
|
||||
)
|
||||
|
||||
def setup(self, context):
|
||||
self.obj = None
|
||||
self.offset = 0
|
||||
self.mw = Matrix()
|
||||
self.last_mw = Matrix()
|
||||
|
||||
self.gizmo = self.gizmos.new("GIZMO_GT_arrow_3d")
|
||||
|
||||
def move_get_x():
|
||||
return self.offset
|
||||
|
||||
def move_set_x(value):
|
||||
self.obj.matrix_world.col[3] = self.mw.col[3] + (self.mw.col[2] * self.offset)
|
||||
self.last_mw = self.obj.matrix_world.copy()
|
||||
self.offset = value
|
||||
|
||||
self.gizmo.target_set_handler("offset", get=move_get_x, set=move_set_x)
|
||||
|
||||
def refresh(self, context):
|
||||
if self.obj != context.object or self.last_mw != context.object.matrix_world:
|
||||
self.obj = context.object
|
||||
self.offset = 0
|
||||
self.mw = context.object.matrix_world.copy()
|
||||
obj = context.object
|
||||
mw = context.object.matrix_world.normalized()
|
||||
mw.col[3] -= mw.col[2] * self.offset
|
||||
self.gizmo.matrix_basis = mw
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -16,11 +16,12 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import bpy
|
||||
import ifcopenshell.util.placement
|
||||
from blenderbim.bim.module.project.data import ProjectData
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.prop import StrProperty
|
||||
from blenderbim.bim.prop import StrProperty, ObjProperty
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
@@ -45,6 +46,19 @@ def get_template_file(self, context):
|
||||
return ProjectData.data["template_file"]
|
||||
|
||||
|
||||
def get_library_file(self, context):
|
||||
if not ProjectData.is_loaded:
|
||||
ProjectData.load()
|
||||
return ProjectData.data["library_file"]
|
||||
|
||||
|
||||
def update_library_file(self, context):
|
||||
if self.library_file != "0":
|
||||
bpy.ops.bim.select_library_file(
|
||||
filepath=os.path.join(bpy.context.scene.BIMProperties.data_dir, "libraries", self.library_file)
|
||||
)
|
||||
|
||||
|
||||
def update_filter_mode(self, context):
|
||||
self.filter_categories.clear()
|
||||
if self.filter_mode == "NONE":
|
||||
@@ -157,6 +171,7 @@ class BIMProjectProperties(PropertyGroup):
|
||||
is_coordinating: BoolProperty(name="For Coordination Only", default=False)
|
||||
deflection_tolerance: FloatProperty(name="Deflection Tolerance", default=0.001)
|
||||
angular_tolerance: FloatProperty(name="Angular Tolerance", default=0.5)
|
||||
void_limit: IntProperty(name="Void Limit", default=30)
|
||||
distance_limit: FloatProperty(name="Distance Limit", default=1000)
|
||||
false_origin: StringProperty(name="False Origin", default="0,0,0")
|
||||
element_offset: IntProperty(name="Element Offset", default=0)
|
||||
@@ -168,7 +183,10 @@ 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")
|
||||
library_file: EnumProperty(items=get_library_file, name="Library File", update=update_library_file)
|
||||
use_relative_project_path: BoolProperty(name="Use Relative Project Path", default=False)
|
||||
queried_obj: bpy.props.PointerProperty(type=bpy.types.Object)
|
||||
clipping_planes: bpy.props.CollectionProperty(type=ObjProperty)
|
||||
|
||||
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))
|
||||
|
||||
@@ -21,7 +21,7 @@ import blenderbim.bim
|
||||
from blenderbim.bim.helper import prop_with_search
|
||||
from bpy.types import Panel, Menu, UIList
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.project.data import ProjectData
|
||||
from blenderbim.bim.module.project.data import ProjectData, LinksData
|
||||
|
||||
|
||||
class BIM_MT_project(Menu):
|
||||
@@ -146,6 +146,8 @@ class BIM_PT_project(Panel):
|
||||
row = self.layout.row()
|
||||
row.prop(pprops, "angular_tolerance")
|
||||
row = self.layout.row()
|
||||
row.prop(pprops, "void_limit")
|
||||
row = self.layout.row()
|
||||
row.prop(pprops, "distance_limit")
|
||||
row = self.layout.row()
|
||||
row.prop(pprops, "false_origin")
|
||||
@@ -206,7 +208,10 @@ class BIM_PT_project(Panel):
|
||||
|
||||
if props.ifc_file:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Saved", icon="EXPORT")
|
||||
if context.scene.BIMProperties.is_dirty:
|
||||
row.label(text="Saved*", icon="EXPORT")
|
||||
else:
|
||||
row.label(text="Saved", icon="EXPORT")
|
||||
row.label(text=ProjectData.data["last_saved"])
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
@@ -250,14 +255,21 @@ class BIM_PT_project_library(Panel):
|
||||
self.layout.use_property_split = True
|
||||
self.props = context.scene.BIMProjectProperties
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=IfcStore.library_path or "No Library Loaded", icon="ASSET_MANAGER")
|
||||
row.prop(self.props, "library_file", text="")
|
||||
if self.props.library_file == "0":
|
||||
row.operator("bim.select_library_file", icon="FILE_FOLDER", text="")
|
||||
row = self.layout.row(align=True)
|
||||
if IfcStore.library_file:
|
||||
row.label(text=IfcStore.library_file.schema)
|
||||
row.label(
|
||||
text=os.path.splitext(os.path.basename(IfcStore.library_path))[0]
|
||||
+ f" - {IfcStore.library_file.schema}",
|
||||
icon="ASSET_MANAGER",
|
||||
)
|
||||
row.operator("bim.append_library_element_by_query", text="", icon="APPEND_BLEND")
|
||||
row.operator("bim.save_library_file", text="", icon="EXPORT")
|
||||
row.operator("bim.select_library_file", icon="FILE_FOLDER", text="")
|
||||
if IfcStore.library_file:
|
||||
self.draw_library_ul()
|
||||
else:
|
||||
row.label(text="No Library Loaded", icon="ASSET_MANAGER")
|
||||
|
||||
def draw_library_ul(self):
|
||||
if not self.props.library_elements:
|
||||
@@ -302,6 +314,46 @@ class BIM_PT_links(Panel):
|
||||
"active_link_index",
|
||||
)
|
||||
|
||||
if LinksData.enable_culling:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Object Culling Enabled", icon="MOD_TRIANGULATE")
|
||||
|
||||
if not LinksData.linked_data or not self.props.links:
|
||||
if self.props.links:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="No Object Selected", icon="QUESTION")
|
||||
return
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="")
|
||||
row.operator("bim.append_inspected_linked_element", icon="APPEND_BLEND", text="")
|
||||
|
||||
for name, value in LinksData.linked_data["attributes"].items():
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=name)
|
||||
row.label(text=value)
|
||||
|
||||
for pset in LinksData.linked_data["properties"]:
|
||||
box = self.layout.box()
|
||||
row = box.row(align=True)
|
||||
row.label(text=pset[0], icon="COPY_ID")
|
||||
for name, value in pset[1].items():
|
||||
row = box.row(align=True)
|
||||
row.label(text=name)
|
||||
row.label(text=value)
|
||||
|
||||
if LinksData.linked_data["type_properties"]:
|
||||
row = self.layout.row()
|
||||
row.label(icon="LINKED", text="Type Properties")
|
||||
for pset in LinksData.linked_data["type_properties"]:
|
||||
box = self.layout.box()
|
||||
row = box.row(align=True)
|
||||
row.label(text=pset[0], icon="COPY_ID")
|
||||
for name, value in pset[1].items():
|
||||
row = box.row(align=True)
|
||||
row.label(text=name)
|
||||
row.label(text=value)
|
||||
|
||||
|
||||
class BIM_UL_library(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2024 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import bpy
|
||||
import blenderbim.tool as tool
|
||||
from bpy.types import WorkSpaceTool
|
||||
from blenderbim.bim.module.project.data import LinksData
|
||||
|
||||
|
||||
class ExploreTool(bpy.types.WorkSpaceTool):
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_context_mode = "OBJECT"
|
||||
bl_idname = "bim.explore_tool"
|
||||
bl_label = "Explore Tool"
|
||||
bl_description = "Fetch data about a linked IFC element"
|
||||
bl_icon = os.path.join(os.path.dirname(__file__), "ops.authoring.explore")
|
||||
bl_widget = None
|
||||
bl_keymap = (
|
||||
("bim.query_linked_element", {"type": "RIGHTMOUSE", "value": "PRESS"}, None),
|
||||
("bim.explore_hotkey", {"type": "W", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_W")]}),
|
||||
("bim.explore_hotkey", {"type": "C", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_C")]}),
|
||||
("bim.explore_hotkey", {"type": "F", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_F")]}),
|
||||
("bim.explore_hotkey", {"type": "C", "value": "PRESS", "alt": True}, {"properties": [("hotkey", "A_C")]}),
|
||||
)
|
||||
|
||||
def draw_settings(context, layout, ws_tool):
|
||||
row = layout.row(align=True)
|
||||
row.label(text="Query Object", icon="MOUSE_RMB")
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="Walk Mode", icon="EVENT_W")
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="Add Clipping Plane", icon="EVENT_C")
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="Flip Clipping Plane", icon="EVENT_F")
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_ALT")
|
||||
row.label(text="Set Orbit Center", icon="MOUSE_MMB")
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_ALT")
|
||||
row.label(text="Disable Culling" if LinksData.enable_culling else "Enable Culling", icon="EVENT_C")
|
||||
|
||||
|
||||
class ExploreHotkey(bpy.types.Operator):
|
||||
bl_idname = "bim.explore_hotkey"
|
||||
bl_label = "Explore Hotkey"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
hotkey: bpy.props.StringProperty()
|
||||
description: bpy.props.StringProperty()
|
||||
|
||||
@classmethod
|
||||
def description(cls, context, operator):
|
||||
return operator.description or ""
|
||||
|
||||
def execute(self, context):
|
||||
getattr(self, f"hotkey_{self.hotkey}")()
|
||||
return {"FINISHED"}
|
||||
|
||||
def hotkey_S_W(self):
|
||||
bpy.ops.view3d.walk("INVOKE_DEFAULT")
|
||||
|
||||
def hotkey_S_C(self):
|
||||
bpy.ops.bim.create_clipping_plane("INVOKE_DEFAULT")
|
||||
|
||||
def hotkey_S_F(self):
|
||||
bpy.ops.bim.flip_clipping_plane("INVOKE_DEFAULT")
|
||||
|
||||
def hotkey_A_C(self):
|
||||
if LinksData.enable_culling:
|
||||
bpy.ops.bim.disable_culling()
|
||||
else:
|
||||
bpy.ops.bim.enable_culling("INVOKE_DEFAULT")
|
||||
@@ -80,10 +80,10 @@ class ObjectPsetsData(Data):
|
||||
def inherited_psets(cls):
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
if element.is_a("IfcTypeObject"):
|
||||
return
|
||||
return []
|
||||
element_type = ifcopenshell.util.element.get_type(element)
|
||||
if element_type:
|
||||
return cls.psetqtos(element_type)
|
||||
return cls.psetqtos(element_type, psets_only=True)
|
||||
|
||||
@classmethod
|
||||
def pset_name(cls):
|
||||
@@ -125,9 +125,21 @@ class ObjectQtosData(Data):
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {"qtos": cls.psetqtos(tool.Ifc.get_entity(bpy.context.active_object), qtos_only=True)}
|
||||
cls.data = {
|
||||
"qtos": cls.psetqtos(tool.Ifc.get_entity(bpy.context.active_object), qtos_only=True),
|
||||
"inherited_qsets": cls.inherited_qsets(),
|
||||
}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def inherited_qsets(cls):
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
if element.is_a("IfcTypeObject"):
|
||||
return []
|
||||
element_type = ifcopenshell.util.element.get_type(element)
|
||||
if element_type:
|
||||
return cls.psetqtos(element_type, qtos_only=True)
|
||||
|
||||
|
||||
class MaterialPsetsData(Data):
|
||||
data = {}
|
||||
|
||||
@@ -85,7 +85,6 @@ class EnablePsetEditing(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
self.props = get_pset_props(context, self.obj, self.obj_type)
|
||||
self.props.properties.clear()
|
||||
ifc_definition_id = blenderbim.bim.helper.get_obj_ifc_definition_id(context, self.obj, self.obj_type)
|
||||
|
||||
if self.pset_id:
|
||||
pset = tool.Ifc.get().by_id(self.pset_id)
|
||||
@@ -250,7 +249,7 @@ class DisablePsetEditing(bpy.types.Operator, Operator):
|
||||
props = get_pset_props(context, self.obj, self.obj_type)
|
||||
if props.active_pset_id:
|
||||
pset = tool.Ifc.get().by_id(props.active_pset_id)
|
||||
ifc_definition_id = blenderbim.bim.helper.get_obj_ifc_definition_id(context, self.obj, self.obj_type)
|
||||
ifc_definition_id = tool.Blender.get_obj_ifc_definition_id(self.obj, self.obj_type, context)
|
||||
if tool.Pset.is_pset_empty(pset):
|
||||
ifcopenshell.api.run(
|
||||
"pset.remove_pset", tool.Ifc.get(), product=tool.Ifc.get().by_id(ifc_definition_id), pset=pset
|
||||
@@ -272,7 +271,7 @@ class EditPset(bpy.types.Operator, Operator):
|
||||
def _execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
props = get_pset_props(context, self.obj, self.obj_type)
|
||||
ifc_definition_id = blenderbim.bim.helper.get_obj_ifc_definition_id(context, self.obj, self.obj_type)
|
||||
ifc_definition_id = tool.Blender.get_obj_ifc_definition_id(self.obj, self.obj_type, context)
|
||||
element = tool.Ifc.get().by_id(ifc_definition_id)
|
||||
properties = {}
|
||||
|
||||
@@ -343,7 +342,7 @@ class RemovePset(bpy.types.Operator, Operator):
|
||||
pset_name = tool.Ifc.get().by_id(self.pset_id).Name
|
||||
for obj in objects:
|
||||
props = get_pset_props(context, obj, self.obj_type)
|
||||
ifc_definition_id = blenderbim.bim.helper.get_obj_ifc_definition_id(context, obj, self.obj_type)
|
||||
ifc_definition_id = tool.Blender.get_obj_ifc_definition_id(obj, self.obj_type, context)
|
||||
element = tool.Ifc.get().by_id(ifc_definition_id)
|
||||
pset = ifcopenshell.util.element.get_psets(element, should_inherit=False).get(pset_name, None)
|
||||
if pset:
|
||||
@@ -374,7 +373,7 @@ class AddQto(bpy.types.Operator, Operator):
|
||||
def _execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
props = get_pset_props(context, self.obj, self.obj_type)
|
||||
ifc_definition_id = blenderbim.bim.helper.get_obj_ifc_definition_id(context, self.obj, self.obj_type)
|
||||
ifc_definition_id = tool.Blender.get_obj_ifc_definition_id(self.obj, self.obj_type, context)
|
||||
element = tool.Ifc.get().by_id(ifc_definition_id)
|
||||
bpy.ops.bim.enable_pset_editing(
|
||||
pset_id=0, pset_name=props.qto_name, pset_type="QTO", obj=self.obj, obj_type=self.obj_type
|
||||
|
||||
@@ -33,7 +33,6 @@ from blenderbim.bim.module.pset.data import (
|
||||
GroupPsetData,
|
||||
ProfilePsetsData,
|
||||
WorkSchedulePsetsData,
|
||||
|
||||
)
|
||||
|
||||
|
||||
@@ -172,7 +171,7 @@ def draw_psetqto_editable_ui(box, props, prop):
|
||||
|
||||
|
||||
class BIM_PT_object_psets(Panel):
|
||||
bl_label = "Object Property Sets"
|
||||
bl_label = "Property Sets"
|
||||
bl_idname = "BIM_PT_object_psets"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
@@ -204,17 +203,19 @@ class BIM_PT_object_psets(Panel):
|
||||
if not props.active_pset_id and props.active_pset_name and props.active_pset_type == "PSET":
|
||||
draw_psetqto_ui(context, 0, {}, props, self.layout, "Object")
|
||||
|
||||
for pset in ObjectPsetsData.data["psets"]:
|
||||
draw_psetqto_ui(context, pset["id"], pset, props, self.layout, "Object")
|
||||
if ObjectPsetsData.data["psets"]:
|
||||
self.layout.label(text="Instance:")
|
||||
for pset in ObjectPsetsData.data["psets"]:
|
||||
draw_psetqto_ui(context, pset["id"], pset, props, self.layout, "Object")
|
||||
|
||||
if ObjectPsetsData.data["inherited_psets"]:
|
||||
self.layout.label(text="Inherited Psets:", icon="FILE_PARENT")
|
||||
self.layout.label(text="Type:")
|
||||
for pset in ObjectPsetsData.data["inherited_psets"]:
|
||||
draw_psetqto_ui(context, pset["id"], pset, props, self.layout, "Object")
|
||||
|
||||
|
||||
class BIM_PT_object_qtos(Panel):
|
||||
bl_label = "Object Quantity Sets"
|
||||
bl_label = "Quantity Sets"
|
||||
bl_idname = "BIM_PT_object_qtos"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
@@ -245,9 +246,16 @@ class BIM_PT_object_qtos(Panel):
|
||||
|
||||
if not props.active_pset_id and props.active_pset_name and props.active_pset_type == "QTO":
|
||||
draw_psetqto_ui(context, 0, {}, props, self.layout, "Object")
|
||||
|
||||
if ObjectQtosData.data["qtos"]:
|
||||
self.layout.label(text="Instance:")
|
||||
for qto in ObjectQtosData.data["qtos"]:
|
||||
draw_psetqto_ui(context, qto["id"], qto, props, self.layout, "Object")
|
||||
|
||||
for qto in ObjectQtosData.data["qtos"]:
|
||||
draw_psetqto_ui(context, qto["id"], qto, props, self.layout, "Object")
|
||||
if ObjectQtosData.data["inherited_qsets"]:
|
||||
self.layout.label(text="Type:")
|
||||
for qset in ObjectQtosData.data["inherited_qsets"]:
|
||||
draw_psetqto_ui(context, qset["id"], qset, props, self.layout, "Object")
|
||||
|
||||
|
||||
class BIM_PT_material_psets(Panel):
|
||||
@@ -541,8 +549,6 @@ class BIM_PT_group_psets(Panel):
|
||||
draw_psetqto_ui(context, pset["id"], pset, props, self.layout, "Group")
|
||||
|
||||
|
||||
|
||||
|
||||
class BIM_PT_profile_psets(Panel):
|
||||
bl_label = "Profile Property Sets"
|
||||
bl_idname = "BIM_PT_profile_psets"
|
||||
|
||||
@@ -21,6 +21,7 @@ import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.schema
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.type
|
||||
import blenderbim.bim.handler
|
||||
import blenderbim.core.geometry
|
||||
import blenderbim.core.material
|
||||
@@ -103,33 +104,68 @@ class ReassignClass(bpy.types.Operator):
|
||||
predefined_type = context.scene.BIMRootProperties.ifc_predefined_type
|
||||
if predefined_type == "USERDEFINED":
|
||||
predefined_type = context.scene.BIMRootProperties.ifc_userdefined_type
|
||||
reassigned_elements = set()
|
||||
|
||||
# NOTE: root.reassign_class
|
||||
# automatically will reassign class for other occurrences of the type
|
||||
# so we need to run it only for the types or non-typed elements
|
||||
elements_to_reassign = set()
|
||||
# need to update blender object name
|
||||
# for all elements that were changed in the process
|
||||
elements_to_update = set()
|
||||
for obj in objects:
|
||||
product = ifcopenshell.api.run(
|
||||
obj.BIMObjectProperties.is_reassigning_class = False
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element.is_a("IfcTypeObject"):
|
||||
elements_to_reassign.add(element)
|
||||
elements_to_update.update(ifcopenshell.util.element.get_types(element))
|
||||
continue
|
||||
|
||||
# check if element is typed
|
||||
element_type = ifcopenshell.util.element.get_type(element)
|
||||
if element_type:
|
||||
elements_to_reassign.add(element_type)
|
||||
elements_to_update.update(ifcopenshell.util.element.get_types(element_type))
|
||||
continue
|
||||
|
||||
# non-typed element
|
||||
elements_to_reassign.add(element)
|
||||
|
||||
# store elements to objects to update later as elements will get invalid
|
||||
# after class reassignment
|
||||
elements_to_update = elements_to_update | elements_to_reassign
|
||||
objects_to_update = set(o for e in elements_to_update if (o := tool.Ifc.get_object(e)))
|
||||
|
||||
base_class = context.scene.BIMRootProperties.ifc_class
|
||||
if context.scene.BIMRootProperties.ifc_product == "IfcElementType":
|
||||
type_class = base_class
|
||||
occurrence_classes = ifcopenshell.util.type.get_applicable_entities(type_class)
|
||||
occurrence_class = None if len(occurrence_classes) == 0 else occurrence_classes[0]
|
||||
else:
|
||||
occurrence_class = base_class
|
||||
type_classes = ifcopenshell.util.type.get_applicable_types(occurrence_class)
|
||||
type_class = None if len(type_classes) == 0 else type_classes[0]
|
||||
|
||||
reassigned_elements = set()
|
||||
for element in elements_to_reassign:
|
||||
ifc_class = type_class if element.is_a("IfcTypeObject") else occurrence_class
|
||||
if ifc_class is None:
|
||||
self.report(
|
||||
{"ERROR"},
|
||||
f"Couldn't find valid class for reassigning element of class {element.is_a()} based on class {base_class}",
|
||||
)
|
||||
return {"CANCELLED"}
|
||||
|
||||
element = ifcopenshell.api.run(
|
||||
"root.reassign_class",
|
||||
self.file,
|
||||
product=tool.Ifc.get_entity(obj),
|
||||
ifc_class=context.scene.BIMRootProperties.ifc_class,
|
||||
product=element,
|
||||
ifc_class=ifc_class,
|
||||
predefined_type=predefined_type,
|
||||
)
|
||||
reassigned_elements.add(product)
|
||||
obj.name = tool.Loader.get_name(product)
|
||||
obj.BIMObjectProperties.is_reassigning_class = False
|
||||
reassigned_elements.add(element)
|
||||
|
||||
dependent_elements = set()
|
||||
for reassigned_element in reassigned_elements:
|
||||
if reassigned_element.is_a("IfcTypeObject"):
|
||||
dependent_elements.update(ifcopenshell.util.element.get_types(product))
|
||||
else:
|
||||
element_type = ifcopenshell.util.element.get_type(product)
|
||||
if element_type:
|
||||
dependent_elements.add(element_type)
|
||||
dependent_elements.update(ifcopenshell.util.element.get_types(element_type))
|
||||
|
||||
for dependent_element in dependent_elements:
|
||||
obj = tool.Ifc.get_object(dependent_element)
|
||||
if obj:
|
||||
obj.name = tool.Loader.get_name(dependent_element)
|
||||
for obj in objects_to_update:
|
||||
obj.name = tool.Loader.get_name(tool.Ifc.get_entity(obj))
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -158,6 +194,9 @@ class AssignClass(bpy.types.Operator, Operator):
|
||||
ifc_context = tool.Ifc.get().by_id(ifc_context)
|
||||
active_object = context.active_object
|
||||
for obj in objects:
|
||||
if obj.mode != "OBJECT":
|
||||
self.report({"ERROR"}, "Object must be in OBJECT mode to assign class")
|
||||
continue
|
||||
core.assign_class(
|
||||
tool.Ifc,
|
||||
tool.Collector,
|
||||
|
||||
@@ -131,6 +131,7 @@ class SelectFilterElements(bpy.types.Operator):
|
||||
text_data = bpy.data.texts.new(name)
|
||||
text_data.from_string(",".join(global_ids))
|
||||
filter_groups[self.group_index].filters[self.index].value = f"bpy.data.texts['{name}']"
|
||||
self.report({"INFO"}, f'List of Global Ids was saved to the text file "{name}" in the current .blend file')
|
||||
else:
|
||||
filter_groups[self.group_index].filters[self.index].value = ",".join(global_ids)
|
||||
return {"FINISHED"}
|
||||
@@ -538,9 +539,11 @@ class ActivateIfcClassFilter(Operator):
|
||||
"filter_classes",
|
||||
context.scene.BIMSearchProperties,
|
||||
"filter_classes_index",
|
||||
rows=20
|
||||
if len(bpy.context.scene.BIMSearchProperties.filter_classes) > 20
|
||||
else len(bpy.context.scene.BIMSearchProperties.filter_classes),
|
||||
rows=(
|
||||
20
|
||||
if len(bpy.context.scene.BIMSearchProperties.filter_classes) > 20
|
||||
else len(bpy.context.scene.BIMSearchProperties.filter_classes)
|
||||
),
|
||||
)
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.toggle_filter_selection", text="Select All").action = "SELECT"
|
||||
@@ -595,9 +598,11 @@ class ActivateContainerFilter(Operator):
|
||||
"filter_container",
|
||||
context.scene.BIMSearchProperties,
|
||||
"filter_container_index",
|
||||
rows=20
|
||||
if len(bpy.context.scene.BIMSearchProperties.filter_container) > 20
|
||||
else len(bpy.context.scene.BIMSearchProperties.filter_container),
|
||||
rows=(
|
||||
20
|
||||
if len(bpy.context.scene.BIMSearchProperties.filter_container) > 20
|
||||
else len(bpy.context.scene.BIMSearchProperties.filter_container)
|
||||
),
|
||||
)
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.toggle_filter_selection", text="Select All").action = "SELECT"
|
||||
|
||||
@@ -128,8 +128,12 @@ class SequenceData:
|
||||
cls.data["work_times"] = {}
|
||||
for work_time in tool.Ifc.get().by_type("IfcWorkTime"):
|
||||
data = work_time.get_info()
|
||||
data["Start"] = ifcopenshell.util.date.ifc2datetime(data["Start"]) if data["Start"] else None
|
||||
data["Finish"] = ifcopenshell.util.date.ifc2datetime(data["Finish"]) if data["Finish"] else None
|
||||
if tool.Ifc.get_schema() == "IFC4X3":
|
||||
start_date, finish_date = data["StartDate"], data["FinishDate"]
|
||||
else:
|
||||
start_date, finish_date = data["Start"], data["Finish"]
|
||||
data["Start"] = ifcopenshell.util.date.ifc2datetime(start_date) if start_date else None
|
||||
data["Finish"] = ifcopenshell.util.date.ifc2datetime(finish_date) if finish_date else None
|
||||
data["RecurrencePattern"] = work_time.RecurrencePattern.id() if work_time.RecurrencePattern else None
|
||||
cls.data["work_times"][work_time.id()] = data
|
||||
|
||||
|
||||
@@ -48,8 +48,10 @@ class SpatialTool(WorkSpaceTool):
|
||||
("bim.spatial_hotkey", {"type": "B", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_B")]}),
|
||||
("bim.spatial_hotkey", {"type": "A", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_A")]}),
|
||||
("bim.spatial_hotkey", {"type": "B", "value": "PRESS", "alt": True}, {"properties": [("hotkey", "A_B")]}),
|
||||
("bim.spatial_hotkey", {"type": "V", "value": "PRESS", "alt": True}, {"properties": [("hotkey", "A_V")]}),
|
||||
("bim.spatial_hotkey", {"type": "T", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_T")]}),
|
||||
("bim.spatial_hotkey", {"type": "G", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_G")]}),
|
||||
("bim.spatial_hotkey", {"type": "H", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_H")]}),
|
||||
)
|
||||
|
||||
def draw_settings(context, layout, ws_tool):
|
||||
@@ -91,31 +93,34 @@ class SpatialToolUI:
|
||||
if context.active_object and context.selected_objects:
|
||||
cls.draw_selected_object_interface(context)
|
||||
|
||||
add_layout_hotkey(cls.layout, "Decorate Boundaries", "A_V", bpy.ops.bim.decorate_boundaries.__doc__)
|
||||
|
||||
@classmethod
|
||||
def draw_default_interface(cls, context):
|
||||
row = cls.layout.row(align=True)
|
||||
row.prop(data=cls.model_props, property="rl3", text="RL")
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_A")
|
||||
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")
|
||||
op_name = lambda op: op.get_rna_type().name
|
||||
add_layout_hotkey(
|
||||
cls.layout,
|
||||
op_name(bpy.ops.bim.generate_spaces_from_walls),
|
||||
"S_A",
|
||||
bpy.ops.bim.generate_spaces_from_walls.__doc__,
|
||||
)
|
||||
add_layout_hotkey(cls.layout, op_name(bpy.ops.bim.generate_space), "S_A", bpy.ops.bim.generate_space.__doc__)
|
||||
add_layout_hotkey(
|
||||
cls.layout, op_name(bpy.ops.bim.toggle_space_visibility), "S_T", bpy.ops.bim.toggle_space_visibility.__doc__
|
||||
)
|
||||
add_layout_hotkey(
|
||||
cls.layout, op_name(bpy.ops.bim.toggle_hide_spaces), "S_H", bpy.ops.bim.toggle_hide_spaces.__doc__
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def draw_selected_object_interface(cls, context):
|
||||
active_obj = bpy.context.active_object
|
||||
element = tool.Ifc.get_entity(active_obj)
|
||||
if element and bpy.context.selected_objects and element.is_a("IfcSpace"):
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_G")
|
||||
row.operator("bim.generate_space", text="Regen")
|
||||
add_layout_hotkey(cls.layout, "Regen", "S_G", bpy.ops.bim.generate_space.__doc__)
|
||||
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
@@ -123,7 +128,7 @@ class SpatialToolUI:
|
||||
row.prop(cls.model_props, "boundary_class", text="")
|
||||
row.operator("bim.add_boundary", text="Add Boundary")
|
||||
|
||||
add_layout_hotkey(cls.layout, "Boundaries", "A_B", "Toggle boundaries")
|
||||
add_layout_hotkey(cls.layout, "Boundaries", "A_B", "Load/unload boundaries on selected objects")
|
||||
|
||||
@classmethod
|
||||
def draw_type_selection_interface(cls, context):
|
||||
@@ -169,6 +174,8 @@ class Hotkey(bpy.types.Operator, Operator):
|
||||
bpy.ops.bim.add_boundary()
|
||||
|
||||
def hotkey_A_B(self):
|
||||
if not AuthoringData.is_loaded:
|
||||
AuthoringData.load()
|
||||
if not bpy.context.selected_objects:
|
||||
return
|
||||
if AuthoringData.data["has_visible_boundaries"]:
|
||||
@@ -176,8 +183,14 @@ class Hotkey(bpy.types.Operator, Operator):
|
||||
else:
|
||||
bpy.ops.bim.show_boundaries()
|
||||
|
||||
def hotkey_A_V(self):
|
||||
bpy.ops.bim.decorate_boundaries()
|
||||
|
||||
def hotkey_S_T(self):
|
||||
bpy.ops.bim.toggle_space_visibility()
|
||||
|
||||
def hotkey_S_H(self):
|
||||
bpy.ops.bim.toggle_hide_spaces()
|
||||
|
||||
def hotkey_S_G(self):
|
||||
bpy.ops.bim.generate_space()
|
||||
|
||||
@@ -27,7 +27,7 @@ import ifctester.reporter
|
||||
import ifcopenshell
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.bim.handler
|
||||
from blenderbim.bim.module.tester.data import TesterData
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class ExecuteIfcTester(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -44,7 +44,7 @@ class ExecuteIfcTester(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
with tempfile.TemporaryDirectory() as dirpath:
|
||||
start = time.time()
|
||||
output = os.path.join(dirpath, "{}.html".format(props.specs))
|
||||
output = Path(os.path.join(dirpath, "{}.html".format(props.specs)))
|
||||
|
||||
filepath = None
|
||||
if props.should_load_from_memory and tool.Ifc.get():
|
||||
@@ -65,8 +65,15 @@ class ExecuteIfcTester(bpy.types.Operator, tool.Ifc.Operator):
|
||||
if props.generate_html_report:
|
||||
engine = ifctester.reporter.Html(specs)
|
||||
engine.report()
|
||||
engine.to_file(output)
|
||||
webbrowser.open("file://" + output)
|
||||
output_path = output.as_posix()
|
||||
engine.to_file(output_path)
|
||||
webbrowser.open(f"file://{output_path}")
|
||||
|
||||
if props.generate_ods_report:
|
||||
engine = ifctester.reporter.Ods(specs)
|
||||
engine.report()
|
||||
output_path = output.with_suffix(".ods").as_posix()
|
||||
engine.to_file(output_path)
|
||||
|
||||
report = None
|
||||
report = ifctester.reporter.Json(specs).report()["specifications"]
|
||||
|
||||
@@ -52,6 +52,7 @@ class IfcTesterProperties(PropertyGroup):
|
||||
ifc_file: StringProperty(default="", name="IFC File")
|
||||
should_load_from_memory: BoolProperty(default=False, name="Load from Memory")
|
||||
generate_html_report: BoolProperty(default=False, name="Generate HTML report")
|
||||
generate_ods_report: BoolProperty(default=False, name="Generate ODS report")
|
||||
flag: BoolProperty(default=False, name="Flag Failed Entities")
|
||||
active_specification_index: IntProperty(name="Active Specification Index", update=update_active_specification_index)
|
||||
old_index: IntProperty(name="", default=0)
|
||||
|
||||
@@ -44,6 +44,8 @@ class BIM_PT_tester(Panel):
|
||||
row = self.layout.row()
|
||||
row.prop(props, "generate_html_report")
|
||||
row = self.layout.row()
|
||||
row.prop(props, "generate_ods_report")
|
||||
row = self.layout.row()
|
||||
row.prop(props, "flag")
|
||||
|
||||
if not tool.Ifc.get() or not props.should_load_from_memory:
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
import bpy
|
||||
import bmesh
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.schema
|
||||
import ifcopenshell.util.type
|
||||
import ifcopenshell.api
|
||||
@@ -49,8 +50,12 @@ class AssignType(bpy.types.Operator, Operator):
|
||||
if self.related_object
|
||||
else context.selected_objects or [context.active_object]
|
||||
)
|
||||
model_props = context.scene.BIMModelProperties
|
||||
for obj in related_objects:
|
||||
core.assign_type(tool.Ifc, tool.Type, element=tool.Ifc.get_entity(obj), type=type)
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
core.assign_type(tool.Ifc, tool.Type, element=element, type=type)
|
||||
if model_props.occurrence_name_style == "TYPE":
|
||||
obj.name = tool.Model.generate_occurrence_name(type, element.is_a())
|
||||
|
||||
|
||||
class UnassignType(bpy.types.Operator):
|
||||
@@ -166,15 +171,24 @@ class SelectSimilarType(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
related_object = bpy.data.objects.get(self.related_object) if self.related_object else context.active_object
|
||||
relating_type = ifcopenshell.util.element.get_type(tool.Ifc.get_entity(related_object))
|
||||
if not relating_type:
|
||||
return {"FINISHED"}
|
||||
related_objects = ifcopenshell.util.element.get_types(relating_type)
|
||||
for obj in context.visible_objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element and element in related_objects:
|
||||
obj.select_set(True)
|
||||
objects = bpy.context.selected_objects
|
||||
|
||||
# store relating types to avoid selecting same elements multiple times
|
||||
relating_types = set()
|
||||
|
||||
for related_object in objects:
|
||||
relating_type = ifcopenshell.util.element.get_type(tool.Ifc.get_entity(related_object))
|
||||
if not relating_type:
|
||||
related_object.select_set(False)
|
||||
continue
|
||||
relating_types.add(relating_type)
|
||||
|
||||
for relating_type in relating_types:
|
||||
related_objects = ifcopenshell.util.element.get_types(relating_type)
|
||||
for element in related_objects:
|
||||
obj = tool.Ifc.get_object(element)
|
||||
if obj and obj in context.visible_objects:
|
||||
obj.select_set(True)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
|
||||
@@ -29,8 +29,9 @@ 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"
|
||||
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):
|
||||
@@ -41,12 +42,10 @@ class AddOpening(bpy.types.Operator, tool.Ifc.Operator):
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMModelProperties
|
||||
selected_objects = context.selected_objects
|
||||
target_object = selected_objects[0]
|
||||
|
||||
target_object = selected_objects[0]
|
||||
|
||||
opening_objects = [obj for obj in selected_objects if obj != target_object]
|
||||
|
||||
|
||||
for opening_obj in opening_objects:
|
||||
element1 = tool.Ifc.get_entity(target_object)
|
||||
obj1 = target_object
|
||||
@@ -58,7 +57,7 @@ class AddOpening(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
if element1 and element2:
|
||||
if element1.is_a("IfcOpeningElement") and element2.is_a("IfcOpeningElement"):
|
||||
self.report({'INFO'}, "You can't add an opening to another opening.")
|
||||
self.report({"INFO"}, "You can't add an opening to another opening.")
|
||||
continue
|
||||
elif not element1.is_a("IfcOpeningElement") and not element2.is_a("IfcOpeningElement"):
|
||||
if element1.is_a("IfcWindow") or element1.is_a("IfcDoor"): # Add a fill to an element.
|
||||
@@ -75,7 +74,7 @@ class AddOpening(bpy.types.Operator, tool.Ifc.Operator):
|
||||
element1, element2 = element2, element1
|
||||
|
||||
if element1.is_a("IfcOpeningElement"):
|
||||
self.report({'INFO'}, "You can't add an opening to another opening.")
|
||||
self.report({"INFO"}, "You can't add an opening to another opening.")
|
||||
continue
|
||||
|
||||
if tool.Ifc.is_moved(obj1):
|
||||
@@ -259,8 +258,10 @@ class BooleansMarkAsManual(bpy.types.Operator):
|
||||
if self.mark_as_manual:
|
||||
tool.Model.mark_manual_booleans(element, booleans)
|
||||
else:
|
||||
tool.Model.unmark_manual_booleans(element, booleans)
|
||||
tool.Model.unmark_manual_booleans(element, [b.id() for b in booleans])
|
||||
|
||||
self.report({"INFO"}, f"{len(booleans)} were marked as {'manual' if self.mark_as_manual else 'automatic'}")
|
||||
self.report(
|
||||
{"INFO"}, f"{len(booleans)} booleans were marked as {'manual' if self.mark_as_manual else 'automatic'}"
|
||||
)
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
import os
|
||||
import bpy
|
||||
import bmesh
|
||||
import json
|
||||
import time
|
||||
import logging
|
||||
@@ -38,6 +39,9 @@ from blenderbim.bim.ui import IFCFileSelector
|
||||
from blenderbim.bim.helper import get_enum_items
|
||||
from mathutils import Vector, Matrix, Euler
|
||||
from math import radians
|
||||
from pathlib import Path
|
||||
from collections import namedtuple
|
||||
from typing import List
|
||||
|
||||
|
||||
class SetTab(bpy.types.Operator):
|
||||
@@ -127,7 +131,7 @@ class SelectIfcFile(bpy.types.Operator, IFCFileSelector):
|
||||
bl_idname = "bim.select_ifc_file"
|
||||
bl_label = "Select IFC File"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Select a different IFC file"
|
||||
bl_description = f"Select a different IFC file.\n{tool.Blender.operator_invoke_filepath_hotkeys_description}"
|
||||
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)
|
||||
@@ -138,6 +142,11 @@ class SelectIfcFile(bpy.types.Operator, IFCFileSelector):
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
filepath = Path(context.scene.BIMProperties.ifc_file)
|
||||
res = tool.Blender.operator_invoke_filepath_hotkeys(self, context, event, filepath)
|
||||
if res is not None:
|
||||
return res
|
||||
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
@@ -404,11 +413,21 @@ class BIM_OT_add_section_plane(bpy.types.Operator):
|
||||
|
||||
def create_section_compare_node(self):
|
||||
group = bpy.data.node_groups.new("Section Compare", type="ShaderNodeTree")
|
||||
group.inputs.new("NodeSocketFloat", "Value")
|
||||
group.inputs["Value"].default_value = 1.0 # Mandatory multiplier for the last node group
|
||||
group.inputs.new("NodeSocketVector", "Vector")
|
||||
group.outputs.new("NodeSocketFloat", "Value")
|
||||
group.outputs.new("NodeSocketFloat", "Line Decorator")
|
||||
if bpy.app.version >= (4, 0, 0):
|
||||
input_value = group.interface.new_socket(name="Value", in_out="INPUT", socket_type="NodeSocketFloat")
|
||||
input_value.default_value = 1.0 # Mandatory multiplier for the last node group
|
||||
group.interface.new_socket(name="Vector", in_out="INPUT", socket_type="NodeSocketVector")
|
||||
group.interface.new_socket(name="Line Decorator", in_out="INPUT", socket_type="NodeSocketFloat")
|
||||
group.interface.new_socket(name="Value", in_out="OUTPUT", socket_type="NodeSocketFloat")
|
||||
group.interface.new_socket(name="Line Decorator", in_out="OUTPUT", socket_type="NodeSocketFloat")
|
||||
else:
|
||||
group.inputs.new("NodeSocketFloat", "Value")
|
||||
group.inputs["Value"].default_value = 1.0 # Mandatory multiplier for the last node group
|
||||
group.inputs.new("NodeSocketVector", "Vector")
|
||||
group.inputs.new("NodeSocketFloat", "Line Decorator")
|
||||
group.outputs.new("NodeSocketFloat", "Value")
|
||||
group.outputs.new("NodeSocketFloat", "Line Decorator")
|
||||
|
||||
group_input = group.nodes.new(type="NodeGroupInput")
|
||||
group_input.location = 0, 50
|
||||
|
||||
@@ -431,8 +450,16 @@ class BIM_OT_add_section_plane(bpy.types.Operator):
|
||||
multiply.inputs[0].default_value = 1
|
||||
multiply.location = 600, 150
|
||||
|
||||
add_line_decorator = group.nodes.new(type="ShaderNodeMath")
|
||||
add_line_decorator.operation = "ADD"
|
||||
add_line_decorator.location = 600, -200
|
||||
|
||||
multiply_line_decorator = group.nodes.new(type="ShaderNodeMath")
|
||||
multiply_line_decorator.operation = "MULTIPLY"
|
||||
multiply_line_decorator.location = 800, -200
|
||||
|
||||
group_output = group.nodes.new(type="NodeGroupOutput")
|
||||
group_output.location = 800, 0
|
||||
group_output.location = 1000, 0
|
||||
|
||||
group.links.new(group_input.outputs["Value"], multiply.inputs[0])
|
||||
group.links.new(group_input.outputs["Vector"], separate_xyz.inputs[0])
|
||||
@@ -440,12 +467,20 @@ class BIM_OT_add_section_plane(bpy.types.Operator):
|
||||
group.links.new(greater.outputs[0], multiply.inputs[1])
|
||||
group.links.new(multiply.outputs[0], group_output.inputs["Value"])
|
||||
group.links.new(separate_xyz.outputs[2], compare.inputs[0])
|
||||
group.links.new(compare.outputs[0], group_output.inputs["Line Decorator"])
|
||||
group.links.new(compare.outputs[0], add_line_decorator.inputs[0])
|
||||
group.links.new(group_input.outputs["Line Decorator"], add_line_decorator.inputs[1])
|
||||
group.links.new(multiply.outputs[0], multiply_line_decorator.inputs[0])
|
||||
group.links.new(add_line_decorator.outputs[0], multiply_line_decorator.inputs[1])
|
||||
group.links.new(multiply_line_decorator.outputs[0], group_output.inputs["Line Decorator"])
|
||||
|
||||
def create_section_override_node(self, obj, context):
|
||||
group = bpy.data.node_groups.new("Section Override", type="ShaderNodeTree")
|
||||
group.inputs.new("NodeSocketShader", "Shader")
|
||||
group.outputs.new("NodeSocketShader", "Shader")
|
||||
if bpy.app.version >= (4, 0, 0):
|
||||
group.interface.new_socket(name="Shader", in_out="INPUT", socket_type="NodeSocketShader")
|
||||
group.interface.new_socket(name="Shader", in_out="OUTPUT", socket_type="NodeSocketShader")
|
||||
else:
|
||||
group.inputs.new("NodeSocketShader", "Shader")
|
||||
group.outputs.new("NodeSocketShader", "Shader")
|
||||
links = group.links
|
||||
nodes = group.nodes
|
||||
|
||||
@@ -455,6 +490,7 @@ class BIM_OT_add_section_plane(bpy.types.Operator):
|
||||
|
||||
mix_decorator = group.nodes.new(type="ShaderNodeMixShader")
|
||||
mix_decorator.name = "Line Decorator Mix"
|
||||
mix_decorator.inputs[0].default_value = 0 # Directly pass input shader when there is no cutaway
|
||||
mix_decorator.location = group_output.location - Vector((200, 0))
|
||||
|
||||
mix_section = group.nodes.new(type="ShaderNodeMixShader")
|
||||
@@ -519,7 +555,15 @@ class BIM_OT_add_section_plane(bpy.types.Operator):
|
||||
cut_obj.object = obj
|
||||
cut_obj.location = last_section_node.location - Vector((400, 150)) - offset
|
||||
|
||||
group.links.new(section_compare.outputs[0], last_section_node.inputs[0])
|
||||
group.links.new(section_compare.outputs["Value"], last_section_node.inputs[0])
|
||||
group.links.new(
|
||||
section_compare.outputs["Line Decorator"],
|
||||
(
|
||||
last_section_node.inputs["Line Decorator"]
|
||||
if "Line Decorator" in last_section_node.inputs
|
||||
else group.nodes.get("Line Decorator Mix").inputs[0]
|
||||
),
|
||||
)
|
||||
group.links.new(cut_obj.outputs["Object"], section_compare.inputs[1])
|
||||
|
||||
section_compare.name = "Last Section Compare"
|
||||
@@ -594,6 +638,14 @@ class BIM_OT_remove_section_plane(bpy.types.Operator):
|
||||
previous_section_compare = section_compare.inputs[0].links[0].from_node
|
||||
next_section_compare = section_compare.outputs[0].links[0].to_node
|
||||
section_override.links.new(previous_section_compare.outputs[0], next_section_compare.inputs[0])
|
||||
section_override.links.new(
|
||||
previous_section_compare.outputs[1],
|
||||
(
|
||||
next_section_compare.inputs["Line Decorator"]
|
||||
if "Line Decorator" in next_section_compare.inputs
|
||||
else next_section_compare.inputs[0]
|
||||
),
|
||||
)
|
||||
self.offset_previous_nodes(section_compare, offset_x=200)
|
||||
section_override.nodes.remove(section_compare)
|
||||
section_override.nodes.remove(tex_coords)
|
||||
@@ -605,8 +657,8 @@ class BIM_OT_remove_section_plane(bpy.types.Operator):
|
||||
if section_compare.inputs[0].links:
|
||||
previous_section_compare = section_compare.inputs[0].links[0].from_node
|
||||
previous_section_compare.location += Vector((offset_x, offset_y))
|
||||
if previous_section_compare.inputs[1].links:
|
||||
previous_section_compare.inputs[1].links[0].from_node.location += Vector((offset_x, offset_y))
|
||||
if previous_section_compare.inputs["Vector"].links:
|
||||
previous_section_compare.inputs["Vector"].links[0].from_node.location += Vector((offset_x, offset_y))
|
||||
self.offset_previous_nodes(previous_section_compare, offset_x, offset_y)
|
||||
|
||||
def purge_all_section_data(self, context):
|
||||
@@ -926,3 +978,124 @@ class BIM_OT_show_description(bpy.types.Operator):
|
||||
@classmethod
|
||||
def description(cls, context, properties):
|
||||
return properties.description
|
||||
|
||||
|
||||
CuttingPlaneData = namedtuple("CuttingPlaneData", ["co", "normal"])
|
||||
|
||||
|
||||
class ClippingPlaneCutWithCappings(bpy.types.Operator):
|
||||
bl_idname = "bim.clipping_plane_cut_with_cappings"
|
||||
bl_label = "Cut With Clipping Planes"
|
||||
bl_description = "Cut selected objects with clipping planes and create cappings"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
cutting_planes = [p.obj for p in context.scene.BIMProjectProperties.clipping_planes]
|
||||
if not cutting_planes:
|
||||
self.report({"INFO"}, "No cutting planes found.")
|
||||
return {"FINISHED"}
|
||||
cutting_planes_data = self.get_cutting_plane_data(cutting_planes)
|
||||
|
||||
wm = context.window_manager
|
||||
objects_processed, t0 = 0, time.time()
|
||||
wm.progress_begin(0, len(context.selected_objects))
|
||||
for obj_i, obj in enumerate(context.selected_objects):
|
||||
if obj.type != "MESH":
|
||||
continue
|
||||
|
||||
if obj in cutting_planes:
|
||||
continue
|
||||
|
||||
RevertClippingPlaneCut.revert_object_mesh(self, obj)
|
||||
|
||||
# localize matrix to consider object's transform
|
||||
ws_to_ls = obj.matrix_world.inverted()
|
||||
rotation = ws_to_ls.to_quaternion()
|
||||
|
||||
mesh = obj.data
|
||||
bm = tool.Blender.get_bmesh_for_mesh(mesh)
|
||||
object_changed = False
|
||||
|
||||
for plane_data in cutting_planes_data:
|
||||
geom = bm.verts[:] + bm.edges[:] + bm.faces[:]
|
||||
plane_co = ws_to_ls @ plane_data.co
|
||||
plane_no = rotation @ plane_data.normal
|
||||
# clear_outer -> remove everything in direction of the normal
|
||||
results = bmesh.ops.bisect_plane(
|
||||
bm,
|
||||
geom=geom,
|
||||
dist=10e-4,
|
||||
plane_co=plane_co,
|
||||
plane_no=plane_no,
|
||||
clear_outer=True,
|
||||
)
|
||||
edges_to_fill = [e for e in results["geom_cut"] if isinstance(e, bmesh.types.BMEdge)]
|
||||
object_changed = object_changed or edges_to_fill
|
||||
bmesh.ops.contextual_create(bm, geom=edges_to_fill)
|
||||
|
||||
# don't swap mesh if it wasn't affected by any of the cutting planes
|
||||
if object_changed:
|
||||
temp_mesh = bpy.data.meshes.new("temp_cut")
|
||||
temp_mesh.BIMMeshProperties.replaced_mesh = mesh
|
||||
for material in mesh.materials:
|
||||
temp_mesh.materials.append(material)
|
||||
obj.data = temp_mesh
|
||||
tool.Blender.apply_bmesh(temp_mesh, bm, obj)
|
||||
|
||||
objects_processed += 1
|
||||
wm.progress_update(obj_i)
|
||||
|
||||
self.report({"INFO"}, f"{objects_processed} processed - {time.time()-t0:.3f} sec")
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
def get_cutting_plane_data(self, cutting_planes: List[bpy.types.Object]) -> List[CuttingPlaneData]:
|
||||
cutting_planes_data = []
|
||||
|
||||
for obj in cutting_planes:
|
||||
cutting_matrix = obj.matrix_world
|
||||
plane_data = CuttingPlaneData(cutting_matrix.translation, cutting_matrix.col[2].to_3d())
|
||||
cutting_planes_data.append(plane_data)
|
||||
|
||||
return cutting_planes_data
|
||||
|
||||
# NOTE: unused, will be used later for cutting boxes support
|
||||
def get_box_cutting_plane_data(self, obj: bpy.types.Object) -> List[CuttingPlaneData]:
|
||||
matrix_world = obj.matrix_world
|
||||
rotation = matrix_world.to_quaternion() # avoid scale for normals
|
||||
cutting_planes_data = []
|
||||
for p in obj.data.polygons:
|
||||
plane_data = CuttingPlaneData(matrix_world @ p.center, rotation @ p.normal)
|
||||
cutting_planes_data.append(plane_data)
|
||||
return cutting_planes_data
|
||||
|
||||
|
||||
class RevertClippingPlaneCut(bpy.types.Operator):
|
||||
bl_idname = "bim.revert_clipping_plane_cut"
|
||||
bl_label = "Revert Clipping Plane Cut"
|
||||
bl_description = "Revert clipping plane cut (switch back to the original mesh)"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.active_object
|
||||
wm = context.window_manager
|
||||
|
||||
objects_processed, t0 = 0, time.time()
|
||||
wm.progress_begin(0, len(context.selected_objects))
|
||||
for obj_i, obj in enumerate(context.selected_objects):
|
||||
if obj.type != "MESH":
|
||||
continue
|
||||
self.revert_object_mesh(obj)
|
||||
objects_processed += 1
|
||||
wm.progress_update(obj_i)
|
||||
wm.progress_end()
|
||||
|
||||
self.report({"INFO"}, f"{objects_processed} processed - {time.time()-t0:.3f} sec")
|
||||
return {"FINISHED"}
|
||||
|
||||
def revert_object_mesh(self, obj):
|
||||
mesh = obj.data
|
||||
replaced_mesh = mesh.BIMMeshProperties.replaced_mesh
|
||||
if replaced_mesh:
|
||||
obj.data = replaced_mesh
|
||||
tool.Blender.remove_data_block(mesh, do_unlink=False)
|
||||
|
||||
@@ -351,6 +351,7 @@ class BIMTabProperties(PropertyGroup):
|
||||
|
||||
|
||||
class BIMProperties(PropertyGroup):
|
||||
is_dirty: BoolProperty(name="Is Dirty", default=False)
|
||||
schema_dir: StringProperty(
|
||||
default=os.path.join(cwd, "schema") + os.path.sep, name="Schema Directory", update=update_schema_dir
|
||||
)
|
||||
@@ -474,6 +475,7 @@ class BIMMeshProperties(PropertyGroup):
|
||||
ifc_parameters: CollectionProperty(name="IFC Parameters", type=IfcParameter)
|
||||
material_checksum: StringProperty(name="Material Checksum", default="[]")
|
||||
mesh_checksum: StringProperty(name="Mesh Checksum", default="")
|
||||
replaced_mesh: PointerProperty(type=bpy.types.Mesh, description="Original mesh to revert section cutaway")
|
||||
|
||||
|
||||
class BIMFacet(PropertyGroup):
|
||||
|
||||
@@ -111,6 +111,23 @@ class BIM_PT_section_plane(Panel):
|
||||
row.operator("bim.remove_section_plane")
|
||||
|
||||
|
||||
class BIM_PT_section_with_cappings(Panel):
|
||||
bl_label = "Section Cutaways With Cappings"
|
||||
bl_idname = "BIM_PT_section_with_cappings"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "output"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_parent_id = "BIM_PT_tab_sandbox"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
wm = context.window_manager
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.clipping_plane_cut_with_cappings", icon="XRAY", text="Cut")
|
||||
row.operator("bim.revert_clipping_plane_cut", icon="FILE_REFRESH", text="Revert Cut")
|
||||
|
||||
|
||||
class BIM_UL_generic(bpy.types.UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if item:
|
||||
@@ -240,8 +257,9 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
|
||||
|
||||
row = layout.row()
|
||||
row.prop(context.scene.BIMModelProperties, "occurrence_name_style")
|
||||
row = layout.row()
|
||||
row.prop(context.scene.BIMModelProperties, "occurrence_name_function")
|
||||
if context.scene.BIMModelProperties == "CUSTOM":
|
||||
row = layout.row()
|
||||
row.prop(context.scene.BIMModelProperties, "occurrence_name_function")
|
||||
|
||||
row = self.layout.row()
|
||||
row.prop(self, "decorations_colour")
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
schema_version = "1.0.0"
|
||||
id = "blenderbim"
|
||||
version = "0.0.999999"
|
||||
name = "BlenderBIM"
|
||||
tagline = "Transforms Blender into a native Building Information Model authoring platform using IFC."
|
||||
maintainer = "Dion Moult <dion@thinkmoult.com>"
|
||||
type = "add-on"
|
||||
permissions = ["files"]
|
||||
website = "https://blenderbim.org/"
|
||||
tags = ["System"]
|
||||
blender_version_min = "3.5.0"
|
||||
license = ["SPDX:GPL-3.0-or-later"]
|
||||
@@ -191,3 +191,24 @@ def select_connection(geometry, connection=None):
|
||||
|
||||
def remove_connection(geometry, connection=None):
|
||||
geometry.remove_connection(connection)
|
||||
|
||||
def get_similar_openings(ifc, opening):
|
||||
model = ifc.get()
|
||||
all_openings = model.by_type("IfcOpeningElement")
|
||||
similar_openings = [o for o in all_openings if o.ObjectPlacement == opening.ObjectPlacement and o != opening]
|
||||
return similar_openings
|
||||
|
||||
def get_similar_openings_building_objs(ifc, similar_openings):
|
||||
building_objs = []
|
||||
for similar_opening in similar_openings:
|
||||
building_objs.append(ifc.get_object(similar_opening.VoidsElements[0].RelatingBuildingElement))
|
||||
return building_objs
|
||||
|
||||
def edit_similar_opening_placement(geometry, opening = None, similar_openings = None):
|
||||
if not opening or not similar_openings:
|
||||
return
|
||||
for similar_opening in similar_openings:
|
||||
old_placement = similar_opening.ObjectPlacement
|
||||
similar_opening.ObjectPlacement = opening.ObjectPlacement
|
||||
geometry.delete_opening_object_placement(old_placement)
|
||||
|
||||
|
||||
@@ -43,9 +43,11 @@ def add_material_set(ifc, material, set_type=None):
|
||||
return ifc_material
|
||||
|
||||
|
||||
def remove_material(ifc, material_tool, style, material=None):
|
||||
def remove_material(ifc, material_tool, style, material=None) -> bool:
|
||||
"""returns True after deleting False,\n
|
||||
returns False if material used in material sets and cannot be removed"""
|
||||
if material_tool.is_material_used_in_sets(material):
|
||||
return
|
||||
return False
|
||||
obj = ifc.get_object(material)
|
||||
ifc.unlink(element=material)
|
||||
ifc.run("material.remove_material", material=material)
|
||||
@@ -53,6 +55,7 @@ def remove_material(ifc, material_tool, style, material=None):
|
||||
material_tool.delete_object(obj)
|
||||
if material_tool.is_editing_materials():
|
||||
material_tool.import_material_definitions(material_tool.get_active_material_type())
|
||||
return True
|
||||
|
||||
|
||||
def remove_material_set(ifc, material_tool, material=None):
|
||||
|
||||
@@ -205,3 +205,10 @@ def toggle_space_visibility(ifc, spatial):
|
||||
if not spaces:
|
||||
return
|
||||
spatial.toggle_spaces_visibility_wired_and_textured(spaces)
|
||||
|
||||
def toggle_hide_spaces(ifc, spatial):
|
||||
model = ifc.get()
|
||||
spaces = model.by_type("IfcSpace")
|
||||
if not spaces:
|
||||
return
|
||||
spatial.toggle_hide_spaces(spaces)
|
||||
|
||||
@@ -76,7 +76,7 @@ class Blender:
|
||||
def get_name(cls, ifc_class, name): pass
|
||||
def get_selected_objects(cls): pass
|
||||
def create_ifc_object(cls, ifc_class: str, name: str = None, data=None): pass
|
||||
def get_obj_ifc_definition_id(cls, obj=None, obj_type=None): pass
|
||||
def get_obj_ifc_definition_id(cls, obj=None, obj_type=None, context=None): pass
|
||||
def is_ifc_object(cls, obj): pass
|
||||
def is_ifc_class_active(cls, ifc_class): pass
|
||||
def get_viewport_context(cls): pass
|
||||
@@ -397,6 +397,7 @@ class Geometry:
|
||||
def should_generate_uvs(cls, obj): pass
|
||||
def should_use_presentation_style_assignment(cls): pass
|
||||
def unresolve_type_representation(cls, representation, element): pass
|
||||
def delete_opening_object_placement(cls, opening): pass
|
||||
|
||||
|
||||
@interface
|
||||
@@ -876,6 +877,7 @@ class Spatial:
|
||||
def assign_relating_type_to_element(cls, ifc, Type, element, relating_type): pass
|
||||
def regen_obj_representation(cls, ifc, geometry, obj, body): pass
|
||||
def toggle_spaces_visibility_wired_and_textured(cls, spaces): pass
|
||||
def toggle_hide_spaces(cls, spaces): pass
|
||||
|
||||
@interface
|
||||
class Covering:
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import blenderbim.core.tool
|
||||
import blenderbim.tool as tool
|
||||
import ifcopenshell.util.element
|
||||
@@ -23,7 +24,7 @@ import ifcopenshell.util.element
|
||||
|
||||
class Aggregate(blenderbim.core.tool.Aggregate):
|
||||
@classmethod
|
||||
def can_aggregate(cls, relating_obj, related_obj):
|
||||
def can_aggregate(cls, relating_obj: bpy.types.Object, related_obj: bpy.types.Object) -> bool:
|
||||
relating_object = tool.Ifc.get_entity(relating_obj)
|
||||
related_object = tool.Ifc.get_entity(related_obj)
|
||||
if not relating_object or not related_object:
|
||||
@@ -60,4 +61,4 @@ class Aggregate(blenderbim.core.tool.Aggregate):
|
||||
def get_relating_object(cls, related_element):
|
||||
for rel in related_element.Decomposes:
|
||||
if rel.is_a("IfcRelAggregates"):
|
||||
return rel.RelatingObject
|
||||
return rel.RelatingObject
|
||||
|
||||
@@ -17,13 +17,17 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import bmesh
|
||||
import json
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.element
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.bim
|
||||
import addon_utils
|
||||
from mathutils import Vector
|
||||
from pathlib import Path
|
||||
import addon_utils
|
||||
from blenderbim.bim.ifc import IFC_CONNECTED_TYPE
|
||||
from typing import Any, Optional, Union, Literal, Iterable, Callable
|
||||
|
||||
|
||||
VIEWPORT_ATTRIBUTES = [
|
||||
@@ -37,13 +41,16 @@ VIEWPORT_ATTRIBUTES = [
|
||||
"clip_planes",
|
||||
]
|
||||
|
||||
OBJECT_DATA_TYPE = Union[bpy.types.Mesh, bpy.types.Curve, bpy.types.Camera]
|
||||
|
||||
|
||||
class Blender(blenderbim.core.tool.Blender):
|
||||
OBJECT_TYPES_THAT_SUPPORT_EDIT_MODE = ("MESH", "CURVE", "SURFACE", "META", "FONT", "LATTICE", "ARMATURE")
|
||||
OBJECT_TYPES_THAT_SUPPORT_EDIT_GPENCIL_MODE = ("GPENCIL",)
|
||||
TYPE_MANAGER_ICON = "LIGHTPROBE_VOLUME" if bpy.app.version >= (4, 1, 0) else "LIGHTPROBE_GRID"
|
||||
|
||||
@classmethod
|
||||
def get_area_props(cls, context):
|
||||
def get_area_props(cls, context: bpy.types.Context) -> Any:
|
||||
try:
|
||||
if context.screen.name.endswith("-nonnormal"): # Ctrl-space temporary fullscreen
|
||||
screen = bpy.data.screens[context.screen.name[0 : -len("-nonnormal")]]
|
||||
@@ -55,12 +62,12 @@ class Blender(blenderbim.core.tool.Blender):
|
||||
return
|
||||
|
||||
@classmethod
|
||||
def set_active_object(cls, obj):
|
||||
def set_active_object(cls, obj: bpy.types.Object) -> None:
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
obj.select_set(True)
|
||||
|
||||
@classmethod
|
||||
def setup_tabs(cls):
|
||||
def setup_tabs(cls) -> None:
|
||||
# https://blender.stackexchange.com/questions/140644/how-can-make-the-state-of-a-boolean-property-relative-to-the-3d-view-area
|
||||
for screen in bpy.data.screens:
|
||||
if len(screen.BIMAreaProperties) == 20:
|
||||
@@ -70,7 +77,7 @@ class Blender(blenderbim.core.tool.Blender):
|
||||
screen.BIMAreaProperties.add()
|
||||
|
||||
@classmethod
|
||||
def is_tab(cls, context, tab):
|
||||
def is_tab(cls, context: bpy.types.Context, tab: str) -> bool:
|
||||
aprops = cls.get_area_props(context)
|
||||
if not aprops:
|
||||
return context.screen.BIMTabProperties.tab == tab
|
||||
@@ -79,7 +86,7 @@ class Blender(blenderbim.core.tool.Blender):
|
||||
return aprops.tab == tab
|
||||
|
||||
@classmethod
|
||||
def is_default_scene(cls):
|
||||
def is_default_scene(cls) -> bool:
|
||||
if len(bpy.context.scene.objects) != 3:
|
||||
return False
|
||||
if {obj.type for obj in bpy.context.scene.objects} == {"MESH", "LIGHT", "CAMERA"}:
|
||||
@@ -87,7 +94,7 @@ class Blender(blenderbim.core.tool.Blender):
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def get_name(cls, ifc_class, name):
|
||||
def get_name(cls, ifc_class: str, name: str) -> str:
|
||||
if not bpy.data.objects.get(f"{ifc_class}/{name}"):
|
||||
return name
|
||||
i = 2
|
||||
@@ -96,13 +103,15 @@ class Blender(blenderbim.core.tool.Blender):
|
||||
return f"{name} {i}"
|
||||
|
||||
@classmethod
|
||||
def get_selected_objects(cls):
|
||||
def get_selected_objects(cls) -> set[bpy.types.Object]:
|
||||
if bpy.context.selected_objects:
|
||||
return set(bpy.context.selected_objects + [bpy.context.active_object])
|
||||
return set([bpy.context.active_object])
|
||||
|
||||
@classmethod
|
||||
def create_ifc_object(cls, ifc_class: str, name: str = None, data=None) -> bpy.types.Object:
|
||||
def create_ifc_object(
|
||||
cls, ifc_class: str, name: Optional[str] = None, data: Optional[OBJECT_DATA_TYPE] = None
|
||||
) -> bpy.types.Object:
|
||||
name = name or "My " + ifc_class
|
||||
name = cls.get_name(ifc_class, name)
|
||||
obj = bpy.data.objects.new(name, data)
|
||||
@@ -110,7 +119,11 @@ class Blender(blenderbim.core.tool.Blender):
|
||||
return obj
|
||||
|
||||
@classmethod
|
||||
def get_obj_ifc_definition_id(cls, obj=None, obj_type=None):
|
||||
def get_obj_ifc_definition_id(
|
||||
cls, obj: Optional[str] = None, obj_type: Optional[str] = None, context: Optional[bpy.types.Context] = None
|
||||
) -> Union[int, None]:
|
||||
if context is None:
|
||||
context = bpy.context
|
||||
if obj_type == "Object":
|
||||
return bpy.data.objects.get(obj).BIMObjectProperties.ifc_definition_id
|
||||
elif obj_type == "Material":
|
||||
@@ -122,41 +135,42 @@ class Blender(blenderbim.core.tool.Blender):
|
||||
elif obj_type == "MaterialSetItem":
|
||||
return bpy.data.objects.get(obj).BIMObjectMaterialProperties.active_material_set_item_id
|
||||
elif obj_type == "Task":
|
||||
return bpy.context.scene.BIMTaskTreeProperties.tasks[
|
||||
bpy.context.scene.BIMWorkScheduleProperties.active_task_index
|
||||
return context.scene.BIMTaskTreeProperties.tasks[
|
||||
context.scene.BIMWorkScheduleProperties.active_task_index
|
||||
].ifc_definition_id
|
||||
elif obj_type == "Cost":
|
||||
return bpy.context.scene.BIMCostProperties.cost_items[
|
||||
bpy.context.scene.BIMCostProperties.active_cost_item_index
|
||||
return context.scene.BIMCostProperties.cost_items[
|
||||
context.scene.BIMCostProperties.active_cost_item_index
|
||||
].ifc_definition_id
|
||||
elif obj_type == "Resource":
|
||||
return bpy.context.scene.BIMResourceTreeProperties.resources[
|
||||
bpy.context.scene.BIMResourceProperties.active_resource_index
|
||||
return context.scene.BIMResourceTreeProperties.resources[
|
||||
context.scene.BIMResourceProperties.active_resource_index
|
||||
].ifc_definition_id
|
||||
elif obj_type == "Profile":
|
||||
return bpy.context.scene.BIMProfileProperties.profiles[
|
||||
bpy.context.scene.BIMProfileProperties.active_profile_index
|
||||
return context.scene.BIMProfileProperties.profiles[
|
||||
context.scene.BIMProfileProperties.active_profile_index
|
||||
].ifc_definition_id
|
||||
elif obj_type == "WorkSchedule":
|
||||
return bpy.context.scene.BIMWorkScheduleProperties.active_work_schedule_id
|
||||
return context.scene.BIMWorkScheduleProperties.active_work_schedule_id
|
||||
elif obj_type == "Group":
|
||||
prop = bpy.context.scene.BIMGroupProperties
|
||||
prop = context.scene.BIMGroupProperties
|
||||
return prop.groups[prop.active_group_index].ifc_definition_id
|
||||
|
||||
@classmethod
|
||||
def is_ifc_object(cls, obj):
|
||||
def is_ifc_object(cls, obj: bpy.types.Object) -> bool:
|
||||
return bool(obj.BIMObjectProperties.ifc_definition_id)
|
||||
|
||||
@classmethod
|
||||
def is_ifc_class_active(cls, ifc_class):
|
||||
if bpy.context.active_object:
|
||||
if cls.is_ifc_object(bpy.context.active_object):
|
||||
return tool.Ifc.get_entity(bpy.context.active_object).is_a(ifc_class)
|
||||
def is_ifc_class_active(cls, ifc_class: str) -> bool:
|
||||
obj = bpy.context.active_object
|
||||
if obj:
|
||||
if cls.is_ifc_object(obj):
|
||||
return tool.Ifc.get_entity(obj).is_a(ifc_class)
|
||||
return False
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def show_info_message(cls, text, message_type="INFO"):
|
||||
def show_info_message(cls, text: str, message_type: Literal["INFO", "ERROR"] = "INFO") -> None:
|
||||
"""useful for showing error messages outside blender operators
|
||||
|
||||
Possible `message_type`: `INFO` / `ERROR`"""
|
||||
@@ -167,14 +181,14 @@ class Blender(blenderbim.core.tool.Blender):
|
||||
bpy.context.window_manager.popup_menu(message_ui, title=message_type.capitalize(), icon=message_type)
|
||||
|
||||
@classmethod
|
||||
def get_view3d_area(cls):
|
||||
def get_view3d_area(cls) -> Union[bpy.types.Area, None]:
|
||||
for window in bpy.context.window_manager.windows:
|
||||
for area in window.screen.areas:
|
||||
if area.type == "VIEW_3D":
|
||||
return area
|
||||
|
||||
@classmethod
|
||||
def get_blender_prop_default_value(cls, props, prop_name):
|
||||
def get_blender_prop_default_value(cls, props, prop_name: str) -> Any:
|
||||
prop_bl_rna = props.bl_rna.properties[prop_name]
|
||||
if getattr(prop_bl_rna, "array_length", 0) > 0:
|
||||
prop_value = prop_bl_rna.default_array
|
||||
@@ -183,7 +197,7 @@ class Blender(blenderbim.core.tool.Blender):
|
||||
return prop_value
|
||||
|
||||
@classmethod
|
||||
def get_viewport_context(cls):
|
||||
def get_viewport_context(cls) -> dict:
|
||||
"""Get viewport area context for context overriding.
|
||||
|
||||
Useful for calling operators outside viewport context.
|
||||
@@ -198,25 +212,25 @@ class Blender(blenderbim.core.tool.Blender):
|
||||
return context_override
|
||||
|
||||
@classmethod
|
||||
def get_viewport_position(cls):
|
||||
def get_viewport_position(cls) -> dict:
|
||||
region_3d = cls.get_viewport_context()["area"].spaces[0].region_3d
|
||||
copy_if_possible = lambda x: x.copy() if hasattr(x, "copy") else x
|
||||
viewport_data = {attr: copy_if_possible(getattr(region_3d, attr)) for attr in VIEWPORT_ATTRIBUTES}
|
||||
return viewport_data
|
||||
|
||||
@classmethod
|
||||
def set_viewport_position(cls, data):
|
||||
def set_viewport_position(cls, data: dict) -> None:
|
||||
region_3d = cls.get_viewport_context()["area"].spaces[0].region_3d
|
||||
for attr in VIEWPORT_ATTRIBUTES:
|
||||
setattr(region_3d, attr, data[attr])
|
||||
|
||||
@classmethod
|
||||
def set_viewport_tool(cls, tool_name):
|
||||
def set_viewport_tool(cls, tool_name: str) -> None:
|
||||
with bpy.context.temp_override(**tool.Blender.get_viewport_context()):
|
||||
bpy.ops.wm.tool_set_by_id(name=tool_name)
|
||||
|
||||
@classmethod
|
||||
def get_shader_editor_context(cls):
|
||||
def get_shader_editor_context(cls) -> Union[dict, None]:
|
||||
for screen in bpy.data.screens:
|
||||
for area in screen.areas:
|
||||
if area.type == "NODE_EDITOR":
|
||||
@@ -226,7 +240,7 @@ class Blender(blenderbim.core.tool.Blender):
|
||||
return context_override
|
||||
|
||||
@classmethod
|
||||
def copy_node_graph(cls, material_to, material_from):
|
||||
def copy_node_graph(cls, material_to: bpy.types.Material, material_from: bpy.types.Material) -> None:
|
||||
# https://projects.blender.org/blender/blender/issues/108763
|
||||
if bpy.app.version[:2] == (4, 0):
|
||||
print(
|
||||
@@ -270,7 +284,9 @@ class Blender(blenderbim.core.tool.Blender):
|
||||
shader_editor.pin = previous_pin_setting
|
||||
|
||||
@classmethod
|
||||
def get_material_node(cls, blender_material, node_type, kwargs={}):
|
||||
def get_material_node(
|
||||
cls, blender_material: bpy.types.Material, node_type: str, kwargs: Optional[dict] = {}
|
||||
) -> Union[bpy.types.ShaderNode, None]:
|
||||
"""returns first node from the `blender_material` shader graph with type `node_type`"""
|
||||
if not blender_material.use_nodes:
|
||||
return
|
||||
@@ -280,15 +296,15 @@ class Blender(blenderbim.core.tool.Blender):
|
||||
return node
|
||||
|
||||
@classmethod
|
||||
def update_screen(cls):
|
||||
def update_screen(cls) -> None:
|
||||
bpy.ops.wm.redraw_timer(type="DRAW_WIN_SWAP", iterations=1)
|
||||
|
||||
@classmethod
|
||||
def update_viewport(cls):
|
||||
def update_viewport(cls) -> None:
|
||||
tool.Blender.get_viewport_context()["area"].tag_redraw()
|
||||
|
||||
@classmethod
|
||||
def force_depsgraph_update(cls):
|
||||
def force_depsgraph_update(cls) -> None:
|
||||
"""useful if you need to trigger callbacks like `depsgraph_update_pre`"""
|
||||
# blender is requiring some ID to be changed
|
||||
# to trigger depsgraph update
|
||||
@@ -297,9 +313,11 @@ class Blender(blenderbim.core.tool.Blender):
|
||||
bpy.context.view_layer.update()
|
||||
|
||||
@classmethod
|
||||
def ensure_unique_name(cls, name, objects, iteration=0):
|
||||
def ensure_unique_name(cls, name: str, objects: Iterable[str], iteration=0) -> str:
|
||||
"""returns a unique name for the given name and dictionary of objects
|
||||
blender style name with .001, .002, etc. suffix
|
||||
blender style name with .001, .002, etc. suffix.
|
||||
|
||||
objects can be `bpy.data.objects`.
|
||||
"""
|
||||
current_iteration = name if not iteration else f"{name}.{iteration:03d}"
|
||||
if current_iteration not in objects:
|
||||
@@ -307,7 +325,7 @@ class Blender(blenderbim.core.tool.Blender):
|
||||
return cls.ensure_unique_name(name, objects, iteration + 1)
|
||||
|
||||
@classmethod
|
||||
def blender_path_to_posix(cls, blender_path):
|
||||
def blender_path_to_posix(cls, blender_path: str) -> str:
|
||||
"""Process blender path to be saved as posix.
|
||||
|
||||
If path is relative the method will keep it relative to .ifc file
|
||||
@@ -322,7 +340,7 @@ class Blender(blenderbim.core.tool.Blender):
|
||||
return path.as_posix()
|
||||
|
||||
@classmethod
|
||||
def get_default_selection_keypmap(cls):
|
||||
def get_default_selection_keypmap(cls) -> tuple:
|
||||
"""keymap to replicate default blender selection behaviour with click and box selection"""
|
||||
# code below comes from blender_default.py which is part of default blender scripts licensed under GPL v2
|
||||
# https://github.com/blender/blender/blob/master/release/scripts/presets/keyconfig/keymap_data/blender_default.py
|
||||
@@ -369,7 +387,9 @@ class Blender(blenderbim.core.tool.Blender):
|
||||
return keymap
|
||||
|
||||
@classmethod
|
||||
def add_layout_hotkey_operator(cls, tool_name, layout, text, hotkey, description):
|
||||
def add_layout_hotkey_operator(
|
||||
cls, tool_name: str, layout: bpy.types.UILayout, text: str, hotkey: str, description: str
|
||||
) -> tuple[bpy.types.OperatorProperties, bpy.types.UILayout]:
|
||||
modifiers = {
|
||||
"A": "EVENT_ALT",
|
||||
"S": "EVENT_SHIFT",
|
||||
@@ -386,7 +406,7 @@ class Blender(blenderbim.core.tool.Blender):
|
||||
return op, row
|
||||
|
||||
@classmethod
|
||||
def get_object_bounding_box(cls, obj):
|
||||
def get_object_bounding_box(cls, obj: bpy.types.Object) -> dict:
|
||||
"""Returns dict with local min and max x, y, z values for the object.
|
||||
|
||||
Careful with using this method for objects in EDIT mode because
|
||||
@@ -407,14 +427,20 @@ class Blender(blenderbim.core.tool.Blender):
|
||||
return bbox_dict
|
||||
|
||||
@classmethod
|
||||
def select_and_activate_single_object(cls, context, active_object):
|
||||
def select_and_activate_single_object(cls, context: bpy.types.Context, active_object: bpy.types.Object) -> None:
|
||||
for obj in context.selected_objects:
|
||||
obj.select_set(False)
|
||||
context.view_layer.objects.active = active_object
|
||||
active_object.select_set(True)
|
||||
|
||||
@classmethod
|
||||
def set_objects_selection(cls, context, active_object, selected_objects, clear_previous_selection=True):
|
||||
def set_objects_selection(
|
||||
cls,
|
||||
context: bpy.types.Context,
|
||||
active_object: Optional[bpy.types.Object] = None,
|
||||
selected_objects: list[bpy.types.Object] = list(),
|
||||
clear_previous_selection=True,
|
||||
) -> None:
|
||||
if clear_previous_selection:
|
||||
for obj in context.selected_objects:
|
||||
obj.select_set(False)
|
||||
@@ -425,7 +451,7 @@ class Blender(blenderbim.core.tool.Blender):
|
||||
active_object.select_set(True)
|
||||
|
||||
@classmethod
|
||||
def enum_property_has_valid_index(cls, props, prop_name, enum_items):
|
||||
def enum_property_has_valid_index(cls, props, prop_name: str, enum_items: tuple) -> bool:
|
||||
"""method created for readibility and to avoid console warnings like
|
||||
`pyrna_enum_to_py: current value '17' matches no enum in 'BIMModelProperties', '', 'relating_type_id'`
|
||||
"""
|
||||
@@ -436,7 +462,7 @@ class Blender(blenderbim.core.tool.Blender):
|
||||
return current_value_index < len(enum_items)
|
||||
|
||||
@classmethod
|
||||
def append_data_block(cls, filepath, data_block_type, name, link=False, relative=False):
|
||||
def append_data_block(cls, filepath: str, data_block_type: str, name: str, link=False, relative=False) -> dict:
|
||||
if Path(filepath) == Path(bpy.data.filepath):
|
||||
data_block = getattr(bpy.data, data_block_type).get(name, None)
|
||||
if not data_block:
|
||||
@@ -450,13 +476,29 @@ class Blender(blenderbim.core.tool.Blender):
|
||||
return {"data_block": getattr(data_to, data_block_type)[0], "msg": ""}
|
||||
|
||||
@classmethod
|
||||
def remove_data_block(cls, data_block):
|
||||
def remove_data_block(cls, data_block: bpy.types.ID, do_unlink=True) -> None:
|
||||
"""Removes a datablock (such as a mesh)
|
||||
|
||||
See https://projects.blender.org/blender/blender/issues/118787 for more
|
||||
details about do_unlink.
|
||||
|
||||
:param data_block: The bpy.data datablock to delete.
|
||||
:param do_unlink: Whether or not to unlink the datablock first. This
|
||||
defaults to true, which is Blender's default behaviour. If you are
|
||||
sure that the data block has zero users, then you can set this
|
||||
to False, which will make datablock deletion significantly faster
|
||||
by avoiding unnecessary Blender data checks.
|
||||
:return: None
|
||||
:rtype: None
|
||||
"""
|
||||
collection_name = repr(data_block).split(".", 2)[-1].split("[", 1)[0]
|
||||
getattr(bpy.data, collection_name).remove(data_block)
|
||||
getattr(bpy.data, collection_name).remove(
|
||||
data_block, do_unlink=do_unlink, do_id_user=do_unlink, do_ui_user=do_unlink
|
||||
)
|
||||
|
||||
## BMESH UTILS ##
|
||||
@classmethod
|
||||
def apply_bmesh(cls, mesh, bm, obj=None):
|
||||
def apply_bmesh(cls, mesh: bpy.types.Mesh, bm: bmesh.types.BMesh, obj: Optional[bpy.types.Object] = None) -> None:
|
||||
"""`obj` argument is not optional if you plan to update mesh in EDIT mode
|
||||
and it's possible that that mesh object won't be currenly active."""
|
||||
import bmesh
|
||||
@@ -490,7 +532,7 @@ class Blender(blenderbim.core.tool.Blender):
|
||||
mesh.update()
|
||||
|
||||
@classmethod
|
||||
def get_bmesh_for_mesh(cls, mesh, clean=False):
|
||||
def get_bmesh_for_mesh(cls, mesh: bpy.types.Mesh, clean=False) -> bmesh.types.BMesh:
|
||||
import bmesh
|
||||
|
||||
if mesh.is_editmode:
|
||||
@@ -504,7 +546,16 @@ class Blender(blenderbim.core.tool.Blender):
|
||||
return bm
|
||||
|
||||
@classmethod
|
||||
def bmesh_join(cls, bm_a, bm_b, callback=None):
|
||||
def bmesh_join(
|
||||
cls,
|
||||
bm_a: bmesh.types.BMesh,
|
||||
bm_b: bmesh.types.BMesh,
|
||||
callback: Optional[
|
||||
Callable[
|
||||
[bmesh.types.BMesh, list[bmesh.types.BMVert], list[bmesh.types.BMEdge], list[bmesh.types.BMFace]], None
|
||||
]
|
||||
] = None,
|
||||
):
|
||||
"""Join two meshes into single one, store it in `bm_a`"""
|
||||
import bmesh
|
||||
|
||||
@@ -519,7 +570,7 @@ class Blender(blenderbim.core.tool.Blender):
|
||||
return bm_a
|
||||
|
||||
@classmethod
|
||||
def toggle_edit_mode(cls, context):
|
||||
def toggle_edit_mode(cls, context: bpy.types.Context) -> set:
|
||||
ao = context.active_object
|
||||
if not ao:
|
||||
return {"CANCELLED"}
|
||||
@@ -531,26 +582,72 @@ class Blender(blenderbim.core.tool.Blender):
|
||||
return {"CANCELLED"}
|
||||
|
||||
@classmethod
|
||||
def is_object_an_ifc_class(cls, obj, classes):
|
||||
def is_object_an_ifc_class(cls, obj: bpy.types.Object, classes: Iterable[str]) -> bool:
|
||||
if not tool.Ifc.get():
|
||||
return False
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
return element and element.is_a() in classes
|
||||
|
||||
@classmethod
|
||||
def get_object_from_guid(cls, guid):
|
||||
def get_object_from_guid(cls, guid: str) -> Union[IFC_CONNECTED_TYPE, None]:
|
||||
element = tool.Ifc.get().by_guid(guid)
|
||||
obj = tool.Ifc.get_object(element)
|
||||
if obj:
|
||||
return obj
|
||||
|
||||
@classmethod
|
||||
def lock_transform(cls, obj, lock_state=True):
|
||||
def lock_transform(cls, obj: bpy.types.ObjectBase, lock_state=True) -> None:
|
||||
for prop in ("lock_location", "lock_rotation", "lock_scale"):
|
||||
attr = getattr(obj, prop)
|
||||
for axis_idx in range(3):
|
||||
attr[axis_idx] = lock_state
|
||||
|
||||
operator_invoke_filepath_hotkeys_description = "Hold Shift to open the file, Alt to browse containing directory"
|
||||
|
||||
@classmethod
|
||||
def operator_invoke_filepath_hotkeys(
|
||||
cls, operator: bpy.types.Operator, context: bpy.types.Context, event: bpy.types.Event, filepath: Path
|
||||
) -> Union[set, None]:
|
||||
if not event.alt and not event.shift:
|
||||
return
|
||||
|
||||
import platform
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
def open_file_or_folder(path):
|
||||
if platform.system() == "Windows":
|
||||
os.startfile(path)
|
||||
elif platform.system() == "Darwin":
|
||||
subprocess.Popen(["open", path])
|
||||
else:
|
||||
subprocess.Popen(["xdg-open", path])
|
||||
|
||||
# resolve relative filepaths with .blend path by default
|
||||
if not filepath.is_absolute():
|
||||
if bpy.data.filepath:
|
||||
filepath = Path(bpy.data.filepath).parent / filepath
|
||||
else:
|
||||
operator.report({"ERROR"}, f'Couldn\'t resolve relative filepath "{filepath.as_posix()}"')
|
||||
return {"CANCELLED"}
|
||||
|
||||
# holding ALT - open file directory
|
||||
if event.alt == True:
|
||||
# open directory
|
||||
filepath = filepath.parent
|
||||
if not filepath.exists():
|
||||
operator.report({"ERROR"}, f'Cannot open non-existing directory: "{filepath.as_posix()}"')
|
||||
return {"CANCELLED"}
|
||||
open_file_or_folder(filepath.as_posix())
|
||||
return {"PASS_THROUGH"}
|
||||
|
||||
# holding sHIFT - open file
|
||||
if not filepath.exists():
|
||||
operator.report({"ERROR"}, f'Cannot open non-existing file: "{filepath.as_posix()}"')
|
||||
return {"CANCELLED"}
|
||||
open_file_or_folder(filepath.as_posix())
|
||||
return {"PASS_THROUGH"}
|
||||
|
||||
class Modifier:
|
||||
@classmethod
|
||||
def is_eligible_for_railing_modifier(cls, obj):
|
||||
|
||||
@@ -72,7 +72,11 @@ class Boundary(blenderbim.core.tool.Boundary):
|
||||
obj.matrix_world = space.matrix_world
|
||||
|
||||
@classmethod
|
||||
def decorate_boundary(cls, obj):
|
||||
def decorate_boundary(cls, obj: bpy.types.Object) -> None:
|
||||
new = bpy.context.scene.BIMBoundaryProperties.boundaries.add()
|
||||
new.obj = obj
|
||||
obj.show_in_front = True
|
||||
|
||||
@classmethod
|
||||
def undecorate_boundary(cls, obj: bpy.types.Object) -> None:
|
||||
obj.show_in_front = False
|
||||
|
||||
@@ -21,11 +21,12 @@ import ifcopenshell.util.element
|
||||
|
||||
import blenderbim.core.tool
|
||||
import blenderbim.tool as tool
|
||||
from typing import Union
|
||||
|
||||
|
||||
class Collector(blenderbim.core.tool.Collector):
|
||||
@classmethod
|
||||
def sync(cls, obj):
|
||||
def sync(cls, obj: bpy.types.Object) -> None:
|
||||
# This is the reverse of assign. It reads the Blender collection and figures out its IFC hierarchy
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
|
||||
@@ -76,12 +77,15 @@ class Collector(blenderbim.core.tool.Collector):
|
||||
blenderbim.core.spatial.assign_container(
|
||||
tool.Ifc, tool.Collector, tool.Spatial, structure_obj=parent_obj, element_obj=obj
|
||||
)
|
||||
# NOTE: won't allow assigning IfcElements to the IfcProject directly
|
||||
# and some elements might get missing in other viewers if they're don't support displaying
|
||||
# elements without hierarchy
|
||||
blenderbim.core.aggregate.assign_object(
|
||||
tool.Ifc, tool.Aggregate, tool.Collector, relating_obj=parent_obj, related_obj=obj
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def assign(cls, obj):
|
||||
def assign(cls, obj: bpy.types.Object) -> None:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
|
||||
object_collection = None
|
||||
@@ -112,7 +116,9 @@ class Collector(blenderbim.core.tool.Collector):
|
||||
bpy.data.collections.remove(obj.BIMObjectProperties.collection)
|
||||
|
||||
@classmethod
|
||||
def _get_own_collection(cls, element, obj):
|
||||
def _get_own_collection(
|
||||
cls, element: ifcopenshell.entity_instance, obj: bpy.types.Object
|
||||
) -> Union[bpy.types.Collection, None]:
|
||||
if element.is_a("IfcProject"):
|
||||
return cls._create_own_collection(obj)
|
||||
|
||||
@@ -161,7 +167,7 @@ class Collector(blenderbim.core.tool.Collector):
|
||||
return cls._create_own_collection(obj)
|
||||
|
||||
@classmethod
|
||||
def _get_collection(cls, element, obj):
|
||||
def _get_collection(cls, element: ifcopenshell.entity_instance, obj: bpy.types.Object) -> bpy.types.Collection:
|
||||
if element.is_a("IfcTypeObject"):
|
||||
return cls._create_project_child_collection("Types")
|
||||
|
||||
@@ -229,7 +235,7 @@ class Collector(blenderbim.core.tool.Collector):
|
||||
return collection
|
||||
|
||||
@classmethod
|
||||
def _create_project_child_collection(cls, name):
|
||||
def _create_project_child_collection(cls, name: str) -> bpy.types.Collection:
|
||||
collection = bpy.data.collections.get(name)
|
||||
if not collection:
|
||||
collection = bpy.data.collections.new(name)
|
||||
@@ -239,7 +245,7 @@ class Collector(blenderbim.core.tool.Collector):
|
||||
return collection
|
||||
|
||||
@classmethod
|
||||
def _create_own_collection(cls, obj):
|
||||
def _create_own_collection(cls, obj: bpy.types.Object) -> bpy.types.Collection:
|
||||
if obj.BIMObjectProperties.collection:
|
||||
return obj.BIMObjectProperties.collection
|
||||
collection = bpy.data.collections.new(obj.name)
|
||||
|
||||
@@ -35,6 +35,8 @@ import blenderbim.core.tool
|
||||
import blenderbim.core.geometry
|
||||
import blenderbim.tool as tool
|
||||
import ifcopenshell.util.representation
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.selector
|
||||
import blenderbim.bim.module.drawing.sheeter as sheeter
|
||||
import blenderbim.bim.module.drawing.scheduler as scheduler
|
||||
import blenderbim.bim.module.drawing.annotation as annotation
|
||||
@@ -45,11 +47,12 @@ from lxml import etree
|
||||
from mathutils import Vector
|
||||
from fractions import Fraction
|
||||
import collections
|
||||
from typing import Optional, Union
|
||||
|
||||
|
||||
class Drawing(blenderbim.core.tool.Drawing):
|
||||
@classmethod
|
||||
def canonicalise_class_name(self, name):
|
||||
def canonicalise_class_name(cls, name):
|
||||
return re.sub("[^0-9a-zA-Z]+", "", name)
|
||||
|
||||
@classmethod
|
||||
@@ -417,7 +420,7 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
return obj.BIMObjectProperties.collection
|
||||
|
||||
@classmethod
|
||||
def get_drawing_group(cls, drawing):
|
||||
def get_drawing_group(cls, drawing: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
|
||||
for rel in drawing.HasAssignments or []:
|
||||
if rel.is_a("IfcRelAssignsToGroup") and rel.RelatingGroup.ObjectType == "DRAWING":
|
||||
return rel.RelatingGroup
|
||||
@@ -441,7 +444,7 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
return ifcopenshell.util.element.get_psets(drawing).get("EPset_Drawing", {}).get("TargetView", "MODEL_VIEW")
|
||||
|
||||
@classmethod
|
||||
def get_group_elements(cls, group):
|
||||
def get_group_elements(cls, group: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
|
||||
for rel in group.IsGroupedBy or []:
|
||||
return rel.RelatedObjects
|
||||
|
||||
@@ -676,13 +679,13 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
camera.BIMCameraProperties.custom_scale_numerator = numerator
|
||||
camera.BIMCameraProperties.custom_scale_denominator = denominator
|
||||
if "HasUnderlay" in pset:
|
||||
camera.BIMCameraProperties.has_underlay = pset["HasUnderlay"]
|
||||
camera.BIMCameraProperties.has_underlay = bool(pset["HasUnderlay"])
|
||||
if "HasLinework" in pset:
|
||||
camera.BIMCameraProperties.has_linework = pset["HasLinework"]
|
||||
camera.BIMCameraProperties.has_linework = bool(pset["HasLinework"])
|
||||
if "HasAnnotation" in pset:
|
||||
camera.BIMCameraProperties.has_annotation = pset["HasAnnotation"]
|
||||
camera.BIMCameraProperties.has_annotation = bool(pset["HasAnnotation"])
|
||||
if "IsNTS" in pset:
|
||||
camera.BIMCameraProperties.is_nts = pset["IsNTS"]
|
||||
camera.BIMCameraProperties.is_nts = bool(pset["IsNTS"])
|
||||
|
||||
tool.Loader.link_mesh(shape, camera)
|
||||
|
||||
@@ -837,7 +840,7 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
if platform.system() == "Darwin":
|
||||
subprocess.call(("open", path))
|
||||
elif platform.system() == "Windows":
|
||||
os.startfile(path)
|
||||
os.startfile(os.path.normpath(path))
|
||||
else:
|
||||
subprocess.call(("xdg-open", path))
|
||||
|
||||
@@ -1502,19 +1505,27 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
|
||||
@classmethod
|
||||
def get_drawing_metadata(cls, drawing):
|
||||
# fmt: off
|
||||
return [
|
||||
v.strip()
|
||||
for v in ifcopenshell.util.element.get_psets(drawing)["EPset_Drawing"].get("Metadata", "").split(",")
|
||||
for v in (
|
||||
ifcopenshell.util.element.get_psets(drawing)["EPset_Drawing"].get("Metadata", "") or ""
|
||||
).split(",")
|
||||
if v
|
||||
]
|
||||
# fmt: on
|
||||
|
||||
@classmethod
|
||||
def get_annotation_z_index(cls, drawing):
|
||||
return ifcopenshell.util.element.get_pset(drawing, "EPset_Annotation", "ZIndex") or 0
|
||||
|
||||
@classmethod
|
||||
def get_annotation_symbol(cls, drawing):
|
||||
return ifcopenshell.util.element.get_pset(drawing, "EPset_Annotation", "Symbol")
|
||||
def get_annotation_symbol(cls, element: ifcopenshell.entity_instance) -> Union[str, None]:
|
||||
symbol = ifcopenshell.util.element.get_pset(element, "EPset_Annotation", "Symbol")
|
||||
if not symbol:
|
||||
# EPset_AnnotationSurveyArea is not standard! See bSI-4.3 proposal #660.
|
||||
symbol = ifcopenshell.util.element.get_pset(element, "EPset_AnnotationSurveyArea", "PointType")
|
||||
return symbol
|
||||
|
||||
@classmethod
|
||||
def has_linework(cls, drawing):
|
||||
@@ -1525,7 +1536,7 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
return ifcopenshell.util.element.get_psets(drawing).get("EPset_Drawing", {}).get("HasAnnotation", False)
|
||||
|
||||
@classmethod
|
||||
def get_drawing_elements(cls, drawing):
|
||||
def get_drawing_elements(cls, drawing: ifcopenshell.entity_instance) -> set[ifcopenshell.entity_instance]:
|
||||
"""returns a set of elements that are included in the drawing"""
|
||||
ifc_file = tool.Ifc.get()
|
||||
pset = ifcopenshell.util.element.get_psets(drawing).get("EPset_Drawing", {})
|
||||
@@ -1539,6 +1550,10 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
else:
|
||||
base_elements = set(ifc_file.by_type("IfcElement") + ifc_file.by_type("IfcSpatialElement"))
|
||||
elements = {e for e in (elements & base_elements) if e.is_a() != "IfcSpace"}
|
||||
|
||||
# exclude annotations to avoid including annotations from other drawings
|
||||
elements = {i for i in elements if not i.is_a("IfcAnnotation")}
|
||||
# add annotations from the current drawing
|
||||
annotations = tool.Drawing.get_group_elements(tool.Drawing.get_drawing_group(drawing))
|
||||
elements.update(annotations)
|
||||
|
||||
@@ -1549,15 +1564,13 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
return elements
|
||||
|
||||
@classmethod
|
||||
def get_drawing_spaces(cls, drawing):
|
||||
def get_drawing_spaces(cls, drawing: ifcopenshell.entity_instance) -> set[ifcopenshell.entity_instance]:
|
||||
ifc_file = tool.Ifc.get()
|
||||
pset = ifcopenshell.util.element.get_psets(drawing).get("EPset_Drawing", {})
|
||||
include = pset.get("Include", None)
|
||||
elements = cls.get_elements_in_camera_view(
|
||||
tool.Ifc.get_object(drawing), [tool.Ifc.get_object(e) for e in ifc_file.by_type("IfcSpace")]
|
||||
)
|
||||
if include:
|
||||
elements = ifcopenshell.util.selector.filter_elements(ifc_file, include, elements=elements)
|
||||
# NOTE: EPset_Drawing.Include is not used to avoid adding other elements besides spaces
|
||||
exclude = pset.get("Exclude", None)
|
||||
if exclude:
|
||||
elements -= ifcopenshell.util.selector.filter_elements(ifc_file, exclude)
|
||||
@@ -1613,9 +1626,14 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
area = tool.Blender.get_view3d_area()
|
||||
is_local_view = area.spaces[0].local_view is not None
|
||||
if is_local_view:
|
||||
bpy.ops.view3d.localview()
|
||||
# turn off local view before activating drawing, and then turn it on again.
|
||||
for a in bpy.context.screen.areas:
|
||||
if a.type == "VIEW_3D":
|
||||
override = bpy.context.copy()
|
||||
override["area"] = a
|
||||
bpy.ops.view3d.localview(override)
|
||||
bpy.context.scene.camera = camera
|
||||
bpy.ops.view3d.localview()
|
||||
bpy.ops.view3d.localview(override)
|
||||
else:
|
||||
bpy.context.scene.camera = camera
|
||||
area.spaces[0].region_3d.view_perspective = "CAMERA"
|
||||
@@ -1717,7 +1735,9 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
obj.hide_render = True
|
||||
|
||||
@classmethod
|
||||
def get_elements_in_camera_view(cls, camera, objs):
|
||||
def get_elements_in_camera_view(
|
||||
cls, camera: bpy.types.Object, objs: list[ifcopenshell.entity_instance]
|
||||
) -> set[ifcopenshell.entity_instance]:
|
||||
props = camera.data.BIMCameraProperties
|
||||
x = props.width
|
||||
y = props.height
|
||||
|
||||
@@ -26,11 +26,13 @@ import ifcopenshell
|
||||
import blenderbim.core.tool
|
||||
import blenderbim.core.style
|
||||
import blenderbim.core.spatial
|
||||
import blenderbim.core.geometry
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.bim.import_ifc
|
||||
from math import radians, pi
|
||||
from mathutils import Vector, Matrix
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from typing import List
|
||||
|
||||
|
||||
class Geometry(blenderbim.core.tool.Geometry):
|
||||
@@ -101,6 +103,8 @@ class Geometry(blenderbim.core.tool.Geometry):
|
||||
parent_collection = parent_obj.BIMObjectProperties.collection
|
||||
for child in collection.children:
|
||||
parent_collection.children.link(child)
|
||||
for child_object in collection.objects:
|
||||
parent_collection.objects.link(child_object)
|
||||
bpy.data.collections.remove(collection)
|
||||
if getattr(element, "FillsVoids", None):
|
||||
bpy.ops.bim.remove_filling(filling=element.id())
|
||||
@@ -299,6 +303,7 @@ class Geometry(blenderbim.core.tool.Geometry):
|
||||
|
||||
@classmethod
|
||||
def get_active_representation(cls, obj):
|
||||
"""< IfcShapeRepresentation or None"""
|
||||
if obj.data and hasattr(obj.data, "BIMMeshProperties") and obj.data.BIMMeshProperties.ifc_definition_id:
|
||||
return tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
|
||||
|
||||
@@ -441,7 +446,7 @@ class Geometry(blenderbim.core.tool.Geometry):
|
||||
return styles
|
||||
|
||||
usage_count = [0] * len(obj.material_slots)
|
||||
if not usage_count: # if there are no materials, polygons will still use index 0
|
||||
if not usage_count: # if there are no materials, polygons will still use index 0
|
||||
return []
|
||||
for poly in obj.data.polygons:
|
||||
usage_count[poly.material_index] += 1
|
||||
@@ -716,3 +721,209 @@ class Geometry(blenderbim.core.tool.Geometry):
|
||||
obj.matrix_world.translation += original_min_point - new_max_point
|
||||
|
||||
bpy.context.view_layer.update()
|
||||
|
||||
@classmethod
|
||||
def reload_representation(cls, obj):
|
||||
"""reload `obj` active representation"""
|
||||
if not obj.data:
|
||||
return
|
||||
representation = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
|
||||
blenderbim.core.geometry.switch_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
apply_openings=True,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def remove_representation_item(cls, representation_item):
|
||||
# NOTE: we assume it's not the last representation item
|
||||
# otherwise we probably would need to remove representation too
|
||||
# NOTE: a lot of shared code with `geometry.remove_representation`
|
||||
ifc_file = tool.Ifc.get()
|
||||
shape_aspects = []
|
||||
|
||||
consider_inverses = []
|
||||
styled_item, colour, texture, layer = None, None, None, None
|
||||
[consider_inverses.append(styled_item := t) for t in representation_item.StyledByItem]
|
||||
# IFC2X3 is using LayerAssignments
|
||||
for t in (
|
||||
representation_item.LayerAssignment
|
||||
if hasattr(representation_item, "LayerAssignment")
|
||||
else representation_item.LayerAssignments
|
||||
):
|
||||
consider_inverses.append(layer := t)
|
||||
# IfcTessellatedFaceSet
|
||||
[consider_inverses.append(colour := t) for t in getattr(representation_item, "HasColours", [])]
|
||||
[consider_inverses.append(texture := t) for t in getattr(representation_item, "HasTextures", [])]
|
||||
|
||||
for inverse in ifc_file.get_inverse(representation_item):
|
||||
if inverse.is_a("IfcShapeRepresentation"):
|
||||
if inverse.OfShapeAspect:
|
||||
shape_aspects.append(inverse.OfShapeAspect[0])
|
||||
else:
|
||||
representation = inverse
|
||||
|
||||
if styled_item:
|
||||
ifc_file.remove(styled_item)
|
||||
if layer and len(layer.Items) == 1:
|
||||
ifc_file.remove(styled_item)
|
||||
if colour:
|
||||
ifcopenshell.util.element.remove_deep2(ifc_file, colour)
|
||||
if texture:
|
||||
ifcopenshell.util.element.remove_deep2(ifc_file, texture)
|
||||
|
||||
for shape_aspect in shape_aspects:
|
||||
cls.remove_representation_items_from_shape_aspect([representation_item], shape_aspect)
|
||||
|
||||
representation.Items = tuple(set(representation.Items) - {representation_item})
|
||||
also_consider = list(consider_inverses)
|
||||
ifcopenshell.util.element.remove_deep2(ifc_file, representation_item, also_consider=also_consider)
|
||||
|
||||
@classmethod
|
||||
def create_shape_aspect(cls, product_shape, base_representation, items, previous_shape_aspect=None):
|
||||
"""
|
||||
> `product_shape` - IfcProductDefinitionShape or IfcRepresentationMap\n
|
||||
> `base_representation` - base representation to get context attributes from\n
|
||||
> `items` - representation items\n
|
||||
> `previous_shape_aspect` - (optional) previous shape aspect, if provided\n
|
||||
items will be removed the previous shape aspect first\n
|
||||
|
||||
< IfcShapeAspect
|
||||
"""
|
||||
|
||||
if previous_shape_aspect is not None:
|
||||
cls.remove_representation_items_from_shape_aspect(items, previous_shape_aspect)
|
||||
|
||||
shape_aspect = tool.Ifc.get().createIfcShapeAspect(
|
||||
PartOfProductDefinitionShape=product_shape, ShapeRepresentations=()
|
||||
)
|
||||
# keep IfcShapeAspect and IfcShapeRepresentation valid
|
||||
rep = tool.Geometry.add_shape_aspect_representation(shape_aspect, base_representation)
|
||||
rep.Items = items
|
||||
|
||||
return shape_aspect
|
||||
|
||||
@classmethod
|
||||
def remove_representation_items_from_shape_aspect(cls, representation_items, shape_aspect):
|
||||
ifc_file = tool.Ifc.get()
|
||||
# as shape aspect might have multiple representations
|
||||
# it's easier to find it from the item
|
||||
for inverse in ifc_file.get_inverse(representation_items[0]):
|
||||
if inverse.is_a("IfcShapeRepresentation") and shape_aspect in inverse.OfShapeAspect:
|
||||
representation = inverse
|
||||
break
|
||||
|
||||
# removing last item would make representation invalid
|
||||
if len(representation.Items) == len(representation_items):
|
||||
# removing last representation would make shape aspect invalid.
|
||||
# remove shape aspect first otherwise remove_representation won't remove it because of the inverse
|
||||
if len(shape_aspect.ShapeRepresentations) == 1:
|
||||
ifc_file.remove(shape_aspect)
|
||||
tool.Ifc.run("geometry.remove_representation", representation=representation)
|
||||
else:
|
||||
items = set(representation.Items) - set(representation_items)
|
||||
representation.Items = tuple(items)
|
||||
|
||||
@classmethod
|
||||
def add_representation_item_to_shape_aspect(cls, representation_items, shape_aspect):
|
||||
"""NOTE: we assume that all items belonged to the same representation and to the same shape aspect"""
|
||||
ifc_file = tool.Ifc.get()
|
||||
previous_shape_aspect = None
|
||||
for inverse in ifc_file.get_inverse(representation_items[0]):
|
||||
if inverse.is_a("IfcShapeRepresentation"):
|
||||
if inverse.OfShapeAspect:
|
||||
# item is already added to the shape aspect
|
||||
if inverse.OfShapeAspect[0] == shape_aspect:
|
||||
return
|
||||
previous_shape_aspect = inverse.OfShapeAspect[0]
|
||||
else:
|
||||
base_representation = inverse
|
||||
|
||||
# remove item from previous shape aspect
|
||||
if previous_shape_aspect:
|
||||
cls.remove_representation_items_from_shape_aspect(representation_items, previous_shape_aspect)
|
||||
shape_aspect_representation = cls.get_shape_aspect_representation(
|
||||
shape_aspect, base_representation, create_new=True
|
||||
)
|
||||
shape_aspect_representation.Items = shape_aspect_representation.Items + tuple(representation_items)
|
||||
|
||||
@classmethod
|
||||
def get_shape_aspect_representation(cls, shape_aspect, base_representation, create_new=False):
|
||||
for representation in shape_aspect.ShapeRepresentations:
|
||||
if (
|
||||
representation.ContextOfItems == base_representation.ContextOfItems
|
||||
and representation.RepresentationIdentifier == base_representation.RepresentationIdentifier
|
||||
and representation.RepresentationType == base_representation.RepresentationType
|
||||
):
|
||||
return representation
|
||||
|
||||
if not create_new:
|
||||
return None
|
||||
|
||||
return cls.add_shape_aspect_representation(shape_aspect, base_representation)
|
||||
|
||||
@classmethod
|
||||
def add_shape_aspect_representation(cls, shape_aspect, base_representation):
|
||||
shape_aspect_representation = tool.Ifc.get().createIfcShapeRepresentation(
|
||||
ContextOfItems=base_representation.ContextOfItems,
|
||||
RepresentationIdentifier=base_representation.RepresentationIdentifier,
|
||||
RepresentationType=base_representation.RepresentationType,
|
||||
)
|
||||
shape_aspect.ShapeRepresentations = shape_aspect.ShapeRepresentations + (shape_aspect_representation,)
|
||||
return shape_aspect_representation
|
||||
|
||||
@classmethod
|
||||
def get_shape_aspect_representation_for_item(cls, shape_aspect, representation_item):
|
||||
ifc_file = tool.Ifc.get()
|
||||
for inverse in ifc_file.get_inverse(representation_item):
|
||||
if inverse.is_a("IfcShapeRepresentation"):
|
||||
if inverse.OfShapeAspect:
|
||||
if inverse.OfShapeAspect[0] == shape_aspect:
|
||||
return inverse
|
||||
|
||||
@classmethod
|
||||
def get_shape_aspect_styles(cls, element, shape_aspect, representation_item) -> List[ifcopenshell.entity_instance]:
|
||||
"""update `representation_item` style based on styles connected to the `shape_aspect`
|
||||
through material constituents with the same name
|
||||
"""
|
||||
if not shape_aspect.Name:
|
||||
return []
|
||||
|
||||
# get material connected to the shape aspect with material constituent name
|
||||
material = ifcopenshell.util.element.get_material(element, should_skip_usage=True)
|
||||
if not material or not material.is_a("IfcMaterialConstituentSet") or not material.MaterialConstituents:
|
||||
return []
|
||||
|
||||
matching_constituent = next((c for c in material.MaterialConstituents if c.Name == shape_aspect.Name), None)
|
||||
if matching_constituent is None:
|
||||
return []
|
||||
|
||||
constituent_material = matching_constituent.Material
|
||||
if not constituent_material.HasRepresentation:
|
||||
return []
|
||||
|
||||
# get shape aspect representation for item
|
||||
shape_aspect_representation = cls.get_shape_aspect_representation_for_item(shape_aspect, representation_item)
|
||||
|
||||
# get the styles for this context
|
||||
material_representation = None
|
||||
for r in constituent_material.HasRepresentation[0].Representations:
|
||||
if r.ContextOfItems == shape_aspect_representation.ContextOfItems:
|
||||
material_representation = r
|
||||
break
|
||||
|
||||
if material_representation is None:
|
||||
return []
|
||||
|
||||
styles = [s for s in tool.Ifc.get().traverse(material_representation) if s.is_a("IfcPresentationStyle")]
|
||||
return styles
|
||||
|
||||
@classmethod
|
||||
def delete_opening_object_placement(cls, placement):
|
||||
model = tool.Ifc.get()
|
||||
ifcopenshell.util.element.remove_deep2(model, placement)
|
||||
|
||||
@@ -20,45 +20,47 @@ import os
|
||||
import bpy
|
||||
import numpy as np
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.element
|
||||
import blenderbim.core.tool
|
||||
import blenderbim.bim.handler
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.ifc import IfcStore, IFC_CONNECTED_TYPE
|
||||
from typing import Optional, Union, Any
|
||||
|
||||
|
||||
class Ifc(blenderbim.core.tool.Ifc):
|
||||
@classmethod
|
||||
def run(cls, command, **kwargs):
|
||||
def run(cls, command: str, **kwargs) -> Any:
|
||||
return ifcopenshell.api.run(command, IfcStore.get_file(), **kwargs)
|
||||
|
||||
@classmethod
|
||||
def set(cls, ifc):
|
||||
def set(cls, ifc: ifcopenshell.file) -> None:
|
||||
IfcStore.file = ifc
|
||||
|
||||
@classmethod
|
||||
def get(cls):
|
||||
def get(cls) -> ifcopenshell.file:
|
||||
return IfcStore.get_file()
|
||||
|
||||
@classmethod
|
||||
def get_path(cls):
|
||||
def get_path(cls) -> str:
|
||||
return IfcStore.path
|
||||
|
||||
@classmethod
|
||||
def get_schema(cls):
|
||||
def get_schema(cls) -> str:
|
||||
if IfcStore.get_file():
|
||||
return IfcStore.get_file().schema
|
||||
|
||||
@classmethod
|
||||
def has_changed_shading(cls, obj):
|
||||
def has_changed_shading(cls, obj: bpy.types.Material) -> bool:
|
||||
checksum = obj.BIMMaterialProperties.shading_checksum
|
||||
return checksum != repr(np.array(obj.diffuse_color).tobytes())
|
||||
|
||||
@classmethod
|
||||
def is_edited(cls, obj):
|
||||
def is_edited(cls, obj: bpy.types.Object) -> bool:
|
||||
return list(obj.scale) != [1.0, 1.0, 1.0] or obj in IfcStore.edited_objs
|
||||
|
||||
@classmethod
|
||||
def is_moved(cls, obj):
|
||||
def is_moved(cls, obj: bpy.types.Object) -> bool:
|
||||
element = cls.get_entity(obj)
|
||||
if not element or element.is_a("IfcTypeProduct") or element.is_a("IfcProject"):
|
||||
return False
|
||||
@@ -77,7 +79,7 @@ class Ifc(blenderbim.core.tool.Ifc):
|
||||
return IfcStore.get_schema()
|
||||
|
||||
@classmethod
|
||||
def get_entity(cls, obj):
|
||||
def get_entity(cls, obj: bpy.types.Object) -> ifcopenshell.entity_instance:
|
||||
ifc = IfcStore.get_file()
|
||||
props = getattr(obj, "BIMObjectProperties", None)
|
||||
if ifc and props and props.ifc_definition_id:
|
||||
@@ -87,7 +89,7 @@ class Ifc(blenderbim.core.tool.Ifc):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def get_entity_by_id(cls, entity_id):
|
||||
def get_entity_by_id(cls, entity_id) -> Union[ifcopenshell.entity_instance, None]:
|
||||
"""useful to check whether entity_id is still exists in IFC"""
|
||||
ifc_file = tool.Ifc.get()
|
||||
try:
|
||||
@@ -96,11 +98,11 @@ class Ifc(blenderbim.core.tool.Ifc):
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def get_object(cls, element):
|
||||
def get_object(cls, element: ifcopenshell.entity_instance) -> IFC_CONNECTED_TYPE:
|
||||
return IfcStore.get_element(element.id())
|
||||
|
||||
@classmethod
|
||||
def rebuild_element_maps(cls):
|
||||
def rebuild_element_maps(cls) -> None:
|
||||
"""Rebuilds the id_map and guid_map
|
||||
|
||||
When any Blender object is stored outside a Blender PointerProperty,
|
||||
@@ -153,15 +155,15 @@ class Ifc(blenderbim.core.tool.Ifc):
|
||||
blenderbim.bim.handler.subscribe_to(obj, "diffuse_color", blenderbim.bim.handler.color_callback)
|
||||
|
||||
@classmethod
|
||||
def link(cls, element, obj):
|
||||
def link(cls, element: ifcopenshell.entity_instance, obj: IFC_CONNECTED_TYPE) -> None:
|
||||
IfcStore.link_element(element, obj)
|
||||
|
||||
@classmethod
|
||||
def edit(cls, obj):
|
||||
def edit(cls, obj: bpy.types.Object) -> None:
|
||||
IfcStore.edited_objs.add(obj)
|
||||
|
||||
@classmethod
|
||||
def finish_edit(cls, obj):
|
||||
def finish_edit(cls, obj: bpy.types.Object) -> None:
|
||||
try:
|
||||
IfcStore.edited_objs.remove(obj)
|
||||
except:
|
||||
@@ -186,11 +188,13 @@ class Ifc(blenderbim.core.tool.Ifc):
|
||||
return os.path.relpath(uri, ifc_path).replace("\\", "/")
|
||||
|
||||
@classmethod
|
||||
def unlink(cls, element=None, obj=None):
|
||||
def unlink(
|
||||
cls, element: Optional[ifcopenshell.entity_instance] = None, obj: Optional[IFC_CONNECTED_TYPE] = None
|
||||
) -> None:
|
||||
IfcStore.unlink_element(element, obj)
|
||||
|
||||
@classmethod
|
||||
def get_all_element_occurrences(cls, element):
|
||||
def get_all_element_occurrences(cls, element: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
|
||||
if element.is_a("IfcElementType"):
|
||||
element_type = element
|
||||
occurrences = ifcopenshell.util.element.get_types(element_type)
|
||||
|
||||
@@ -415,24 +415,25 @@ 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.set_value(section, "cmd", "ifcmerge $BASE $LOCAL $REMOTE $MERGED")
|
||||
config_writer.set_value(section, "trustExitCode", True)
|
||||
with IfcGitRepo.repo.config_writer() as 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)
|
||||
with IfcGitRepo.repo.config_writer() as config_writer:
|
||||
config_writer.set_value(section, "cmd", "ifcmerge $BASE $REMOTE $LOCAL $MERGED")
|
||||
config_writer.set_value(section, "trustExitCode", True)
|
||||
|
||||
@classmethod
|
||||
def config_push(cls, repo):
|
||||
"""Set push.autoSetupRemote"""
|
||||
config_reader = repo.config_reader()
|
||||
config_writer = repo.config_writer()
|
||||
if not config_reader.has_section("push"):
|
||||
config_writer.set_value("push", "default", "current")
|
||||
config_writer.set_value("push", "autoSetupRemote", True)
|
||||
with repo.config_writer() as config_writer:
|
||||
config_writer.set_value("push", "default", "current")
|
||||
config_writer.set_value("push", "autoSetupRemote", True)
|
||||
|
||||
@classmethod
|
||||
def config_info_attributes(cls, repo):
|
||||
|
||||
@@ -26,6 +26,7 @@ import os
|
||||
import numpy as np
|
||||
from mathutils import Vector
|
||||
from pathlib import Path
|
||||
from typing import Union
|
||||
|
||||
|
||||
# Progressively we'll refactor loading elements into Blender objects into this
|
||||
@@ -34,9 +35,12 @@ from pathlib import Path
|
||||
# supplementary objects (e.g. drawings, structural analysis models, etc).
|
||||
|
||||
|
||||
OBJECT_DATA_TYPE = Union[bpy.types.Mesh, bpy.types.Curve]
|
||||
|
||||
|
||||
class Loader(blenderbim.core.tool.Loader):
|
||||
@classmethod
|
||||
def create_project_collection(self, name):
|
||||
def create_project_collection(cls, name: str) -> bpy.types.Collection:
|
||||
project_obj = tool.Ifc.get_object(tool.Ifc.get().by_type("IfcProject")[0])
|
||||
project_collection = project_obj.BIMObjectProperties.collection
|
||||
for collection in project_collection.children:
|
||||
@@ -51,7 +55,7 @@ class Loader(blenderbim.core.tool.Loader):
|
||||
return collection
|
||||
|
||||
@classmethod
|
||||
def get_mesh_name(cls, geometry):
|
||||
def get_mesh_name(cls, geometry) -> str:
|
||||
representation_id = geometry.id
|
||||
if "-" in representation_id:
|
||||
representation_id = int(re.sub(r"\D", "", representation_id.split("-")[0]))
|
||||
@@ -62,11 +66,11 @@ class Loader(blenderbim.core.tool.Loader):
|
||||
return "{}/{}".format(context_id, representation_id)
|
||||
|
||||
@classmethod
|
||||
def get_name(cls, element):
|
||||
def get_name(cls, element: ifcopenshell.entity_instance) -> str:
|
||||
return "{}/{}".format(element.is_a(), getattr(element, "Name", "None"))
|
||||
|
||||
@classmethod
|
||||
def link_mesh(cls, shape, mesh):
|
||||
def link_mesh(cls, shape, mesh: OBJECT_DATA_TYPE) -> None:
|
||||
geometry = shape.geometry if hasattr(shape, "geometry") else shape
|
||||
if "-" in geometry.id:
|
||||
mesh.BIMMeshProperties.ifc_definition_id = int(geometry.id.split("-")[0])
|
||||
|
||||
@@ -223,3 +223,16 @@ class Material(blenderbim.core.tool.Material):
|
||||
assinged_material = cls.get_material(element)
|
||||
material_profile = tool.Ifc.run("material.add_profile", profile_set=assinged_material, material=material)
|
||||
return material_profile
|
||||
|
||||
@classmethod
|
||||
def update_elements_using_material(cls, material):
|
||||
# update elements that are using this material
|
||||
elements = ifcopenshell.util.element.get_elements_by_material(tool.Ifc.get(), material)
|
||||
# filter only unique representations to avoid reloading same representations multiple times
|
||||
meshes_to_objects = dict()
|
||||
for e in elements:
|
||||
obj = tool.Ifc.get_object(e)
|
||||
mesh = obj.data
|
||||
meshes_to_objects[mesh] = obj
|
||||
for obj in meshes_to_objects.values():
|
||||
tool.Geometry.reload_representation(obj)
|
||||
|
||||
@@ -202,7 +202,7 @@ class Model(blenderbim.core.tool.Model):
|
||||
return surface
|
||||
|
||||
@classmethod
|
||||
def generate_occurrence_name(cls, element_type, ifc_class):
|
||||
def generate_occurrence_name(cls, element_type: ifcopenshell.entity_instance, ifc_class: str):
|
||||
props = bpy.context.scene.BIMModelProperties
|
||||
if props.occurrence_name_style == "CLASS":
|
||||
return ifc_class[3:]
|
||||
@@ -461,12 +461,14 @@ class Model(blenderbim.core.tool.Model):
|
||||
@classmethod
|
||||
def get_material_layer_parameters(cls, element):
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
layer_set_direction = "AXIS2"
|
||||
offset = 0.0
|
||||
thickness = 0.0
|
||||
direction_sense = "POSITIVE"
|
||||
material = ifcopenshell.util.element.get_material(element)
|
||||
if material:
|
||||
if material.is_a("IfcMaterialLayerSetUsage"):
|
||||
layer_set_direction = material.LayerSetDirection
|
||||
offset = material.OffsetFromReferenceLine * unit_scale
|
||||
direction_sense = material.DirectionSense
|
||||
material = material.ForLayerSet
|
||||
@@ -475,10 +477,15 @@ class Model(blenderbim.core.tool.Model):
|
||||
if direction_sense == "NEGATIVE":
|
||||
thickness *= -1
|
||||
offset *= -1
|
||||
return {"thickness": thickness, "offset": offset, "direction_sense": direction_sense}
|
||||
return {
|
||||
"layer_set_direction": layer_set_direction,
|
||||
"thickness": thickness,
|
||||
"offset": offset,
|
||||
"direction_sense": direction_sense,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def get_booleans(cls, element=None, representation=None):
|
||||
def get_booleans(cls, element=None, representation=None) -> list[ifcopenshell.entity_instance]:
|
||||
if representation is None:
|
||||
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||
if not representation:
|
||||
@@ -493,7 +500,7 @@ class Model(blenderbim.core.tool.Model):
|
||||
return booleans
|
||||
|
||||
@classmethod
|
||||
def get_manual_booleans(cls, element, representation=None):
|
||||
def get_manual_booleans(cls, element, representation=None) -> list[ifcopenshell.entity_instance]:
|
||||
pset = ifcopenshell.util.element.get_pset(element, "BBIM_Boolean")
|
||||
if not pset:
|
||||
return []
|
||||
@@ -502,7 +509,7 @@ class Model(blenderbim.core.tool.Model):
|
||||
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||
if not representation:
|
||||
return []
|
||||
booleans = [b for b in cls.get_booleans(element) if b.id() in boolean_ids]
|
||||
booleans = [b for b in cls.get_booleans(element, representation) if b.id() in boolean_ids]
|
||||
return booleans
|
||||
|
||||
@classmethod
|
||||
@@ -521,11 +528,12 @@ class Model(blenderbim.core.tool.Model):
|
||||
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"Data": data})
|
||||
|
||||
@classmethod
|
||||
def unmark_manual_booleans(cls, element, booleans):
|
||||
def unmark_manual_booleans(cls, element, boolean_ids):
|
||||
# NOTE: we use use boolean_ids instead of boolean entities
|
||||
# so it will be possible to unmark manual booleans after they already was deleted
|
||||
pset = ifcopenshell.util.element.get_pset(element, "BBIM_Boolean")
|
||||
if not pset:
|
||||
return
|
||||
boolean_ids = [b.id() for b in booleans]
|
||||
data = set(json.loads(pset["Data"]))
|
||||
data -= set(boolean_ids)
|
||||
data = list(data)
|
||||
@@ -978,8 +986,8 @@ class Model(blenderbim.core.tool.Model):
|
||||
if nosing_length < 0: # tread gaps
|
||||
length += abs(nosing_length) * number_of_treads
|
||||
calculated_params["Length"] = round(length, 5)
|
||||
pitch = height/length
|
||||
pitch_formatted = str(round(pitch*100, 1)) + ' % / ' + str(round(degrees(atan(pitch)), 1)) + ' deg'
|
||||
pitch = height / length
|
||||
pitch_formatted = str(round(pitch * 100, 1)) + " % / " + str(round(degrees(atan(pitch)), 1)) + " deg"
|
||||
calculated_params["Pitch"] = str(pitch_formatted)
|
||||
|
||||
return calculated_params
|
||||
|
||||
@@ -31,7 +31,7 @@ class Project(blenderbim.core.tool.Project):
|
||||
@classmethod
|
||||
def append_all_types_from_template(cls, template):
|
||||
# TODO refactor
|
||||
filepath = os.path.join(bpy.context.scene.BIMProperties.data_dir, "libraries", template)
|
||||
filepath = os.path.join(bpy.context.scene.BIMProperties.data_dir, "templates", "projects", template)
|
||||
bpy.ops.bim.select_library_file(filepath=filepath)
|
||||
if IfcStore.library_file.schema != tool.Ifc.get().schema:
|
||||
return
|
||||
|
||||
@@ -48,7 +48,8 @@ class Pset(blenderbim.core.tool.Pset):
|
||||
return bpy.context.scene.ProfilePsetProperties
|
||||
elif obj_type == "WorkSchedule":
|
||||
return bpy.context.scene.WorkSchedulePsetProperties
|
||||
|
||||
elif obj_type == "Group":
|
||||
return bpy.context.scene.GroupPsetProperties
|
||||
@classmethod
|
||||
def get_pset_name(cls, obj, obj_type):
|
||||
pset = cls.get_pset_props(obj, obj_type)
|
||||
|
||||
@@ -181,7 +181,7 @@ class Qto(blenderbim.core.tool.Qto):
|
||||
"quantity_value" : XX,
|
||||
"quantity_type" : XX
|
||||
}]
|
||||
:rtype list
|
||||
:rtype: list
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
@@ -220,8 +220,11 @@ class Root(blenderbim.core.tool.Root):
|
||||
@classmethod
|
||||
def recreate_connections(cls, relationship, old_to_new):
|
||||
for element, data in relationship.items():
|
||||
new_relating_element = old_to_new.get(data["relating_element"])[0]
|
||||
new_related_element = old_to_new.get(data["related_element"])[0]
|
||||
try:
|
||||
new_relating_element = old_to_new.get(data["relating_element"])[0]
|
||||
new_related_element = old_to_new.get(data["related_element"])[0]
|
||||
except:
|
||||
continue
|
||||
ifcopenshell.api.run(
|
||||
"geometry.connect_path",
|
||||
tool.Ifc.get(),
|
||||
|
||||
@@ -5,15 +5,17 @@ import blenderbim.core.tool
|
||||
import blenderbim.tool as tool
|
||||
import ifcopenshell.util.selector
|
||||
from ifcopenshell.util.selector import Selector
|
||||
from blenderbim.bim.prop import BIMFacet
|
||||
from typing import Union, Literal
|
||||
|
||||
|
||||
class Search(blenderbim.core.tool.Search):
|
||||
@classmethod
|
||||
def get_group_query(cls, group):
|
||||
def get_group_query(cls, group: ifcopenshell.entity_instance) -> str:
|
||||
return json.loads(group.Description)["query"]
|
||||
|
||||
@classmethod
|
||||
def get_filter_groups(cls, module):
|
||||
def get_filter_groups(cls, module: str) -> bpy.types.bpy_prop_collection:
|
||||
if module == "search":
|
||||
return bpy.context.scene.BIMSearchProperties.filter_groups
|
||||
elif module == "csv":
|
||||
@@ -24,13 +26,13 @@ class Search(blenderbim.core.tool.Search):
|
||||
return bpy.context.active_object.data.BIMCameraProperties.exclude_filter_groups
|
||||
|
||||
@classmethod
|
||||
def import_filter_query(cls, query, filter_groups):
|
||||
def import_filter_query(cls, query: str, filter_groups: bpy.types.bpy_prop_collection) -> None:
|
||||
filter_groups.clear()
|
||||
transformer = ImportFilterQueryTransformer(filter_groups)
|
||||
transformer.transform(ifcopenshell.util.selector.filter_elements_grammar.parse(query))
|
||||
|
||||
@classmethod
|
||||
def export_filter_query(cls, filter_groups):
|
||||
def export_filter_query(cls, filter_groups: bpy.types.bpy_prop_collection) -> str:
|
||||
query = []
|
||||
for filter_group in filter_groups:
|
||||
filter_group_query = []
|
||||
@@ -87,13 +89,15 @@ class Search(blenderbim.core.tool.Search):
|
||||
return " + ".join(query)
|
||||
|
||||
@classmethod
|
||||
def get_comparison_and_value(cls, ifc_filter):
|
||||
def get_comparison_and_value(
|
||||
cls, ifc_filter: BIMFacet
|
||||
) -> Union[tuple[Literal["!="], str], tuple[Literal["="], str]]:
|
||||
if ifc_filter.value.startswith("!="):
|
||||
return ("!=", cls.wrap_value(ifc_filter, ifc_filter.value[2:].strip()))
|
||||
return ("=", cls.wrap_value(ifc_filter, ifc_filter.value.strip()))
|
||||
|
||||
@classmethod
|
||||
def wrap_value(cls, ifc_filter, value):
|
||||
def wrap_value(cls, ifc_filter: BIMFacet, value: str) -> str:
|
||||
if value.startswith("/") and value.endswith("/"):
|
||||
return value
|
||||
elif value in ("NULL", "TRUE", "FALSE"):
|
||||
@@ -101,7 +105,7 @@ class Search(blenderbim.core.tool.Search):
|
||||
return '"' + value.replace('"', '\\"') + '"'
|
||||
|
||||
@classmethod
|
||||
def from_selector_query(cls, query):
|
||||
def from_selector_query(cls, query: str) -> list[ifcopenshell.entity_instance]:
|
||||
"""Returns a list of products from a selector query"""
|
||||
return Selector().parse(tool.Ifc.get(), query)
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user