mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-28 07:49:59 +00:00
Compare commits
171 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 | |||
| 4463dd8af9 | |||
| 95c0ac901f |
@@ -76,6 +76,9 @@ 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/drawings
|
||||
|
||||
+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")
|
||||
|
||||
+1
-1
@@ -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.7.13", "3.8.13", "3.9.11", "3.10.3", "3.11.7", "3.12.1"]
|
||||
JSON_VERSION = "v3.6.1"
|
||||
OCE_VERSION = "0.18.3"
|
||||
OCCT_VERSION = "7.5.3"
|
||||
|
||||
+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-eaa2aa0-$(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():
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
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()
|
||||
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 = ""
|
||||
pset_template_file = None
|
||||
pset_template_file: ifcopenshell.file = None
|
||||
classification_path = ""
|
||||
classification_file = None
|
||||
classification_file: ifcopenshell.file = None
|
||||
library_path = ""
|
||||
library_file = None
|
||||
library_file: ifcopenshell.file = None
|
||||
current_transaction = ""
|
||||
last_transaction = ""
|
||||
history = []
|
||||
future = []
|
||||
schema_identifiers = ["IFC4", "IFC2X3", "IFC4X3"]
|
||||
session_files = {}
|
||||
session_files: dict[str, ifcopenshell.file] = {}
|
||||
|
||||
@staticmethod
|
||||
def purge():
|
||||
@@ -117,7 +123,7 @@ class IfcStore:
|
||||
IfcStore.get_cache()
|
||||
|
||||
@staticmethod
|
||||
def load_file(path):
|
||||
def load_file(path) -> None:
|
||||
if not os.path.isfile(path):
|
||||
return
|
||||
extension = path.split(".")[-1]
|
||||
@@ -146,7 +152,7 @@ class IfcStore:
|
||||
return IfcStore.schema
|
||||
|
||||
@staticmethod
|
||||
def get_element(id_or_guid):
|
||||
def get_element(id_or_guid: Union[int, str]) -> IFC_CONNECTED_TYPE:
|
||||
if isinstance(id_or_guid, int):
|
||||
map_object = IfcStore.id_map
|
||||
else:
|
||||
@@ -159,7 +165,7 @@ class IfcStore:
|
||||
return obj
|
||||
|
||||
@staticmethod
|
||||
def relink_all_objects():
|
||||
def relink_all_objects() -> None:
|
||||
if not IfcStore.get_file():
|
||||
return
|
||||
for obj in bpy.data.objects:
|
||||
@@ -172,7 +178,7 @@ class IfcStore:
|
||||
IfcStore.relink_object(obj)
|
||||
|
||||
@staticmethod
|
||||
def relink_object(obj):
|
||||
def relink_object(obj: IFC_CONNECTED_TYPE) -> None:
|
||||
if not obj:
|
||||
return
|
||||
if obj.BIMObjectProperties.ifc_definition_id:
|
||||
@@ -193,7 +199,7 @@ class IfcStore:
|
||||
IfcStore.commit_link_element(data)
|
||||
|
||||
@staticmethod
|
||||
def link_element(element, obj):
|
||||
def link_element(element: ifcopenshell.entity_instance, obj: IFC_CONNECTED_TYPE) -> None:
|
||||
# Please use tool.Ifc.link() instead of this method. We want to
|
||||
# refactor this class and deprecate usage of IfcStore in favour of
|
||||
# tools.
|
||||
@@ -259,7 +265,7 @@ class IfcStore:
|
||||
# TODO We're handling id_map and guid_map, but what about edited_objs? This might cause big problems.
|
||||
|
||||
@staticmethod
|
||||
def rollback_unlink_element(data):
|
||||
def rollback_unlink_element(data) -> None:
|
||||
if "id" not in data or "obj" not in data:
|
||||
return
|
||||
obj = bpy.data.objects.get(data["obj"])
|
||||
@@ -268,13 +274,13 @@ 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: ifcopenshell.entity_instance = None, obj: IFC_CONNECTED_TYPE = None) -> None:
|
||||
if element is None:
|
||||
try:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
@@ -320,7 +326,7 @@ class IfcStore:
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def execute_ifc_operator(operator, context, is_invoke=False):
|
||||
def execute_ifc_operator(operator: bpy.types.Operator, context: bpy.types.Context, is_invoke=False):
|
||||
bpy.context.scene.BIMProperties.is_dirty = True
|
||||
is_top_level_operator = not bool(IfcStore.current_transaction)
|
||||
|
||||
@@ -354,17 +360,17 @@ class IfcStore:
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def begin_transaction(operator):
|
||||
def begin_transaction(operator: bpy.types.Operator) -> None:
|
||||
IfcStore.current_transaction = str(uuid.uuid4())
|
||||
operator.transaction_key = IfcStore.current_transaction
|
||||
|
||||
@staticmethod
|
||||
def end_transaction(operator):
|
||||
def end_transaction(operator: bpy.types.Operator) -> None:
|
||||
IfcStore.current_transaction = ""
|
||||
operator.transaction_key = ""
|
||||
|
||||
@staticmethod
|
||||
def add_transaction_operation(operator, rollback=None, commit=None):
|
||||
def add_transaction_operation(operator: bpy.types.Operator, rollback=None, commit=None) -> None:
|
||||
key = getattr(operator, "transaction_key", None)
|
||||
data = getattr(operator, "transaction_data", None)
|
||||
bpy.context.scene.BIMProperties.last_transaction = key
|
||||
@@ -380,7 +386,7 @@ class IfcStore:
|
||||
IfcStore.future = []
|
||||
|
||||
@staticmethod
|
||||
def undo(until_key=None):
|
||||
def undo(until_key=None) -> None:
|
||||
BrickStore.undo()
|
||||
if not IfcStore.history:
|
||||
return
|
||||
@@ -395,7 +401,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:
|
||||
|
||||
@@ -36,14 +36,15 @@ import blenderbim.tool as tool
|
||||
from itertools import chain, accumulate
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.drawing.prop import ANNOTATION_TYPES_DATA
|
||||
from typing import Dict
|
||||
|
||||
|
||||
class MaterialCreator:
|
||||
def __init__(self, ifc_import_settings: IfcImportSettings, ifc_importer: IfcImporter):
|
||||
self.mesh = None
|
||||
self.obj = None
|
||||
self.materials = {}
|
||||
self.styles = {}
|
||||
self.materials: Dict[int, bpy.types.Material] = {}
|
||||
self.styles: Dict[int, bpy.types.Material] = {}
|
||||
self.parsed_meshes = set()
|
||||
self.ifc_import_settings = ifc_import_settings
|
||||
self.ifc_importer = ifc_importer
|
||||
@@ -156,8 +157,8 @@ 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_id in enumerate(self.mesh["ios_materials"]):
|
||||
slot_index = self.mesh.materials.find(self.styles[style_id].name)
|
||||
material_to_slot[i] = slot_index
|
||||
|
||||
material_index = [
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -89,6 +89,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 +236,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)
|
||||
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -812,8 +812,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")
|
||||
|
||||
|
||||
@@ -579,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
|
||||
@@ -954,7 +953,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 = "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))
|
||||
@@ -1018,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]:
|
||||
@@ -2283,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"}
|
||||
|
||||
@@ -32,6 +32,7 @@ import ifcopenshell.util.representation
|
||||
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
|
||||
@@ -1066,7 +1067,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
|
||||
|
||||
@@ -96,13 +96,15 @@ class RepresentationsData:
|
||||
@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 tool.Geometry.get_shape_aspects(element):
|
||||
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)
|
||||
|
||||
@@ -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:
|
||||
@@ -840,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
|
||||
|
||||
@@ -892,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"
|
||||
@@ -950,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
|
||||
@@ -974,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"}
|
||||
|
||||
@@ -1011,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"):
|
||||
@@ -1020,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"
|
||||
@@ -1044,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)
|
||||
@@ -1056,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
|
||||
@@ -1140,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:
|
||||
@@ -1155,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
|
||||
@@ -1200,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:
|
||||
@@ -1224,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(
|
||||
@@ -1598,6 +1730,11 @@ class EnableEditingRepresentationItems(bpy.types.Operator, Operator):
|
||||
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):
|
||||
bl_idname = "bim.disable_editing_representation_items"
|
||||
@@ -1634,16 +1771,48 @@ class RemoveRepresentationItem(bpy.types.Operator, Operator):
|
||||
tool.Geometry.remove_representation_item(representation_item)
|
||||
tool.Geometry.reload_representation(obj)
|
||||
|
||||
# reload style ui
|
||||
# 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
|
||||
@@ -1674,7 +1843,7 @@ class EditRepresentationItemStyle(bpy.types.Operator, Operator):
|
||||
|
||||
tool.Style.assign_style_to_representation_item(representation_item, surface_style)
|
||||
tool.Geometry.reload_representation(obj)
|
||||
# reload style ui
|
||||
# reload items ui
|
||||
bpy.ops.bim.disable_editing_representation_items()
|
||||
bpy.ops.bim.enable_editing_representation_items()
|
||||
|
||||
@@ -1694,6 +1863,10 @@ class UnassignRepresentationItemStyle(bpy.types.Operator, Operator):
|
||||
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
|
||||
@@ -1708,7 +1881,7 @@ class UnassignRepresentationItemStyle(bpy.types.Operator, Operator):
|
||||
|
||||
tool.Style.assign_style_to_representation_item(representation_item, None)
|
||||
tool.Geometry.reload_representation(obj)
|
||||
# reload style ui
|
||||
# reload items ui
|
||||
bpy.ops.bim.disable_editing_representation_items()
|
||||
bpy.ops.bim.enable_editing_representation_items()
|
||||
|
||||
@@ -1767,7 +1940,19 @@ class EditRepresentationItemShapeAspect(bpy.types.Operator, Operator):
|
||||
shape_aspect.Name = shape_aspect_attrs.name
|
||||
shape_aspect.Description = shape_aspect_attrs.description
|
||||
|
||||
# reload style ui
|
||||
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()
|
||||
|
||||
@@ -1789,6 +1974,7 @@ class RemoveRepresentationItemFromShapeAspect(bpy.types.Operator, Operator):
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
props = obj.BIMGeometryProperties
|
||||
ifc_file = tool.Ifc.get()
|
||||
|
||||
@@ -1796,7 +1982,32 @@ class RemoveRepresentationItemFromShapeAspect(bpy.types.Operator, Operator):
|
||||
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 style ui
|
||||
|
||||
# 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)
|
||||
|
||||
@@ -64,8 +64,11 @@ def get_shape_aspects(self, context):
|
||||
|
||||
|
||||
def get_material_constituents(self, context, edit_text):
|
||||
# TODO: cache in data.py
|
||||
return [m.Name for m in tool.Ifc.get().by_type("IfcMaterialConstituent") if m.Name]
|
||||
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):
|
||||
@@ -97,7 +100,7 @@ class ShapeAspect(PropertyGroup):
|
||||
"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."
|
||||
),
|
||||
search=get_material_constituents,
|
||||
**({} if bpy.app.version < (3, 3, 0) else {"search": get_material_constituents}),
|
||||
)
|
||||
description: StringProperty(
|
||||
name="Description",
|
||||
|
||||
@@ -507,7 +507,10 @@ 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="")
|
||||
|
||||
@@ -152,6 +152,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 +319,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()
|
||||
@@ -762,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)
|
||||
@@ -774,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",
|
||||
|
||||
@@ -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"}
|
||||
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
# 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,
|
||||
@@ -25,18 +25,26 @@ classes = (
|
||||
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 +56,7 @@ classes = (
|
||||
operator.UnlinkIfc,
|
||||
operator.UnloadLink,
|
||||
operator.UnloadProject,
|
||||
operator.ReloadLink,
|
||||
workspace.ExploreHotkey,
|
||||
prop.LibraryElement,
|
||||
prop.FilterCategory,
|
||||
prop.Link,
|
||||
@@ -62,6 +70,7 @@ classes = (
|
||||
ui.BIM_UL_library,
|
||||
ui.BIM_UL_filter_categories,
|
||||
ui.BIM_UL_links,
|
||||
gizmo.ClippingPlane,
|
||||
)
|
||||
|
||||
|
||||
@@ -69,7 +78,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 +100,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
|
||||
@@ -21,10 +21,14 @@ import bpy
|
||||
import time
|
||||
import logging
|
||||
import tempfile
|
||||
import subprocess
|
||||
import numpy as np
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.selector
|
||||
import ifcopenshell.util.geolocation
|
||||
import ifcopenshell.util.representation
|
||||
import ifcopenshell.util.element
|
||||
import blenderbim.bim.handler
|
||||
import blenderbim.bim.schema
|
||||
import blenderbim.tool as tool
|
||||
@@ -35,8 +39,12 @@ from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.ui import IFCFileSelector
|
||||
from blenderbim.bim import import_ifc
|
||||
from blenderbim.bim import export_ifc
|
||||
from math import radians
|
||||
from pathlib import Path
|
||||
from mathutils import Vector, Matrix
|
||||
from bpy.app.handlers import persistent
|
||||
from blenderbim.bim.module.project.data import LinksData
|
||||
from blenderbim.bim.module.project.decorator import ProjectDecorator, ClippingPlaneDecorator
|
||||
|
||||
|
||||
class NewProject(bpy.types.Operator):
|
||||
@@ -76,7 +84,7 @@ class NewProject(bpy.types.Operator):
|
||||
bpy.context.scene.unit_settings.length_unit = "MILLIMETERS"
|
||||
bpy.context.scene.BIMProperties.area_unit = "SQUARE_METRE"
|
||||
bpy.context.scene.BIMProperties.volume_unit = "CUBIC_METRE"
|
||||
bpy.context.scene.BIMProjectProperties.template_file = "IFC4 Demo Library.ifc"
|
||||
bpy.context.scene.BIMProjectProperties.template_file = "IFC4 Demo Template.ifc"
|
||||
|
||||
if self.preset != "wizard":
|
||||
bpy.ops.bim.create_project()
|
||||
@@ -178,7 +186,11 @@ class RefreshLibrary(bpy.types.Operator):
|
||||
self.props.active_library_element = ""
|
||||
|
||||
types = IfcStore.library_file.wrapped_data.types_with_super()
|
||||
for importable_type in sorted(["IfcTypeProduct", "IfcMaterial", "IfcCostSchedule", "IfcProfileDef"]):
|
||||
|
||||
element_classes = ["IfcTypeProduct", "IfcMaterial", "IfcCostSchedule", "IfcProfileDef"]
|
||||
if self.props.library_display_elements:
|
||||
element_classes += ["IfcElement"]
|
||||
for importable_type in sorted(element_classes):
|
||||
if importable_type in types:
|
||||
new = self.props.library_elements.add()
|
||||
new.name = importable_type
|
||||
@@ -197,11 +209,15 @@ class ChangeLibraryElement(bpy.types.Operator):
|
||||
self.library_file = IfcStore.library_file
|
||||
ifc_classes = set()
|
||||
self.props.active_library_element = self.element_name
|
||||
|
||||
crumb = self.props.library_breadcrumb.add()
|
||||
crumb.name = self.element_name
|
||||
|
||||
elements = self.library_file.by_type(self.element_name)
|
||||
[ifc_classes.add(e.is_a()) for e in elements]
|
||||
|
||||
self.props.library_elements.clear()
|
||||
|
||||
if len(ifc_classes) == 1 and list(ifc_classes)[0] == self.element_name:
|
||||
for name, ifc_definition_id in sorted([(self.get_name(e), e.id()) for e in elements]):
|
||||
self.add_library_asset(name, ifc_definition_id)
|
||||
@@ -423,6 +439,11 @@ class AppendLibraryElement(bpy.types.Operator):
|
||||
self.import_type_from_ifc(element, context)
|
||||
elif element.is_a("IfcProduct"):
|
||||
self.import_product_from_ifc(element, context)
|
||||
element_type = ifcopenshell.util.element.get_type(element)
|
||||
obj = tool.Ifc.get_object(element_type)
|
||||
if obj is None:
|
||||
self.import_type_from_ifc(element_type, context)
|
||||
|
||||
elif element.is_a("IfcMaterial"):
|
||||
self.import_material_from_ifc(element, context)
|
||||
try:
|
||||
@@ -811,27 +832,30 @@ class ToggleFilterCategories(bpy.types.Operator):
|
||||
|
||||
class LinkIfc(bpy.types.Operator):
|
||||
bl_idname = "bim.link_ifc"
|
||||
bl_label = "Link Blend/IFC File"
|
||||
bl_label = "Link IFC"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "This will link the Blender file that is synced with the IFC file"
|
||||
bl_description = "Reference in a read-only IFC model in the background"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
files: bpy.props.CollectionProperty(name="Files", type=bpy.types.OperatorFileListElement)
|
||||
directory: bpy.props.StringProperty(subtype="DIR_PATH")
|
||||
filter_glob: bpy.props.StringProperty(default="*.blend;*.blend1", options={"HIDDEN"})
|
||||
filter_glob: bpy.props.StringProperty(default="*.ifc", options={"HIDDEN"})
|
||||
use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=False)
|
||||
false_origin: bpy.props.StringProperty(name="False Origin", default="0,0,0")
|
||||
|
||||
def execute(self, context):
|
||||
files = [f.name for f in self.files] if self.files else [self.filepath]
|
||||
start = time.time()
|
||||
files = [f.name.replace("\\", "/") for f in self.files] if self.files else [self.filepath.replace("\\", "/")]
|
||||
for filename in files:
|
||||
filepath = os.path.join(self.directory, filename)
|
||||
filepath = os.path.join(self.directory, filename).replace("\\", "/")
|
||||
if bpy.data.filepath and Path(filepath).samefile(bpy.data.filepath):
|
||||
self.report({"INFO"}, "Can't link the current .blend file")
|
||||
continue
|
||||
new = context.scene.BIMProjectProperties.links.add()
|
||||
if self.use_relative_path:
|
||||
filepath = os.path.relpath(filepath, bpy.path.abspath("//"))
|
||||
filepath = os.path.relpath(filepath, bpy.path.abspath("//")).replace("\\", "/")
|
||||
new.name = filepath
|
||||
bpy.ops.bim.load_link(filepath=filepath)
|
||||
bpy.ops.bim.load_link(filepath=filepath, false_origin=self.false_origin)
|
||||
print(f"Finished linking {len(files)} IFCs", time.time() - start)
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
@@ -847,6 +871,7 @@ class UnlinkIfc(bpy.types.Operator):
|
||||
filepath: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
self.filepath = self.filepath.replace("\\", "/")
|
||||
bpy.ops.bim.unload_link(filepath=self.filepath)
|
||||
index = context.scene.BIMProjectProperties.links.find(self.filepath)
|
||||
if index != -1:
|
||||
@@ -862,14 +887,15 @@ class UnloadLink(bpy.types.Operator):
|
||||
filepath: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
self.filepath = self.filepath.replace("\\", "/")
|
||||
filepath = self.filepath
|
||||
if not os.path.isabs(filepath):
|
||||
filepath = os.path.abspath(os.path.join(bpy.path.abspath("//"), filepath))
|
||||
filepath = os.path.abspath(os.path.join(bpy.path.abspath("//"), filepath)).replace("\\", "/")
|
||||
for collection in context.scene.collection.children:
|
||||
if collection.library and collection.library.filepath == filepath:
|
||||
if collection.library and collection.library.filepath.replace("\\", "/") == filepath:
|
||||
context.scene.collection.children.unlink(collection)
|
||||
for scene in bpy.data.scenes:
|
||||
if scene.library and scene.library.filepath == filepath:
|
||||
if scene.library and scene.library.filepath.replace("\\", "/") == filepath:
|
||||
bpy.data.scenes.remove(scene)
|
||||
link = context.scene.BIMProjectProperties.links.get(self.filepath)
|
||||
link.is_loaded = False
|
||||
@@ -882,23 +908,59 @@ class LoadLink(bpy.types.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Load the selected file"
|
||||
filepath: bpy.props.StringProperty()
|
||||
false_origin: bpy.props.StringProperty(name="False Origin", default="0,0,0")
|
||||
|
||||
def execute(self, context):
|
||||
self.filepath = self.filepath.replace("\\", "/")
|
||||
filepath = self.filepath
|
||||
if not os.path.isabs(filepath):
|
||||
filepath = os.path.abspath(os.path.join(bpy.path.abspath("//"), filepath))
|
||||
filepath = os.path.abspath(os.path.join(bpy.path.abspath("//"), filepath)).replace("\\", "/")
|
||||
if self.filepath.lower().endswith(".blend"):
|
||||
self.link_blend(filepath)
|
||||
elif self.filepath.lower().endswith(".ifc"):
|
||||
self.link_ifc()
|
||||
return {"FINISHED"}
|
||||
|
||||
def link_blend(self, filepath):
|
||||
with bpy.data.libraries.load(filepath, link=True) as (data_from, data_to):
|
||||
data_to.scenes = data_from.scenes
|
||||
for scene in bpy.data.scenes:
|
||||
if not scene.library or scene.library.filepath != filepath:
|
||||
if not scene.library or scene.library.filepath.replace("\\", "/") != filepath:
|
||||
continue
|
||||
for child in scene.collection.children:
|
||||
if "IfcProject" not in child.name:
|
||||
continue
|
||||
bpy.data.scenes[0].collection.children.link(child)
|
||||
link = context.scene.BIMProjectProperties.links.get(self.filepath)
|
||||
link = bpy.context.scene.BIMProjectProperties.links.get(self.filepath)
|
||||
link.is_loaded = True
|
||||
return {"FINISHED"}
|
||||
|
||||
def link_ifc(self):
|
||||
blend_filepath = self.filepath + ".cache.blend"
|
||||
h5_filepath = self.filepath + ".cache.h5"
|
||||
|
||||
if not os.path.exists(blend_filepath):
|
||||
code = f"""
|
||||
import bpy
|
||||
|
||||
def run():
|
||||
bpy.ops.bim.load_linked_project(filepath="{self.filepath}", false_origin="{self.false_origin}")
|
||||
bpy.ops.wm.save_as_mainfile(filepath="{blend_filepath}")
|
||||
|
||||
try:
|
||||
run()
|
||||
except Exception as e:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
exit(1)
|
||||
"""
|
||||
|
||||
with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as temp_file:
|
||||
temp_file.write(code)
|
||||
run = subprocess.run([bpy.app.binary_path, "-b", "--python", temp_file.name, "--python-exit-code", "1"])
|
||||
if run.returncode == 1:
|
||||
print("An error occurred while processing your IFC.")
|
||||
|
||||
self.link_blend(blend_filepath)
|
||||
|
||||
|
||||
class ReloadLink(bpy.types.Operator):
|
||||
@@ -910,7 +972,7 @@ class ReloadLink(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
def get_linked_ifcs():
|
||||
selected_filename = os.path.basename(self.filepath)
|
||||
selected_filename = os.path.basename(self.filepath.replace("\\", "/"))
|
||||
return [
|
||||
c.library
|
||||
for c in bpy.data.collections
|
||||
@@ -934,7 +996,7 @@ class ToggleLinkSelectability(bpy.types.Operator):
|
||||
link = props.links.get(self.link)
|
||||
self.filepath = self.link
|
||||
if not os.path.isabs(self.filepath):
|
||||
self.filepath = os.path.abspath(os.path.join(bpy.path.abspath("//"), self.filepath))
|
||||
self.filepath = os.path.abspath(os.path.join(bpy.path.abspath("//"), self.filepath)).replace("\\", "/")
|
||||
for collection in self.get_linked_collections():
|
||||
collection.hide_select = not collection.hide_select
|
||||
link.is_selectable = not collection.hide_select
|
||||
@@ -944,7 +1006,7 @@ class ToggleLinkSelectability(bpy.types.Operator):
|
||||
return [
|
||||
c
|
||||
for c in bpy.data.collections
|
||||
if "IfcProject" in c.name and c.library and c.library.filepath == self.filepath
|
||||
if "IfcProject" in c.name and c.library and c.library.filepath.replace("\\", "/") == self.filepath
|
||||
]
|
||||
|
||||
|
||||
@@ -961,7 +1023,7 @@ class ToggleLinkVisibility(bpy.types.Operator):
|
||||
link = props.links.get(self.link)
|
||||
self.filepath = self.link
|
||||
if not os.path.isabs(self.filepath):
|
||||
self.filepath = os.path.abspath(os.path.join(bpy.path.abspath("//"), self.filepath))
|
||||
self.filepath = os.path.abspath(os.path.join(bpy.path.abspath("//"), self.filepath)).replace("\\", "/")
|
||||
if self.mode == "WIREFRAME":
|
||||
self.toggle_wireframe(link)
|
||||
elif self.mode == "VISIBLE":
|
||||
@@ -1000,7 +1062,7 @@ class ToggleLinkVisibility(bpy.types.Operator):
|
||||
return [
|
||||
c
|
||||
for c in bpy.data.collections
|
||||
if "IfcProject" in c.name and c.library and c.library.filepath == self.filepath
|
||||
if "IfcProject" in c.name and c.library and c.library.filepath.replace("\\", "/") == self.filepath
|
||||
]
|
||||
|
||||
|
||||
@@ -1130,3 +1192,747 @@ class ImportIFC(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
bpy.ops.bim.load_project("INVOKE_DEFAULT")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class LoadLinkedProject(bpy.types.Operator):
|
||||
bl_idname = "bim.load_linked_project"
|
||||
bl_label = "Load a project for viewing only."
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filepath: bpy.props.StringProperty()
|
||||
false_origin: bpy.props.StringProperty(name="False Origin", default="0,0,0")
|
||||
|
||||
def execute(self, context):
|
||||
import ifcpatch
|
||||
import multiprocessing
|
||||
import ifcopenshell.geom
|
||||
|
||||
start = time.time()
|
||||
|
||||
self.filepath = self.filepath.replace("\\", "/")
|
||||
print("Processing", self.filepath)
|
||||
|
||||
self.collection = bpy.data.collections.new("IfcProject/" + os.path.basename(self.filepath))
|
||||
self.file = ifcopenshell.open(self.filepath)
|
||||
print("Finished opening")
|
||||
|
||||
self.db_filepath = self.filepath + ".cache.sqlite"
|
||||
db = ifcpatch.execute(
|
||||
{"input": self.filepath, "file": self.file, "recipe": "ExtractPropertiesToSQLite", "arguments": []}
|
||||
)
|
||||
ifcpatch.write(db, self.db_filepath)
|
||||
print("Finished writing property database")
|
||||
|
||||
logger = logging.getLogger("ImportIFC")
|
||||
ifc_import_settings = import_ifc.IfcImportSettings.factory(context, IfcStore.path, logger)
|
||||
ifc_importer = import_ifc.IfcImporter(ifc_import_settings)
|
||||
ifc_importer.file = self.file
|
||||
ifc_importer.process_context_filter()
|
||||
|
||||
self.elements = set(self.file.by_type("IfcElement"))
|
||||
if self.file.schema in ("IFC2X3", "IFC4"):
|
||||
self.elements |= set(self.file.by_type("IfcProxy"))
|
||||
self.elements |= set(self.file.by_type("IfcSite"))
|
||||
self.elements -= set(self.file.by_type("IfcFeatureElement"))
|
||||
self.elements = list(self.elements)
|
||||
|
||||
model_origin = np.array(ifcopenshell.util.geolocation.auto_xyz2enh(self.file, 0, 0, 0))
|
||||
false_origin = np.array([float(o.strip()) for o in self.false_origin.split(",")])
|
||||
model_offset = model_origin - false_origin
|
||||
zero_origin = np.array((0, 0, 0))
|
||||
has_model_offset = not np.allclose(model_offset, zero_origin)
|
||||
|
||||
for settings in ifc_importer.context_settings:
|
||||
settings = ifcopenshell.geom.settings()
|
||||
|
||||
if has_model_offset:
|
||||
offset = ifcopenshell.ifcopenshell_wrapper.float_array_3()
|
||||
offset[0], offset[1], offset[2] = model_offset
|
||||
settings.offset = offset
|
||||
|
||||
iterator = ifcopenshell.geom.iterator(
|
||||
settings, self.file, multiprocessing.cpu_count(), include=self.elements
|
||||
)
|
||||
self.meshes = {}
|
||||
self.blender_mats = {}
|
||||
blender_mats = {}
|
||||
|
||||
default_mat = np.array([[1, 1, 1, 1]], dtype=np.float32)
|
||||
chunked_guids = []
|
||||
chunked_guid_ids = []
|
||||
chunked_verts = []
|
||||
chunked_faces = []
|
||||
chunked_materials = []
|
||||
chunked_material_ids = []
|
||||
material_offset = 0
|
||||
max_slot_index = 0
|
||||
chunk_size = 10000
|
||||
r4 = np.array([[0, 0, 0, 1]])
|
||||
offset = 0
|
||||
|
||||
ci = 0
|
||||
if iterator.initialize():
|
||||
while True:
|
||||
shape = iterator.get()
|
||||
if len(shape.geometry.faces) > 1000 and self.is_local(shape): # 333 tris
|
||||
self.process_occurrence(shape)
|
||||
if not iterator.next():
|
||||
mats = np.concatenate(chunked_materials)
|
||||
midx = np.concatenate(chunked_material_ids)
|
||||
mats, mapping = np.unique(mats, axis=0, return_inverse=True)
|
||||
midx = mapping[midx]
|
||||
|
||||
mat_results = []
|
||||
for mat in mats:
|
||||
mat = tuple(mat)
|
||||
blender_mat = blender_mats.get(mat, None)
|
||||
if not blender_mat:
|
||||
blender_mat = bpy.data.materials.new("Chunk")
|
||||
blender_mat.diffuse_color = mat
|
||||
blender_mats[mat] = blender_mat
|
||||
mat_results.append(blender_mat)
|
||||
|
||||
# The left over chunk
|
||||
self.create_object(
|
||||
np.concatenate(chunked_verts),
|
||||
np.concatenate(chunked_faces),
|
||||
mat_results,
|
||||
midx,
|
||||
chunked_guids,
|
||||
chunked_guid_ids,
|
||||
)
|
||||
break
|
||||
continue
|
||||
|
||||
ci += 1
|
||||
if ci % 50 == 0:
|
||||
print("Doing chunk", ci)
|
||||
|
||||
has_processed_chunk = False
|
||||
|
||||
ms = np.vstack([default_mat, np.frombuffer(shape.geometry.colors_buffer).reshape((-1, 4))])
|
||||
mi = np.frombuffer(shape.geometry.material_ids_buffer, dtype=np.int32)
|
||||
chunked_materials.append(ms)
|
||||
chunked_material_ids.append(mi + material_offset + 1)
|
||||
material_offset += len(ms)
|
||||
|
||||
M4 = np.frombuffer(shape.transformation_buffer).reshape((4, 3))
|
||||
M4 = np.concatenate((M4.T, r4))
|
||||
vs = np.frombuffer(shape.geometry.verts_buffer).reshape((-1, 3))
|
||||
vs = np.hstack((vs, np.ones((len(vs), 1))))
|
||||
vs = (np.asmatrix(M4) * np.asmatrix(vs).T).T.A
|
||||
vs = vs[:, :3].flatten()
|
||||
fs = np.frombuffer(shape.geometry.faces_buffer, dtype=np.int32)
|
||||
chunked_verts.append(vs)
|
||||
chunked_faces.append(fs + offset)
|
||||
offset += len(vs) // 3
|
||||
|
||||
chunked_guids.append(shape.guid)
|
||||
if chunked_guid_ids:
|
||||
chunked_guid_ids.append((len(fs) // 3) + chunked_guid_ids[-1])
|
||||
else:
|
||||
chunked_guid_ids.append(len(fs) // 3)
|
||||
|
||||
if offset > chunk_size:
|
||||
has_processed_chunk = True
|
||||
|
||||
mats = np.concatenate(chunked_materials)
|
||||
midx = np.concatenate(chunked_material_ids)
|
||||
mats, mapping = np.unique(mats, axis=0, return_inverse=True)
|
||||
midx = mapping[midx]
|
||||
|
||||
mat_results = []
|
||||
for mat in mats:
|
||||
mat = tuple(mat)
|
||||
blender_mat = blender_mats.get(mat, None)
|
||||
if not blender_mat:
|
||||
blender_mat = bpy.data.materials.new("Chunk")
|
||||
blender_mat.diffuse_color = mat
|
||||
blender_mats[mat] = blender_mat
|
||||
mat_results.append(blender_mat)
|
||||
|
||||
self.create_object(
|
||||
np.concatenate(chunked_verts),
|
||||
np.concatenate(chunked_faces),
|
||||
mat_results,
|
||||
midx,
|
||||
chunked_guids,
|
||||
chunked_guid_ids,
|
||||
)
|
||||
chunked_guids = []
|
||||
chunked_guid_ids = []
|
||||
chunked_verts = []
|
||||
chunked_faces = []
|
||||
chunked_materials = []
|
||||
chunked_material_ids = []
|
||||
material_offset = 0
|
||||
max_slot_index = 0
|
||||
offset = 0
|
||||
|
||||
if not iterator.next():
|
||||
if not has_processed_chunk:
|
||||
mats = np.concatenate(chunked_materials)
|
||||
midx = np.concatenate(chunked_material_ids)
|
||||
mats, mapping = np.unique(mats, axis=0, return_inverse=True)
|
||||
midx = mapping[midx]
|
||||
|
||||
mat_results = []
|
||||
for mat in mats:
|
||||
mat = tuple(mat)
|
||||
blender_mat = blender_mats.get(mat, None)
|
||||
if not blender_mat:
|
||||
blender_mat = bpy.data.materials.new("Chunk")
|
||||
blender_mat.diffuse_color = mat
|
||||
blender_mats[mat] = blender_mat
|
||||
mat_results.append(blender_mat)
|
||||
|
||||
# The left over chunk
|
||||
self.create_object(
|
||||
np.concatenate(chunked_verts),
|
||||
np.concatenate(chunked_faces),
|
||||
mat_results,
|
||||
midx,
|
||||
chunked_guids,
|
||||
chunked_guid_ids,
|
||||
)
|
||||
break
|
||||
|
||||
bpy.context.scene.collection.children.link(self.collection)
|
||||
print("Finished", time.time() - start)
|
||||
break
|
||||
return {"FINISHED"}
|
||||
|
||||
def is_local(self, shape):
|
||||
m = shape.transformation.matrix.data
|
||||
if max([abs(co) for co in (m[9], m[10], m[11])]) > 1000:
|
||||
return False
|
||||
elif max([abs(co) for co in shape.geometry.verts[0:3]]) > 1000:
|
||||
return False
|
||||
return True
|
||||
|
||||
def process_occurrence(self, shape):
|
||||
element = self.file.by_id(shape.id)
|
||||
matrix = shape.transformation.matrix.data
|
||||
faces = shape.geometry.faces
|
||||
verts = shape.geometry.verts
|
||||
materials = shape.geometry.materials
|
||||
material_ids = shape.geometry.material_ids
|
||||
|
||||
m = shape.transformation.matrix.data
|
||||
mat = np.array(([m[0], m[3], m[6], m[9]], [m[1], m[4], m[7], m[10]], [m[2], m[5], m[8], m[11]], [0, 0, 0, 1]))
|
||||
|
||||
mesh = self.meshes.get(shape.geometry.id, None)
|
||||
if not mesh:
|
||||
mesh = bpy.data.meshes.new("Mesh")
|
||||
|
||||
material_to_slot = {}
|
||||
max_slot_index = 0
|
||||
|
||||
for i, material in enumerate(materials):
|
||||
alpha = 1.0
|
||||
if material.has_transparency and material.transparency > 0:
|
||||
alpha = 1.0 - material.transparency
|
||||
diffuse = material.diffuse + (alpha,)
|
||||
material_name = f"{diffuse[0]}-{diffuse[1]}-{diffuse[2]}-{diffuse[3]}"
|
||||
blender_mat = self.blender_mats.get(material_name, None)
|
||||
if not blender_mat:
|
||||
blender_mat = bpy.data.materials.new(material_name)
|
||||
blender_mat.diffuse_color = diffuse
|
||||
self.blender_mats[material_name] = blender_mat
|
||||
slot_index = mesh.materials.find(material.name)
|
||||
if slot_index == -1:
|
||||
mesh.materials.append(blender_mat)
|
||||
slot_index = max_slot_index
|
||||
max_slot_index += 1
|
||||
material_to_slot[i] = slot_index
|
||||
|
||||
material_index = [(material_to_slot[i] if i != -1 else 0) for i in material_ids]
|
||||
|
||||
num_vertices = len(verts) // 3
|
||||
total_faces = len(faces)
|
||||
loop_start = range(0, total_faces, 3)
|
||||
num_loops = total_faces // 3
|
||||
loop_total = [3] * num_loops
|
||||
num_vertex_indices = len(faces)
|
||||
|
||||
mesh.vertices.add(num_vertices)
|
||||
mesh.vertices.foreach_set("co", verts)
|
||||
mesh.loops.add(num_vertex_indices)
|
||||
mesh.loops.foreach_set("vertex_index", faces)
|
||||
mesh.polygons.add(num_loops)
|
||||
mesh.polygons.foreach_set("loop_start", loop_start)
|
||||
mesh.polygons.foreach_set("loop_total", loop_total)
|
||||
mesh.polygons.foreach_set("use_smooth", [0] * total_faces)
|
||||
mesh.polygons.foreach_set("material_index", material_index)
|
||||
mesh.update()
|
||||
|
||||
self.meshes[shape.geometry.id] = mesh
|
||||
|
||||
obj = bpy.data.objects.new(tool.Loader.get_name(element), mesh)
|
||||
obj.matrix_world = Matrix(mat.tolist())
|
||||
|
||||
obj["guids"] = [shape.guid]
|
||||
obj["guid_ids"] = [len(mesh.polygons)]
|
||||
obj["db"] = self.db_filepath
|
||||
|
||||
self.collection.objects.link(obj)
|
||||
|
||||
def create_object(self, verts, faces, materials, material_ids, guids, guid_ids):
|
||||
num_vertices = len(verts) // 3
|
||||
if not num_vertices:
|
||||
return
|
||||
total_faces = len(faces)
|
||||
loop_start = range(0, total_faces, 3)
|
||||
num_loops = total_faces // 3
|
||||
loop_total = [3] * num_loops
|
||||
num_vertex_indices = len(faces)
|
||||
|
||||
mesh = bpy.data.meshes.new("Mesh")
|
||||
|
||||
for material in materials:
|
||||
mesh.materials.append(material)
|
||||
|
||||
mesh.vertices.add(num_vertices)
|
||||
mesh.vertices.foreach_set("co", verts)
|
||||
mesh.loops.add(num_vertex_indices)
|
||||
mesh.loops.foreach_set("vertex_index", faces)
|
||||
mesh.polygons.add(num_loops)
|
||||
mesh.polygons.foreach_set("loop_start", loop_start)
|
||||
mesh.polygons.foreach_set("loop_total", loop_total)
|
||||
mesh.polygons.foreach_set("use_smooth", [0] * total_faces)
|
||||
|
||||
if material_ids.size > 0:
|
||||
mesh.polygons.foreach_set("material_index", material_ids)
|
||||
|
||||
mesh.update()
|
||||
|
||||
obj = bpy.data.objects.new("Chunk", mesh)
|
||||
obj["guids"] = list(guids)
|
||||
obj["guid_ids"] = list(guid_ids)
|
||||
obj["db"] = self.db_filepath
|
||||
|
||||
self.collection.objects.link(obj)
|
||||
|
||||
|
||||
class QueryLinkedElement(bpy.types.Operator):
|
||||
bl_idname = "bim.query_linked_element"
|
||||
bl_label = "Query Linked Element"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.area.type == "VIEW_3D"
|
||||
|
||||
def execute(self, context):
|
||||
import sqlite3
|
||||
from bpy_extras.view3d_utils import region_2d_to_vector_3d, region_2d_to_origin_3d
|
||||
|
||||
LinksData.linked_data = {}
|
||||
bpy.context.scene.BIMProjectProperties.queried_obj = None
|
||||
|
||||
for area in bpy.context.screen.areas:
|
||||
if area.type == "PROPERTIES":
|
||||
for region in area.regions:
|
||||
if region.type == "WINDOW":
|
||||
region.tag_redraw()
|
||||
elif area.type == "VIEW_3D":
|
||||
area.tag_redraw()
|
||||
|
||||
region = context.region
|
||||
rv3d = context.region_data
|
||||
coord = (self.mouse_x, self.mouse_y)
|
||||
origin = region_2d_to_origin_3d(region, rv3d, coord)
|
||||
direction = region_2d_to_vector_3d(region, rv3d, coord)
|
||||
hit, location, normal, face_index, obj, matrix = self.ray_cast(context, origin, direction)
|
||||
if not hit:
|
||||
self.report({"INFO"}, "No object found.")
|
||||
return {"FINISHED"}
|
||||
|
||||
if "guids" not in obj:
|
||||
self.report({"INFO"}, "Object is not a linked IFC element.")
|
||||
return {"FINISHED"}
|
||||
|
||||
guid = None
|
||||
guid_start_index = 0
|
||||
for i, guid_end_index in enumerate(obj["guid_ids"]):
|
||||
if face_index < guid_end_index:
|
||||
guid = obj["guids"][i]
|
||||
bpy.context.scene.BIMProjectProperties.queried_obj = obj
|
||||
|
||||
selected_tris = []
|
||||
selected_edges = []
|
||||
vert_indices = set()
|
||||
for polygon in obj.data.polygons[guid_start_index:guid_end_index]:
|
||||
vert_indices.update(polygon.vertices)
|
||||
vert_indices = list(vert_indices)
|
||||
vert_map = {k: v for v, k in enumerate(vert_indices)}
|
||||
selected_vertices = [tuple(obj.matrix_world @ obj.data.vertices[vi].co) for vi in vert_indices]
|
||||
for polygon in obj.data.polygons[guid_start_index:guid_end_index]:
|
||||
selected_tris.append(tuple(vert_map[v] for v in polygon.vertices))
|
||||
selected_edges.extend(tuple([vert_map[vi] for vi in e] for e in polygon.edge_keys))
|
||||
|
||||
obj["selected_vertices"] = selected_vertices
|
||||
obj["selected_edges"] = selected_edges
|
||||
obj["selected_tris"] = selected_tris
|
||||
|
||||
break
|
||||
guid_start_index = guid_end_index
|
||||
|
||||
self.db = sqlite3.connect(obj["db"])
|
||||
self.c = self.db.cursor()
|
||||
|
||||
self.c.execute(f"SELECT * FROM elements WHERE global_id = '{guid}' LIMIT 1")
|
||||
element = self.c.fetchone()
|
||||
|
||||
attributes = {}
|
||||
for i, attr in enumerate(["GlobalId", "IFC Class", "Predefined Type", "Name", "Description"]):
|
||||
if element[i + 1] is not None:
|
||||
attributes[attr] = element[i + 1]
|
||||
|
||||
self.c.execute("SELECT * FROM properties WHERE element_id = ?", (element[0],))
|
||||
rows = self.c.fetchall()
|
||||
|
||||
properties = {}
|
||||
for row in rows:
|
||||
properties.setdefault(row[1], {})[row[2]] = row[3]
|
||||
|
||||
self.c.execute("SELECT * FROM relationships WHERE from_id = ?", (element[0],))
|
||||
relationships = self.c.fetchall()
|
||||
|
||||
relating_type_id = None
|
||||
|
||||
for relationship in relationships:
|
||||
if relationship[1] == "IfcRelDefinesByType":
|
||||
relating_type_id = relationship[2]
|
||||
|
||||
type_properties = {}
|
||||
if relating_type_id is not None:
|
||||
self.c.execute("SELECT * FROM properties WHERE element_id = ?", (relating_type_id,))
|
||||
rows = self.c.fetchall()
|
||||
for row in rows:
|
||||
type_properties.setdefault(row[1], {})[row[2]] = row[3]
|
||||
|
||||
LinksData.linked_data = {
|
||||
"attributes": attributes,
|
||||
"properties": [(k, properties[k]) for k in sorted(properties.keys())],
|
||||
"type_properties": [(k, type_properties[k]) for k in sorted(type_properties.keys())],
|
||||
}
|
||||
|
||||
for area in bpy.context.screen.areas:
|
||||
if area.type == "PROPERTIES":
|
||||
for region in area.regions:
|
||||
if region.type == "WINDOW":
|
||||
region.tag_redraw()
|
||||
elif area.type == "VIEW_3D":
|
||||
area.tag_redraw()
|
||||
|
||||
self.report({"INFO"}, f"Loaded data for {guid}")
|
||||
ProjectDecorator.install(bpy.context)
|
||||
return {"FINISHED"}
|
||||
|
||||
def ray_cast(self, context, origin, direction):
|
||||
depsgraph = context.evaluated_depsgraph_get()
|
||||
result = context.scene.ray_cast(depsgraph, origin, direction)
|
||||
return result
|
||||
|
||||
def invoke(self, context, event):
|
||||
self.mouse_x = event.mouse_region_x
|
||||
self.mouse_y = event.mouse_region_y
|
||||
return self.execute(context)
|
||||
|
||||
|
||||
class EnableCulling(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_culling"
|
||||
bl_label = "Enable Culling"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def __init__(self):
|
||||
self.last_view_corners = None
|
||||
self.total_mousemoves = 0
|
||||
self.cullable_objects = []
|
||||
|
||||
def modal(self, context, event):
|
||||
if not LinksData.enable_culling:
|
||||
for obj in bpy.context.visible_objects:
|
||||
if obj.type == "MESH" and obj.name.startswith("Ifc"):
|
||||
obj.display_type = "SOLID"
|
||||
self.cullable_objects = []
|
||||
return {"CANCELLED"}
|
||||
|
||||
# Even if the view is changing, there are certain scenarios where we
|
||||
# don't want to apply culling. For example, if we scroll to zoom but
|
||||
# are simultaneously moving our mouse, or if we `Zoom to Selected`. A
|
||||
# dumb but seemingly effective way is to count MOUSEMOVE events. If at
|
||||
# least 3 consecutive events occur, you're probably not doing some
|
||||
# other navigational thing.
|
||||
if event.type == "MOUSEMOVE":
|
||||
self.total_mousemoves += 1
|
||||
else:
|
||||
self.total_mousemoves = 0
|
||||
|
||||
if self.total_mousemoves > 2 and self.is_view_changed(context):
|
||||
self.total_mousemoves = 0
|
||||
camera_position = context.region_data.view_matrix.inverted().translation
|
||||
for obj in self.cullable_objects:
|
||||
if obj.type == "MESH" and obj.name.startswith("Ifc"):
|
||||
if self.is_object_in_view(obj, context, camera_position):
|
||||
obj.display_type = "SOLID"
|
||||
elif obj.display_type != "BOUNDS":
|
||||
obj.display_type = "BOUNDS"
|
||||
|
||||
return {"PASS_THROUGH"}
|
||||
|
||||
def is_view_changed(self, context):
|
||||
view_matrix = context.region_data.view_matrix
|
||||
projection_matrix = context.region_data.window_matrix
|
||||
vp_matrix = projection_matrix @ view_matrix
|
||||
|
||||
# Get NDC coordinates of the viewport corners
|
||||
viewport_corners = [Vector((-1, -1, 0)), Vector((1, -1, 0)), Vector((1, 1, 0)), Vector((-1, 1, 0))]
|
||||
ndc_corners = [vp_matrix @ Vector((corner.x, corner.y, -1, 1)) for corner in viewport_corners]
|
||||
ndc_corners = [(corner / corner.w).xy for corner in ndc_corners]
|
||||
|
||||
if self.last_view_corners != ndc_corners:
|
||||
self.last_view_corners = ndc_corners
|
||||
return True
|
||||
return False
|
||||
|
||||
def is_object_in_view(self, obj, context, camera_position):
|
||||
# Get the view matrix and the projection matrix from the active viewport
|
||||
view_matrix = context.region_data.view_matrix
|
||||
projection_matrix = context.region_data.window_matrix
|
||||
# Calculate the combined view projection matrix
|
||||
vp_matrix = projection_matrix @ view_matrix
|
||||
obj_matrix_world = obj.matrix_world
|
||||
|
||||
# Transform each corner of the bounding box using the view projection matrix
|
||||
# and check if it's inside the normalized device coordinates (NDC) space
|
||||
for corner in [obj_matrix_world @ Vector(corner) for corner in obj.bound_box]:
|
||||
ndc = vp_matrix @ corner.to_4d()
|
||||
ndc /= ndc.w
|
||||
if -1 <= ndc.x <= 1 and -1 <= ndc.y <= 1 and 0 <= ndc.z <= 1:
|
||||
# At least one corner is inside the view, so the object is visible
|
||||
break
|
||||
else:
|
||||
return False
|
||||
|
||||
# Check if the object is too far away from the camera
|
||||
object_center = obj.matrix_world.translation
|
||||
distance_threshold = 900 # 30m squared
|
||||
if (camera_position - object_center).length_squared > distance_threshold:
|
||||
# The object is too far away, so consider it not visible
|
||||
return False
|
||||
return True
|
||||
|
||||
def invoke(self, context, event):
|
||||
LinksData.enable_culling = True
|
||||
self.cullable_objects = []
|
||||
for obj in bpy.context.visible_objects:
|
||||
if obj.type == "MESH" and obj.name.startswith("Ifc") and max(obj.dimensions) < 0.6:
|
||||
self.cullable_objects.append(obj)
|
||||
camera_position = context.region_data.view_matrix.inverted().translation
|
||||
if not self.is_object_in_view(obj, context, camera_position):
|
||||
obj.display_type = "BOUNDS"
|
||||
context.window_manager.modal_handler_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
class DisableCulling(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_culling"
|
||||
bl_label = "Disable Culling"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def execute(self, context):
|
||||
LinksData.enable_culling = False
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RefreshClippingPlanes(bpy.types.Operator):
|
||||
bl_idname = "bim.refresh_clipping_planes"
|
||||
bl_label = "Refresh Clipping Planes"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def __init__(self):
|
||||
self.total_planes = 0
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.modal_handler_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
def modal(self, context, event):
|
||||
should_refresh = False
|
||||
|
||||
self.clean_deleted_planes(context)
|
||||
|
||||
for clipping_plane in context.scene.BIMProjectProperties.clipping_planes:
|
||||
if clipping_plane.obj and self.is_moved(clipping_plane.obj):
|
||||
should_refresh = True
|
||||
break
|
||||
|
||||
total_planes = len(context.scene.BIMProjectProperties.clipping_planes)
|
||||
if should_refresh or total_planes != self.total_planes:
|
||||
self.refresh_clipping_planes(context)
|
||||
for clipping_plane in context.scene.BIMProjectProperties.clipping_planes:
|
||||
if clipping_plane.obj:
|
||||
tool.Geometry.record_object_position(clipping_plane.obj)
|
||||
self.total_planes = total_planes
|
||||
return {"PASS_THROUGH"}
|
||||
|
||||
def clean_deleted_planes(self, context):
|
||||
while True:
|
||||
for i, clipping_plane in enumerate(context.scene.BIMProjectProperties.clipping_planes):
|
||||
if clipping_plane.obj:
|
||||
try:
|
||||
clipping_plane.obj.name
|
||||
except:
|
||||
context.scene.BIMProjectProperties.clipping_planes.remove(i)
|
||||
break
|
||||
else:
|
||||
context.scene.BIMProjectProperties.clipping_planes.remove(i)
|
||||
break
|
||||
else:
|
||||
break
|
||||
|
||||
def is_moved(self, obj):
|
||||
if not obj.BIMObjectProperties.location_checksum:
|
||||
return True # Let's be conservative
|
||||
loc_check = np.frombuffer(eval(obj.BIMObjectProperties.location_checksum))
|
||||
rot_check = np.frombuffer(eval(obj.BIMObjectProperties.rotation_checksum))
|
||||
loc_real = np.array(obj.matrix_world.translation).flatten()
|
||||
rot_real = np.array(obj.matrix_world.to_3x3()).flatten()
|
||||
if np.allclose(loc_check, loc_real, atol=1e-4) and np.allclose(rot_check, rot_real, atol=1e-2):
|
||||
return False
|
||||
return True
|
||||
|
||||
def refresh_clipping_planes(self, context):
|
||||
import bmesh
|
||||
from itertools import cycle
|
||||
|
||||
area = next(a for a in bpy.context.screen.areas if a.type == "VIEW_3D")
|
||||
region = next(r for r in area.regions if r.type == "WINDOW")
|
||||
data = region.data
|
||||
|
||||
if not len(context.scene.BIMProjectProperties.clipping_planes):
|
||||
data.use_clip_planes = False
|
||||
else:
|
||||
with bpy.context.temp_override(area=area, region=region):
|
||||
bpy.ops.view3d.clip_border()
|
||||
|
||||
clip_planes = []
|
||||
for clipping_plane in bpy.context.scene.BIMProjectProperties.clipping_planes:
|
||||
obj = clipping_plane.obj
|
||||
if not obj:
|
||||
continue
|
||||
print("doing", obj)
|
||||
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(obj.data)
|
||||
|
||||
world_matrix = obj.matrix_world
|
||||
|
||||
bm.faces.ensure_lookup_table()
|
||||
face = bm.faces[0]
|
||||
center = world_matrix @ face.calc_center_median()
|
||||
print("center", center)
|
||||
normal = world_matrix.to_3x3() @ face.normal * -1
|
||||
center += normal * -0.01
|
||||
print("normal", normal)
|
||||
print("new center", center)
|
||||
|
||||
normal.normalize()
|
||||
distance = -center.dot(normal)
|
||||
clip_plane = (normal.x, normal.y, normal.z, distance)
|
||||
clip_planes.append(clip_plane)
|
||||
bm.free()
|
||||
|
||||
clip_planes = cycle(clip_planes)
|
||||
data.clip_planes = [tuple(next(clip_planes)) for i in range(0, 6)]
|
||||
data.update()
|
||||
region.tag_redraw()
|
||||
[a.tag_redraw() for a in bpy.context.screen.areas]
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class CreateClippingPlane(bpy.types.Operator):
|
||||
bl_idname = "bim.create_clipping_plane"
|
||||
bl_label = "Create Clipping Plane"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.area.type == "VIEW_3D"
|
||||
|
||||
def execute(self, context):
|
||||
from bpy_extras.view3d_utils import region_2d_to_vector_3d, region_2d_to_origin_3d
|
||||
|
||||
# Clean up deleted planes
|
||||
|
||||
if len(bpy.context.scene.BIMProjectProperties.clipping_planes) > 5:
|
||||
self.report({"INFO"}, "Maximum of six clipping planes allowed.")
|
||||
return {"FINISHED"}
|
||||
|
||||
for area in bpy.context.screen.areas:
|
||||
if area.type == "VIEW_3D":
|
||||
area.tag_redraw()
|
||||
|
||||
region = context.region
|
||||
rv3d = context.region_data
|
||||
coord = (self.mouse_x, self.mouse_y)
|
||||
origin = region_2d_to_origin_3d(region, rv3d, coord)
|
||||
direction = region_2d_to_vector_3d(region, rv3d, coord)
|
||||
hit, location, normal, face_index, obj, matrix = self.ray_cast(context, origin, direction)
|
||||
if not hit:
|
||||
self.report({"INFO"}, "No object found.")
|
||||
return {"FINISHED"}
|
||||
|
||||
vertices = [(-0.5, -0.5, 0), (0.5, -0.5, 0), (0.5, 0.5, 0), (-0.5, 0.5, 0)]
|
||||
|
||||
faces = [(0, 1, 2, 3)]
|
||||
|
||||
mesh = bpy.data.meshes.new(name="ClippingPlane")
|
||||
mesh.from_pydata(vertices, [], faces)
|
||||
mesh.update()
|
||||
|
||||
plane_obj = bpy.data.objects.new("ClippingPlane", mesh)
|
||||
bpy.context.collection.objects.link(plane_obj)
|
||||
z_axis = Vector((0, 0, 1))
|
||||
rotation_matrix = z_axis.rotation_difference(normal).to_matrix().to_4x4()
|
||||
plane_obj.matrix_world = rotation_matrix
|
||||
plane_obj.matrix_world.translation = location
|
||||
|
||||
bpy.context.scene.cursor.location = location
|
||||
|
||||
new = bpy.context.scene.BIMProjectProperties.clipping_planes.add()
|
||||
new.obj = plane_obj
|
||||
|
||||
tool.Blender.set_active_object(plane_obj)
|
||||
|
||||
ClippingPlaneDecorator.install(bpy.context)
|
||||
bpy.ops.bim.refresh_clipping_planes("INVOKE_DEFAULT")
|
||||
return {"FINISHED"}
|
||||
|
||||
def ray_cast(self, context, origin, direction):
|
||||
depsgraph = context.evaluated_depsgraph_get()
|
||||
result = context.scene.ray_cast(depsgraph, origin, direction)
|
||||
return result
|
||||
|
||||
def invoke(self, context, event):
|
||||
self.mouse_x = event.mouse_region_x
|
||||
self.mouse_y = event.mouse_region_y
|
||||
return self.execute(context)
|
||||
|
||||
|
||||
class FlipClippingPlane(bpy.types.Operator):
|
||||
bl_idname = "bim.flip_clipping_plane"
|
||||
bl_label = "Flip Clipping Plane"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.area.type == "VIEW_3D"
|
||||
|
||||
def execute(self, context):
|
||||
obj = bpy.context.active_object
|
||||
if obj in [cp.obj for cp in bpy.context.scene.BIMProjectProperties.clipping_planes]:
|
||||
obj.rotation_euler[0] += radians(180)
|
||||
bpy.context.view_layer.update()
|
||||
return {"FINISHED"}
|
||||
|
||||
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,23 @@ 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_library_display_elements(self, context):
|
||||
bpy.ops.bim.refresh_library()
|
||||
|
||||
|
||||
def update_filter_mode(self, context):
|
||||
self.filter_categories.clear()
|
||||
if self.filter_mode == "NONE":
|
||||
@@ -107,6 +125,11 @@ class BIMProjectProperties(PropertyGroup):
|
||||
organisation_name: StringProperty(name="Organisation")
|
||||
organisation_email: StringProperty(name="Organisation Email")
|
||||
authorisation: StringProperty(name="Authoriser")
|
||||
library_display_elements: BoolProperty(
|
||||
name="Display IfcElements",
|
||||
description="Display IfcElements from library too, not just the types",
|
||||
update=update_library_display_elements,
|
||||
)
|
||||
active_library_element: StringProperty(name="Enable Authoring Mode", default="")
|
||||
library_breadcrumb: CollectionProperty(name="Library Breadcrumb", type=StrProperty)
|
||||
library_elements: CollectionProperty(name="Library Elements", type=LibraryElement)
|
||||
@@ -169,7 +192,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):
|
||||
@@ -255,16 +255,26 @@ 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):
|
||||
self.layout.use_property_split = False
|
||||
self.layout.prop(self.props, "library_display_elements")
|
||||
self.layout.use_property_split = True
|
||||
if not self.props.library_elements:
|
||||
row = self.layout.row()
|
||||
row.label(text="No Assets Found", icon="ERROR")
|
||||
@@ -307,6 +317,41 @@ 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:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="No Object Selected", icon="QUESTION")
|
||||
return
|
||||
|
||||
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")
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -49,8 +49,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 +170,17 @@ 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
|
||||
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
|
||||
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)
|
||||
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)
|
||||
|
||||
@@ -475,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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -450,9 +450,25 @@ 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, do_unlink=True):
|
||||
"""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
|
||||
@@ -551,6 +567,50 @@ class Blender(blenderbim.core.tool.Blender):
|
||||
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, context, event, filepath: Path):
|
||||
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
|
||||
|
||||
@@ -676,13 +676,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 +837,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))
|
||||
|
||||
@@ -1504,7 +1504,7 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
def get_drawing_metadata(cls, drawing):
|
||||
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
|
||||
]
|
||||
|
||||
@@ -1613,9 +1613,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"
|
||||
|
||||
@@ -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())
|
||||
@@ -720,6 +724,9 @@ class Geometry(blenderbim.core.tool.Geometry):
|
||||
|
||||
@classmethod
|
||||
def reload_representation(cls, obj):
|
||||
"""reload `obj` active representation"""
|
||||
if not obj.data:
|
||||
return
|
||||
representation = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
|
||||
blenderbim.core.geometry.switch_representation(
|
||||
tool.Ifc,
|
||||
@@ -743,7 +750,13 @@ class Geometry(blenderbim.core.tool.Geometry):
|
||||
consider_inverses = []
|
||||
styled_item, colour, texture, layer = None, None, None, None
|
||||
[consider_inverses.append(styled_item := t) for t in representation_item.StyledByItem]
|
||||
[consider_inverses.append(layer := t) for t in representation_item.LayerAssignment]
|
||||
# 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", [])]
|
||||
@@ -771,18 +784,6 @@ class Geometry(blenderbim.core.tool.Geometry):
|
||||
also_consider = list(consider_inverses)
|
||||
ifcopenshell.util.element.remove_deep2(ifc_file, representation_item, also_consider=also_consider)
|
||||
|
||||
@classmethod
|
||||
def get_shape_aspects(cls, element):
|
||||
# IfcProduct
|
||||
if hasattr(element, "Representation"):
|
||||
return element.Representation.HasShapeAspects
|
||||
|
||||
# IfcTypeProduct
|
||||
shape_aspects = []
|
||||
for repersentation_map in element.RepresentationMaps:
|
||||
shape_aspects += repersentation_map.HasShapeAspects
|
||||
return shape_aspects
|
||||
|
||||
@classmethod
|
||||
def create_shape_aspect(cls, product_shape, base_representation, items, previous_shape_aspect=None):
|
||||
"""
|
||||
@@ -875,3 +876,54 @@ class Geometry(blenderbim.core.tool.Geometry):
|
||||
)
|
||||
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)
|
||||
|
||||
@@ -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,7 +477,12 @@ 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) -> list[ifcopenshell.entity_instance]:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -733,3 +733,17 @@ class Spatial(blenderbim.core.tool.Spatial):
|
||||
bpy.data.objects[obj.name].show_wire = False
|
||||
bpy.data.objects[obj.name].display_type = "TEXTURED"
|
||||
return
|
||||
|
||||
@classmethod
|
||||
def toggle_hide_spaces(cls, spaces):
|
||||
first_obj = tool.Ifc.get_object(spaces[0])
|
||||
if first_obj.hide_get() == False:
|
||||
for space in spaces:
|
||||
obj = tool.Ifc.get_object(space)
|
||||
obj.hide_set(True)
|
||||
return
|
||||
|
||||
elif first_obj.hide_get() == True:
|
||||
for space in spaces:
|
||||
obj = tool.Ifc.get_object(space)
|
||||
obj.hide_set(False)
|
||||
|
||||
@@ -63,8 +63,9 @@ class Type(blenderbim.core.tool.Type):
|
||||
types = ifc_file.by_type("IfcElementType")
|
||||
# exclude IfcSpatialElementType
|
||||
types += ifc_file.by_type("IfcTypeProduct", include_subtypes=False)
|
||||
types += ifc_file.by_type("IfcWindowStyle")
|
||||
types += ifc_file.by_type("IfcDoorStyle")
|
||||
if not tool.Ifc.get_schema().startswith("IFC4X3"):
|
||||
types += ifc_file.by_type("IfcWindowStyle")
|
||||
types += ifc_file.by_type("IfcDoorStyle")
|
||||
return types
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
# This file will be autogenerated and rewritten when packaged.
|
||||
|
||||
translations_dict = {}
|
||||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
@@ -19,7 +19,8 @@ instructions as the **Stable installation**.
|
||||
|
||||
You will need to choose which build to download.
|
||||
|
||||
- If you are on Blender >=3.1, choose py310
|
||||
- If you are on Blender >=4.1, choose py311
|
||||
- If you are on Blender >=3.1 and <=4.0, choose py10
|
||||
- If you are on Blender >=2.93 and <3.1, choose py39
|
||||
- Choose ``linux``, ``macos``, ``macosm1`` (for Apple M1 devices), or ``win``
|
||||
depending on your operating system
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
Translations
|
||||
============
|
||||
|
||||
The BlenderBIM Add-on supports translations to all languages that Blender
|
||||
supports. We'll describe how you can help translate the add-on as a translator,
|
||||
or how you can ensure your strings are translatable as a developer.
|
||||
Translations are managed using a separate add-on built for this purpose.
|
||||
|
||||
1. Clone the `blenderbim-translations
|
||||
<https://github.com/IfcOpenShell/blenderbim-translations>`_ repository. This
|
||||
repository holds all the core translation strings in ``.po`` format.
|
||||
2. Download the `blenderbim-translations add-on
|
||||
<https://github.com/IfcOpenShell/IfcOpenShell/blob/v0.7.0/src/blenderbim/scripts/bbim_translations.py>`_
|
||||
and install it as a Blender add-on. This add-on lets you convert translation
|
||||
data to and from the ``.po`` files to the BlenderBIM Add-on.
|
||||
3. In the **Render Properties** tab, find the **BlenderBIM Translations**
|
||||
panel, and browse to where you have closed the ``blenderbim-translations``
|
||||
repository, and click on **Setup Translation UI**.
|
||||
|
||||
You should now see a new interface with two buttons.
|
||||
|
||||
.. image:: images/translation-panel.png
|
||||
|
||||
How to translate the add-on
|
||||
---------------------------
|
||||
|
||||
Before beginning, look inside the ``blenderbim-translations`` repository for a
|
||||
``.po`` file for the language you want to translate to (e.g. ``de_DE.po`` for
|
||||
German). If this file does not yet exist, congratulations! You are the first
|
||||
person to translate to this language! Create a folder in
|
||||
``blenderbim-translations`` with your language code and copy the
|
||||
``blenderbim.pot`` language template file to that folder and rename it
|
||||
according to your language (e.g. for German you would copy ``blenderbim.pot`` to
|
||||
``de_DE/de_DE.po``).
|
||||
|
||||
Supported language codes are:
|
||||
|
||||
.. code-block::
|
||||
|
||||
'ca_AD', 'en_US', 'es', 'fr_FR', 'ja_JP', 'sk_SK', 'cs_CZ', 'de_DE',
|
||||
'it_IT', 'ka', 'ko_KR', 'pt_BR', 'pt_PT', 'ru_RU', 'uk_UA', 'vi_VN',
|
||||
'zh_HANS', 'zh_HANT', 'ab', 'ar_EG', 'bg_BG', 'el_GR', 'eo', 'eu_EU',
|
||||
'fa_IR', 'fi_FI', 'ha', 'he_IL', 'hi_IN', 'hr_HR', 'hu_HU', 'id_ID',
|
||||
'ky_KG', 'ne_NP', 'nl_NL', 'pl_PL', 'sr_RS', 'sr_RS@latin', 'sv_SE', 'sw',
|
||||
'ta', 'th_TH', 'tr_TR'.
|
||||
|
||||
As a translator, it is recommended to translate text in bulk by modifying the
|
||||
``.po`` files directly. We recommend installing software such as `Poedit
|
||||
<https://poedit.net/>`_. These translation software offer features such as auto
|
||||
translation, suggestions, and tracking. Alternatively, you may edit the ``.po``
|
||||
file as a text file.
|
||||
|
||||
Once you have edited the relevant language's ``.po`` file, click on the
|
||||
**Update Translations From .po** button in the **BlenderBIM Translations**
|
||||
panel.
|
||||
|
||||
How to add new translation strings
|
||||
----------------------------------
|
||||
|
||||
When you have new strings to translate, press the **Parse BlenderBIM strings to
|
||||
.pot** button. This detects strings in the source code using regex patterns and
|
||||
writes out to the ``blenderbim.pot`` language template file. You may then diff
|
||||
this file and propagate changes manually to all translated ``.po`` files.
|
||||
|
||||
The ``.pot`` file is only used as a blank template for users to create or
|
||||
compare ``.po`` files. The ``.po`` files are the source of truth for
|
||||
translation strings. Blender does not read from the ``.pot`` or ``.po`` files.
|
||||
Instead, Blender reads from ``blenderbim/translations.py`` which contains a
|
||||
dictionary of strings formatted specifically for Blender. The
|
||||
``translations.py`` file is generated from the ``.po`` files. This is generated
|
||||
when we distribute installable packages, or when translators manually press the
|
||||
**Update Translations From .po** button.
|
||||
|
||||
.. warning::
|
||||
|
||||
Do not commit the ``translations.py`` file as it is auto-generated.
|
||||
@@ -42,6 +42,7 @@ Learn how to model a small building and create simple architectural plans, secti
|
||||
devs/hello_world
|
||||
devs/running_tests
|
||||
devs/writing_docs
|
||||
devs/translations
|
||||
devs/undo_system
|
||||
devs/system_support
|
||||
devs/ux_guidelines
|
||||
|
||||
@@ -61,8 +61,8 @@ include:
|
||||
Filtered model loading
|
||||
----------------------
|
||||
|
||||
You may filter elements and only load a portion of the model. Click on **Enable
|
||||
Advanced Mode** when loading a model.
|
||||
You may filter elements and only load a portion of the model. Click on
|
||||
:ref:`users/general_usage/ifc_project:Advanced mode` when loading a model.
|
||||
|
||||
.. image:: images/advanced-mode.png
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ project, or view high level project metadata about the currently loaded project.
|
||||
Choose between starting a completely blank project with no objects, or with
|
||||
preloaded object assets (such as wall types, beam types, etc) that you can
|
||||
use immediately. The blank project is recommended for users who want to
|
||||
curate assets, whereas the IFC4 Demo Library is recommended for users wanting
|
||||
curate assets, whereas the IFC4 Demo Template is recommended for users wanting
|
||||
to experiment and learn with a basic set of objects.
|
||||
|
||||
This also shows other larger object libraries for steel and countries
|
||||
@@ -102,6 +102,9 @@ with basic features to save and unload the project.
|
||||
Advanced mode
|
||||
-------------
|
||||
|
||||
The advanced mode will not load the model right away, but preload it and
|
||||
provide additional loading possibilities like :ref:`users/dealing_with_large_models:Filtered model loading`.
|
||||
|
||||
.. image:: images/ifc-project-load.png
|
||||
|
||||
.. |FILE| image:: /images/icon-FILE.svg
|
||||
|
||||
@@ -57,12 +57,12 @@ class LibraryGenerator:
|
||||
target_view="PLAN_VIEW",
|
||||
parent=plan,
|
||||
),
|
||||
"plan_annotation": ifcopenshell.api.run(
|
||||
"model_annotation": ifcopenshell.api.run(
|
||||
"context.add_context",
|
||||
self.file,
|
||||
context_type="Plan",
|
||||
context_type="Model",
|
||||
context_identifier="Annotation",
|
||||
target_view="PLAN_VIEW",
|
||||
target_view="MODEL_VIEW",
|
||||
parent=plan,
|
||||
),
|
||||
}
|
||||
@@ -194,8 +194,8 @@ class LibraryGenerator:
|
||||
))
|
||||
|
||||
representation = self.file.createIfcShapeRepresentation(
|
||||
self.representations["plan_annotation"],
|
||||
self.representations["plan_annotation"].ContextIdentifier,
|
||||
self.representations["model_annotation"],
|
||||
self.representations["model_annotation"].ContextIdentifier,
|
||||
"Annotation2D",
|
||||
items,
|
||||
)
|
||||
|
||||
@@ -16,9 +16,13 @@
|
||||
# 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 csv
|
||||
import random
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import random
|
||||
import blenderbim.tool as tool
|
||||
from math import cos, sin, tan, pi
|
||||
from pathlib import Path
|
||||
from itertools import chain
|
||||
@@ -28,6 +32,10 @@ from mathutils import Vector, Matrix
|
||||
from random import uniform
|
||||
|
||||
|
||||
# When run from Blender
|
||||
BLEND_DIR = os.path.dirname(bpy.data.filepath)
|
||||
OUT_PATH = os.path.join(BLEND_DIR, "..", "blenderbim", "bim", "data", "libraries", "IFC4 Landscape Library.ifc")
|
||||
|
||||
SimpleTreeParams = namedtuple("SimpleTreeParams", "plant_height crown_diameter trunk_diameter")
|
||||
LowPolyTreeParams = namedtuple(
|
||||
"LowPolyTreeParams", "plant_height crown_diameter crown_max_loc crown_taper trunk_height trunk_diameter random_seed"
|
||||
@@ -299,7 +307,7 @@ tree_presets = {
|
||||
|
||||
|
||||
class LibraryGenerator:
|
||||
def generate(self, library_name, output_filename="IFC4 EU Steel.ifc"):
|
||||
def generate(self, library_name, output_filename):
|
||||
ifcopenshell.api.pre_listeners = {}
|
||||
ifcopenshell.api.post_listeners = {}
|
||||
|
||||
@@ -319,7 +327,7 @@ class LibraryGenerator:
|
||||
model = ifcopenshell.api.run("context.add_context", self.file, context_type="Model")
|
||||
plan = ifcopenshell.api.run("context.add_context", self.file, context_type="Plan")
|
||||
self.representations = {
|
||||
"model_body": ifcopenshell.api.run(
|
||||
"Model/Body/MODEL_VIEW": ifcopenshell.api.run(
|
||||
"context.add_context",
|
||||
self.file,
|
||||
context_type="Model",
|
||||
@@ -327,7 +335,7 @@ class LibraryGenerator:
|
||||
target_view="MODEL_VIEW",
|
||||
parent=model,
|
||||
),
|
||||
"plan_body": ifcopenshell.api.run(
|
||||
"Plan/Body/PLAN_VIEW": ifcopenshell.api.run(
|
||||
"context.add_context",
|
||||
self.file,
|
||||
context_type="Plan",
|
||||
@@ -335,30 +343,60 @@ class LibraryGenerator:
|
||||
target_view="PLAN_VIEW",
|
||||
parent=plan,
|
||||
),
|
||||
"Model/Body/PLAN_VIEW": ifcopenshell.api.run(
|
||||
"context.add_context",
|
||||
self.file,
|
||||
context_type="Model",
|
||||
context_identifier="Body",
|
||||
target_view="PLAN_VIEW",
|
||||
parent=model,
|
||||
),
|
||||
"Model/Body/SECTION_VIEW": ifcopenshell.api.run(
|
||||
"context.add_context",
|
||||
self.file,
|
||||
context_type="Model",
|
||||
context_identifier="Body",
|
||||
target_view="SECTION_VIEW",
|
||||
parent=model,
|
||||
),
|
||||
}
|
||||
|
||||
# Manually modeled trees
|
||||
for obj in bpy.data.objects:
|
||||
if not obj.type == "MESH":
|
||||
continue
|
||||
if "Plan/" in obj.name or "Model/" in obj.name:
|
||||
continue
|
||||
representations = {"Model/Body/MODEL_VIEW": obj.name}
|
||||
for rep_key in self.representations.keys():
|
||||
rep_obj = bpy.data.objects.get(obj.name + " " + rep_key)
|
||||
if rep_obj:
|
||||
representations[rep_key] = rep_obj.name
|
||||
self.create_type("IfcGeographicElementType", obj.name, representations)
|
||||
|
||||
# Auto generated generic trees
|
||||
|
||||
self.builder = ShapeBuilder(self.file)
|
||||
builder = self.builder
|
||||
|
||||
TreeData = namedtuple("TreeData", "tree_name preset_name preset_data")
|
||||
trees = [
|
||||
TreeData("Cone Tree (1.8m)", "low_poly", (1.8, 1.13, 0.0, 1.0, 0.28, 0.16, 10)),
|
||||
TreeData("Cone Tree (15m)", "low_poly", (15.0, 4.37, 0.0, 1.0, 0.74, 0.44, 10)),
|
||||
TreeData("Cone Tree (5m)", "low_poly", (5.0, 1.7, 0.0, 1.0, 0.32, 0.22, 10)),
|
||||
TreeData("Hedge Small (0.4m)", "low_poly", (0.4, 0.4, 0.49, 0.15, 0.0, 0.0, 10)),
|
||||
TreeData("Hedge Medium (0.8m)", "low_poly", (0.8, 0.8, 0.49, 0.15, 0.0, 0.0, 10)),
|
||||
TreeData("Hedge Big (1.8m)", "low_poly", (1.8, 1.8, 0.49, 0.15, 0.0, 0.0, 10)),
|
||||
TreeData("Palm Tree (5m)", "palm_tree", (5.0, 8.02, 0.2)),
|
||||
TreeData("Conical Tree Small (5m)", "low_poly", (5.0, 1.13, 0.0, 1.0, 0.28, 0.16, 5)),
|
||||
TreeData("Conical Tree Medium (10m)", "low_poly", (10.0, 2.7, 0.0, 1.0, 0.32, 0.22, 4)),
|
||||
TreeData("Conical Tree Big (15m)", "low_poly", (15.0, 4.37, 0.0, 1.0, 0.74, 0.44, 3)),
|
||||
TreeData("Palm Tree (15m)", "palm_tree", (15.0, 16.64, 0.4)),
|
||||
TreeData("Shrub Small (0.4m)", "low_poly", (0.4, 0.4, 0.535, 0.524, 0.0, 0.0, 10)),
|
||||
TreeData("Shrum Medium (0.8m)", "low_poly", (0.8, 0.8, 0.535, 0.524, 0.0, 0.0, 10)),
|
||||
TreeData("Shrub Big (1.8m)", "low_poly", (1.8, 1.8, 0.535, 0.524, 0.0, 0.0, 10)),
|
||||
TreeData("Simple Tree (1.8m)", "simple", (1.8, 1.34, 0.2)),
|
||||
TreeData("Simple Tree (5m)", "simple", (5.0, 3.29, 0.2)),
|
||||
TreeData("Simple Tree (15m)", "simple", (15.0, 9.21, 0.44)),
|
||||
TreeData("Tree Small (1.8m)", "low_poly", (1.8, 1.7, 0.5, 0.36, 0.28, 0.16, 10)),
|
||||
TreeData("Tree Big (15m)", "low_poly", (15.0, 10.0, 0.5, 0.36, 1.83, 0.44, 10)),
|
||||
TreeData("Tree Medium (5m)", "low_poly", (5.0, 5.0, 0.5, 0.36, 0.84, 0.22, 10)),
|
||||
TreeData("Shrub Big (0.8m)", "low_poly", (0.8, 0.8, 0.535, 0.524, 0.0, 0.0, 10)),
|
||||
TreeData("Lollipop Tree (4m)", "simple", (4.0, 2.5, 0.2)),
|
||||
TreeData("Lollipop Tree (6m)", "simple", (6.0, 3.5, 0.2)),
|
||||
TreeData("Lollipop Tree (8m)", "simple", (8.0, 4.5, 0.2)),
|
||||
TreeData("Lollipop Tree (10m)", "simple", (10.0, 5.5, 0.44)),
|
||||
TreeData("Lollipop Tree (14m)", "simple", (14.0, 7.5, 0.44)),
|
||||
TreeData("Lollipop Tree (18m)", "simple", (18.0, 9.5, 0.44)),
|
||||
TreeData("Lollipop Tree (25m)", "simple", (25.0, 13.5, 0.50)),
|
||||
TreeData("Tree Small (8m)", "low_poly", (8.0, 6.0, 0.0, .8, 1.0, 0.2, 10)),
|
||||
TreeData("Tree Medium (12m)", "low_poly", (12.0, 10.0, 0.0, .8, 2.0, 0.4, 20)),
|
||||
TreeData("Tree Big (20m)", "low_poly", (20.0, 15.0, 0.0, .8, 4.0, 0.6, 30)),
|
||||
]
|
||||
|
||||
for tree_data in trees:
|
||||
@@ -369,14 +407,29 @@ class LibraryGenerator:
|
||||
"IfcGeographicElementType", tree_data.tree_name, **self.get_representations(tree_geometry)
|
||||
)
|
||||
|
||||
# From tree species table
|
||||
|
||||
with open(os.path.join(BLEND_DIR, "tree_species.csv"), 'r') as csvfile:
|
||||
reader = csv.reader(csvfile)
|
||||
for i, row in enumerate(reader):
|
||||
if i == 0:
|
||||
continue
|
||||
preset = tree_presets.get(row[0])
|
||||
data = [float(x) for x in row[2:]]
|
||||
preset_data = preset.preset_class(*data)._asdict()
|
||||
tree_geometry = preset.generator(builder, **preset_data)
|
||||
self.create_explicit_type(
|
||||
"IfcGeographicElementType", row[1], **self.get_representations(tree_geometry)
|
||||
)
|
||||
|
||||
self.file.write(output_filename)
|
||||
|
||||
def get_representations(self, generated_geometry: GeneratedGeometry):
|
||||
representation_2d = self.builder.get_representation(
|
||||
self.representations["plan_body"], items=generated_geometry.items_2d
|
||||
self.representations["Plan/Body/PLAN_VIEW"], items=generated_geometry.items_2d
|
||||
)
|
||||
representation_3d = self.builder.get_representation(
|
||||
self.representations["model_body"], items=generated_geometry.items_3d
|
||||
self.representations["Model/Body/MODEL_VIEW"], items=generated_geometry.items_3d
|
||||
)
|
||||
return {
|
||||
"representation_3d": representation_3d,
|
||||
@@ -397,7 +450,39 @@ class LibraryGenerator:
|
||||
ifcopenshell.api.run("project.assign_declaration", self.file, definition=element, relating_context=self.library)
|
||||
return element
|
||||
|
||||
def create_type(self, ifc_class, name, representations):
|
||||
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class=ifc_class, name=name)
|
||||
for rep_name, obj_name in representations.items():
|
||||
obj = bpy.data.objects.get(obj_name)
|
||||
representation = ifcopenshell.api.run(
|
||||
"geometry.add_representation",
|
||||
self.file,
|
||||
context=self.representations[rep_name],
|
||||
blender_object=obj,
|
||||
geometry=obj.data,
|
||||
total_items=max(1, len(obj.material_slots)),
|
||||
)
|
||||
styles = []
|
||||
for slot in obj.material_slots:
|
||||
style = ifcopenshell.api.run("style.add_style", self.file, name=slot.material.name)
|
||||
ifcopenshell.api.run(
|
||||
"style.add_surface_style",
|
||||
self.file,
|
||||
style=style,
|
||||
ifc_class="IfcSurfaceStyleShading",
|
||||
attributes=tool.Style.get_surface_shading_attributes(slot.material),
|
||||
)
|
||||
styles.append(style)
|
||||
if styles:
|
||||
ifcopenshell.api.run(
|
||||
"style.assign_representation_styles", self.file, shape_representation=representation, styles=styles
|
||||
)
|
||||
ifcopenshell.api.run(
|
||||
"geometry.assign_representation", self.file, product=element, representation=representation
|
||||
)
|
||||
ifcopenshell.api.run("project.assign_declaration", self.file, definition=element, relating_context=self.library)
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
path = Path(__file__).parents[1] / "blenderbim/bim/data/libraries"
|
||||
LibraryGenerator().generate("Landscape Assets Library", output_filename=str(path / "IFC4 Landscape Library.ifc"))
|
||||
LibraryGenerator().generate("Landscape Assets Library", output_filename=OUT_PATH)
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,27 @@
|
||||
"f","name","PlantHeight","CrownDiameter","CrownMaxLoc","CrownTaper","TrunkHeight","TrunkDiameter","RandomSeed"
|
||||
"low_poly","Apple",8,9,0.5,0.2,1.5,0.2,1
|
||||
"low_poly","Beech",30,20,0,0.4,3,0.75,2
|
||||
"low_poly","Cedar",25,17,0,1,1,0.625,3
|
||||
"low_poly","Dogwood",8,6,1,0.9,0.3,0.2,4
|
||||
"low_poly","Elm",17,12,0.6,0.7,2,0.425,5
|
||||
"low_poly","Fir",27,15,0.1,1,0.1,0.675,6
|
||||
"low_poly","Ginkgo",25,9,0.5,0.6,7,0.625,7
|
||||
"low_poly","Horsechesnut",30,23,0.5,0.2,3,0.75,8
|
||||
"low_poly","Ilex",26,13,0.4,0.3,12,0.65,9
|
||||
"low_poly","Juniper",30,15,0.7,1,1,0.75,10
|
||||
"low_poly","Kentucky Coffeetree",20,10,0.5,0,6,0.5,11
|
||||
"low_poly","Linden",28,18,0.3,0.2,3,0.7,12
|
||||
"low_poly","Mulberry",15,12,0.5,0.5,2,0.375,13
|
||||
"low_poly","Nannybery",5,4,0.7,0.8,1,0.125,14
|
||||
"low_poly","Oak",25,13,0.5,0.5,6,0.625,15
|
||||
"low_poly","Pine",27,9,0.5,0.2,14,0.675,16
|
||||
"low_poly","Quaking Aspen",13,4,0.5,0.3,4,0.325,17
|
||||
"low_poly","Redwood",90,24,0.1,0.8,8,2.25,18
|
||||
"low_poly","Sycamore",35,22,0.2,0.6,5,0.875,19
|
||||
"low_poly","Tree of Heaven",20,15,0.2,0.8,3,0.5,20
|
||||
"low_poly","Umbrella Tree",8,5,0.4,0.5,2,0.2,21
|
||||
"low_poly","Viburnum",4,3,0.8,0.7,0.2,0.1,22
|
||||
"low_poly","Weeping Willow",13,12,0.9,0.3,1,0.325,23
|
||||
"low_poly","Zanthoxylum",10,8,0.4,0.6,2,0.25,24
|
||||
"low_poly","Yellowwood",25,20,0.2,0.3,5,0.625,25
|
||||
"low_poly","Zelkova",12,8,0.4,0.4,2,0.3,26
|
||||
|
@@ -552,3 +552,58 @@ Scenario: Override paste buffer - with active IFC data
|
||||
And the object "IfcWall/Cube.001" has a "Tessellation" representation of "Model/Body/MODEL_VIEW"
|
||||
And the object "IfcBuildingStorey/My Storey.001" exists
|
||||
And the object "IfcBuildingStorey/My Storey.001" is an "IfcBuildingStorey"
|
||||
|
||||
Scenario: Duplicate linked aggregate
|
||||
Given I load the IFC test file "/test/files/linked-aggregates.ifc"
|
||||
And the object "IfcWall/Wall_01" is selected
|
||||
When I duplicate linked aggregate the selected objects
|
||||
Then the object "IfcWall/Wall_01.001" exists
|
||||
And the object "IfcWall/Wall_02.001" exists
|
||||
And the object "IfcElementAssembly/Assembly_02" exists
|
||||
Then the object "IfcElementAssembly/Assembly" and "IfcElementAssembly/Assembly_02" belong to the same Linked Aggregate Group
|
||||
|
||||
Scenario: Refresh linked aggregate
|
||||
Given I load the IFC test file "/test/files/linked-aggregates.ifc"
|
||||
And the object "IfcWall/Wall_01" is selected
|
||||
When I duplicate linked aggregate the selected objects
|
||||
Then the object "IfcWall/Wall_01.001" exists
|
||||
When I deselect all objects
|
||||
And the object "IfcWall/Wall_01.001" is selected
|
||||
When the object layer length is set to "3"
|
||||
Then the object "IfcWall/Wall_01.001" dimensions are "3,0.1,3"
|
||||
When I refresh linked aggregate the selected object
|
||||
Then the object "IfcWall/Wall_01" exists
|
||||
And the object "IfcWall/Wall_01" dimensions are "3,0.1,3"
|
||||
|
||||
Scenario: Refresh linked aggregate - after deleting an object
|
||||
Given I load the IFC test file "/test/files/linked-aggregates.ifc"
|
||||
And the object "IfcWall/Wall_01" is selected
|
||||
When I duplicate linked aggregate the selected objects
|
||||
Then the object "IfcWall/Wall_01.001" exists
|
||||
When I deselect all objects
|
||||
And the object "IfcWall/Wall_01.001" is selected
|
||||
And I delete the selected objects
|
||||
When I deselect all objects
|
||||
And the object "IfcWall/Wall_02.001" is selected
|
||||
When I refresh linked aggregate the selected object
|
||||
Then the object "IfcWall/Wall_01" does not exist
|
||||
And the object "IfcWall/Wall_02" exists
|
||||
|
||||
Scenario: Refresh linked aggregate - after duplicating an object
|
||||
Given I load the IFC test file "/test/files/linked-aggregates.ifc"
|
||||
And the object "IfcWall/Wall_01" is selected
|
||||
When I duplicate linked aggregate the selected objects
|
||||
Then the object "IfcWall/Wall_01.001" exists
|
||||
And the object "IfcWall/Wall_02.001" exists
|
||||
When I deselect all objects
|
||||
And the object "IfcWall/Wall_01" is selected
|
||||
When I duplicate the selected objects
|
||||
Then the object "IfcWall/Wall_01.002" exists
|
||||
When I rename the object "IfcWall/Wall_01.002" to "IfcWall/Wall_03"
|
||||
And I deselect all objects
|
||||
And the object "IfcWall/Wall_03" is selected
|
||||
When I refresh linked aggregate the selected object
|
||||
Then the object "IfcWall/Wall_01.001" exists
|
||||
And the object "IfcWall/Wall_02.001" exists
|
||||
And the object "IfcWall/Wall_03.001" exists
|
||||
|
||||
|
||||
@@ -233,6 +233,16 @@ def i_evaluate_expression(expression):
|
||||
def i_duplicate_the_selected_objects():
|
||||
bpy.ops.bim.override_object_duplicate_move()
|
||||
blenderbim.bim.handler.active_object_callback()
|
||||
|
||||
@when("I duplicate linked aggregate the selected objects")
|
||||
def i_duplicate_the_selected_objects():
|
||||
bpy.ops.bim.object_duplicate_move_linked_aggregate()
|
||||
blenderbim.bim.handler.active_object_callback()
|
||||
|
||||
@when("I refresh linked aggregate the selected object")
|
||||
def i_refresh_the_selected_objects():
|
||||
bpy.ops.bim.refresh_linked_aggregate()
|
||||
blenderbim.bim.handler.active_object_callback()
|
||||
|
||||
|
||||
@when("I deselect all objects")
|
||||
@@ -360,6 +370,13 @@ def the_object_name_exists(name) -> bpy.types.Object:
|
||||
return obj
|
||||
|
||||
|
||||
@then(parsers.parse('the object "{name}" does not exist'))
|
||||
def the_object_name_does_not_exist(name) -> bpy.types.Object:
|
||||
obj = bpy.data.objects.get(name)
|
||||
if not obj:
|
||||
assert True, f'The object "{name}" exists'
|
||||
return obj
|
||||
|
||||
@given(parsers.parse('the collection "{name}" exists'))
|
||||
@when(parsers.parse('the collection "{name}" exists'))
|
||||
@then(parsers.parse('the collection "{name}" exists'))
|
||||
@@ -774,6 +791,10 @@ def the_file_name_should_contain_value(name, value):
|
||||
def the_object_name_has_no_modifiers(name):
|
||||
assert len(the_object_name_exists(name).modifiers) == 0
|
||||
|
||||
@given(parsers.parse('I load the IFC test file "{filepath}"'))
|
||||
def i_load_the_ifc_test_file(filepath):
|
||||
filepath = f"{variables['cwd']}{filepath}"
|
||||
bpy.ops.bim.load_project(filepath=filepath, use_relative_path=True)
|
||||
|
||||
@given("I load the demo construction library")
|
||||
@when("I load the demo construction library")
|
||||
@@ -826,6 +847,64 @@ def hit_undo():
|
||||
bpy.ops.ed.undo_push(message="UNDO STEP")
|
||||
bpy.ops.ed.undo()
|
||||
|
||||
@then(parsers.parse('the object "{obj_name1}" has a connection with "{obj_name2}"'))
|
||||
def the_obj1_has_a_connection_with_obj2(obj_name1, obj_name2):
|
||||
element1 = replace_variables(obj_name1)
|
||||
element1 = tool.Ifc.get_entity(the_object_name_exists(element1))
|
||||
element2 = replace_variables(obj_name2)
|
||||
element2 = tool.Ifc.get_entity(the_object_name_exists(element2))
|
||||
connections = []
|
||||
if hasattr(element1, "ConnectedTo") and element1.ConnectedTo:
|
||||
connections = [connection for connection in element1.ConnectedTo]
|
||||
elif hasattr(element1, "ConnectedFrom") and element1.ConnectedFrom:
|
||||
connections = [connection for connection in element1.ConnectedFrom]
|
||||
else:
|
||||
assert False, f'Object "{obj_name1}" has no connections'
|
||||
|
||||
relationships = {}
|
||||
for conn in connections:
|
||||
relationships[conn.RelatedElement] = conn.RelatingElement
|
||||
|
||||
for key, value in relationships.items():
|
||||
assert (key.id() == element1.id() and value.id() == element2.id()) or (key.id() == element2.id() and value.id() == element1.id()), f"The object {obj_name1} is connected to {obj_name2}"
|
||||
|
||||
|
||||
@then(parsers.parse('the object "{obj_name1}" and "{obj_name2}" belong to the same Linked Aggregate Group'))
|
||||
def the_obj1_and_obj2_belong_the_same_linked_aggregate_group(obj_name1, obj_name2):
|
||||
element1 = replace_variables(obj_name1)
|
||||
element1 = tool.Ifc.get_entity(the_object_name_exists(element1))
|
||||
element2 = replace_variables(obj_name2)
|
||||
element2 = tool.Ifc.get_entity(the_object_name_exists(element2))
|
||||
|
||||
elements = [element1, element2]
|
||||
groups = []
|
||||
for element in elements:
|
||||
product_linked_agg_group = [
|
||||
r.RelatingGroup
|
||||
for r in getattr(element, "HasAssignments", []) or []
|
||||
if r.is_a("IfcRelAssignsToGroup")
|
||||
if "BBIM_Linked_Aggregate" in r.RelatingGroup.Name
|
||||
]
|
||||
try:
|
||||
groups.append(product_linked_agg_group[0].id())
|
||||
except:
|
||||
assert False, "Object is not part of a Linked Aggregate."
|
||||
|
||||
assert groups[0] == groups[1], "Objects do not belong to the same Linked Aggregate group"
|
||||
|
||||
@when(parsers.parse('the object layer length is set to "{value}"'))
|
||||
def the_obj_layer_lenght_is_set_to(value):
|
||||
value = float(value)
|
||||
try:
|
||||
eval(f"bpy.context.scene.BIMModelProperties.length")
|
||||
except:
|
||||
assert False, f"Property BIMModelProperties.length does not exist when trying to set to value {value}"
|
||||
|
||||
print(50*"@", bpy.context.selected_objects)
|
||||
|
||||
bpy.context.scene.BIMModelProperties.length = value
|
||||
bpy.ops.bim.change_layer_length(length=value)
|
||||
|
||||
|
||||
# These definitions are not to be used in tests but simply in debugging failing tests
|
||||
|
||||
@@ -875,27 +954,4 @@ def run_pdb():
|
||||
|
||||
pdb.set_trace()
|
||||
|
||||
@then(parsers.parse('the object "{obj_name1}" has a connection with "{obj_name2}"'))
|
||||
def the_obj1_has_a_connection_with_obj2(obj_name1, obj_name2):
|
||||
element1 = replace_variables(obj_name1)
|
||||
element1 = tool.Ifc.get_entity(the_object_name_exists(element1))
|
||||
element2 = replace_variables(obj_name2)
|
||||
element2 = tool.Ifc.get_entity(the_object_name_exists(element2))
|
||||
connections = []
|
||||
if hasattr(element1, "ConnectedTo") and element1.ConnectedTo:
|
||||
connections = [connection for connection in element1.ConnectedTo]
|
||||
elif hasattr(element1, "ConnectedFrom") and element1.ConnectedFrom:
|
||||
connections = [connection for connection in element1.ConnectedFrom]
|
||||
else:
|
||||
assert False, f'Object "{obj_name1}" has no connections'
|
||||
|
||||
|
||||
relationships = {}
|
||||
for conn in connections:
|
||||
relationships[conn.RelatedElement] = conn.RelatingElement
|
||||
|
||||
for key, value in relationships.items():
|
||||
print(key, value)
|
||||
# assert False, f"1-{key} and {element1.id()} and {key.id()}"
|
||||
assert (key.id() == element1.id() and value.id() == element2.id()) or (key.id() == element2.id() and value.id() == element1.id()), f"The object {obj_name1} is connected to {obj_name2}"
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -75,7 +75,7 @@ class TestGetActiveReference(NewFile):
|
||||
|
||||
class TestImportLibraryAttributes(NewFile):
|
||||
def test_run(self):
|
||||
ifc = ifcopenshell.file()
|
||||
tool.Ifc.set(ifc := ifcopenshell.file())
|
||||
library = ifc.createIfcLibraryInformation("Name", "Version", None, "VersionDate", "Location", "Description")
|
||||
subject.import_library_attributes(library)
|
||||
props = bpy.context.scene.BIMLibraryProperties
|
||||
@@ -88,7 +88,7 @@ class TestImportLibraryAttributes(NewFile):
|
||||
|
||||
class TestImportReferenceAttributes(NewFile):
|
||||
def test_run(self):
|
||||
ifc = ifcopenshell.file()
|
||||
tool.Ifc.set(ifc := ifcopenshell.file())
|
||||
reference = ifc.createIfcLibraryReference("Location", "Identification", "Name", "Description", "Language")
|
||||
subject.import_reference_attributes(reference)
|
||||
props = bpy.context.scene.BIMLibraryProperties
|
||||
|
||||
@@ -169,3 +169,31 @@ class TestSetObjectName(NewFile):
|
||||
element.Name = "Foobar"
|
||||
subject.set_object_name(obj, element)
|
||||
assert obj.name == "IfcWall/Foobar"
|
||||
|
||||
|
||||
class TestReassignClass(NewFile):
|
||||
def test_run(self):
|
||||
bpy.context.scene.BIMProjectProperties.template_file = "IFC4 Demo Template.ifc"
|
||||
bpy.ops.bim.create_project()
|
||||
ifc_file = tool.Ifc.get()
|
||||
context = bpy.context
|
||||
relating_type_id = ifc_file.by_type("IfcSlabType")[0].id()
|
||||
n_wall_types = len(ifc_file.by_type("IfcWallType"))
|
||||
n_slab_types = len(ifc_file.by_type("IfcSlabType"))
|
||||
|
||||
# create 3 slabs
|
||||
bpy.ops.bim.add_constr_type_instance(relating_type_id=relating_type_id)
|
||||
bpy.ops.bim.add_constr_type_instance(relating_type_id=relating_type_id)
|
||||
bpy.ops.bim.add_constr_type_instance(relating_type_id=relating_type_id)
|
||||
|
||||
slabs = [tool.Ifc.get_object(e) for e in ifc_file.by_type("IfcSlab")]
|
||||
assert len(slabs) == 3
|
||||
tool.Blender.set_objects_selection(context, slabs[0], (slabs[1],))
|
||||
context.scene.BIMRootProperties.ifc_product = "IfcElement"
|
||||
context.scene.BIMRootProperties.ifc_class = "IfcWall"
|
||||
bpy.ops.bim.reassign_class()
|
||||
|
||||
assert len(ifc_file.by_type("IfcWall")) == 3
|
||||
assert len(ifc_file.by_type("IfcSlab")) == 0
|
||||
assert len(ifc_file.by_type("IfcWallType")) == n_wall_types + 1
|
||||
assert len(ifc_file.by_type("IfcSlabType")) == n_slab_types - 1
|
||||
|
||||
@@ -392,7 +392,7 @@ class TestGetUVMaps(NewFile):
|
||||
|
||||
class TestImportSurfaceAttributes(NewFile):
|
||||
def test_run(self):
|
||||
ifc = ifcopenshell.file()
|
||||
tool.Ifc.set(ifc := ifcopenshell.file())
|
||||
style = ifc.createIfcSurfaceStyle("Name", "BOTH")
|
||||
obj = bpy.data.materials.new("Material")
|
||||
subject.import_surface_attributes(style, obj)
|
||||
@@ -400,7 +400,7 @@ class TestImportSurfaceAttributes(NewFile):
|
||||
assert obj.BIMStyleProperties.attributes.get("Side").enum_value == "BOTH"
|
||||
|
||||
def test_importing_surface_attributes_twice(self):
|
||||
ifc = ifcopenshell.file()
|
||||
tool.Ifc.set(ifc := ifcopenshell.file())
|
||||
style = ifc.createIfcSurfaceStyle("Name", "BOTH")
|
||||
obj = bpy.data.materials.new("Material")
|
||||
subject.import_surface_attributes(style, obj)
|
||||
|
||||
@@ -154,7 +154,7 @@ class TestGetSceneUnitSIPrefix:
|
||||
|
||||
class TestImportUnitAttributes(NewFile):
|
||||
def test_importing_derived_units(self):
|
||||
ifc = ifcopenshell.file()
|
||||
tool.Ifc.set(ifc := ifcopenshell.file())
|
||||
unit = ifc.createIfcDerivedUnit()
|
||||
unit.UnitType = "ANGULARVELOCITYUNIT"
|
||||
unit.UserDefinedType = "UserDefinedType"
|
||||
@@ -164,7 +164,7 @@ class TestImportUnitAttributes(NewFile):
|
||||
assert props.unit_attributes.get("UserDefinedType").string_value == "UserDefinedType"
|
||||
|
||||
def test_importing_monetary_units(self):
|
||||
ifc = ifcopenshell.file()
|
||||
tool.Ifc.set(ifc := ifcopenshell.file())
|
||||
unit = ifc.createIfcMonetaryUnit()
|
||||
unit.Currency = "Currency"
|
||||
subject.import_unit_attributes(unit)
|
||||
@@ -172,7 +172,7 @@ class TestImportUnitAttributes(NewFile):
|
||||
assert props.unit_attributes.get("Currency").string_value == "Currency"
|
||||
|
||||
def test_importing_monetary_units_ifc2x3(self):
|
||||
ifc = ifcopenshell.file(schema="IFC2X3")
|
||||
tool.Ifc.set(ifc := ifcopenshell.file(schema="IFC2X3"))
|
||||
unit = ifc.createIfcMonetaryUnit()
|
||||
unit.Currency = "USD"
|
||||
subject.import_unit_attributes(unit)
|
||||
|
||||
@@ -1,3 +1,36 @@
|
||||
# bsdd
|
||||
|
||||
A library to interact with the buildingSMART Data Dictionary (bSDD) API.
|
||||
This packages is totally independent from other packages in IfcOpenShell and requires only a python installation to run.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install git+https://git@github.com/ifcopenshell/ifcopenshell.git@v0.7.0#subdirectory=src/bsdd
|
||||
|
||||
# check your installation
|
||||
python
|
||||
>>> from bsdd import Client
|
||||
>>> client = Client()
|
||||
>>> [l["uri"] for l in client.get_dictionary()["dictionaries"] if "4.3" in l["uri"]][0]
|
||||
'https://identifier.buildingsmart.org/uri/buildingsmart/ifc/4.3'
|
||||
```
|
||||
|
||||
## Develop
|
||||
|
||||
```bash
|
||||
# create a fork in your domain
|
||||
git clone git@github.com:<your-name>/IfcOpenShell.git
|
||||
cd src/bsdd
|
||||
|
||||
# if you use vs code / pycharm, you can launch it from this folder
|
||||
code .
|
||||
|
||||
# if you use conda/mamba. follow instructions below:
|
||||
# otherwise setup a dev env in your preferred way.
|
||||
mamba create -n bsdd-dev python pytest requests
|
||||
mamba activate bsdd-dev
|
||||
|
||||
# run tests
|
||||
pytest
|
||||
```
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# SPDX-FileCopyrightText: 2024-present IfcOpenShell Developers
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
__version__ = "0.1.0"
|
||||
@@ -0,0 +1 @@
|
||||
from .bsdd import *
|
||||
+3
-1
@@ -576,7 +576,7 @@ class Client:
|
||||
|
||||
def get_classes(self, dictionary_uri: str, use_nested_classes: bool = True,
|
||||
class_type: ClassTypes = "Class",
|
||||
language_code: str = "", version: int = 1) -> DictionaryClassesResponseContractV1:
|
||||
language_code: str = "", version: int = 1, offset=0, limit=1000) -> DictionaryClassesResponseContractV1:
|
||||
"""
|
||||
Get Dictionary with tree of classes
|
||||
This API replaces Domain
|
||||
@@ -587,6 +587,8 @@ class Client:
|
||||
"UseNestedClasses": use_nested_classes,
|
||||
"ClassType": class_type,
|
||||
"languageCode": language_code,
|
||||
"offset" : offset,
|
||||
"limit" : limit
|
||||
}
|
||||
return self.get(endpoint, params)
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "bsdd"
|
||||
dynamic = ["version"]
|
||||
description = ''
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.8"
|
||||
keywords = ["building smart data dictionaries"]
|
||||
classifiers = [
|
||||
"Development Status :: 4 - Beta",
|
||||
"Programming Language :: Python",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Programming Language :: Python :: Implementation :: CPython",
|
||||
"Programming Language :: Python :: Implementation :: PyPy",
|
||||
]
|
||||
dependencies = ["requests"]
|
||||
|
||||
[project.urls]
|
||||
Documentation = "https://github.com/IfcOpenShell/src/bsdd#readme"
|
||||
Issues = "https://github.com/IfcOpenShell/issues"
|
||||
Source = "https://github.com/IfcOpenShell/src/bsdd"
|
||||
|
||||
[tool.hatch.version]
|
||||
path = "__about__.py"
|
||||
|
||||
[tool.hatch.envs.default]
|
||||
dependencies = [
|
||||
"coverage[toml]>=6.5",
|
||||
"pytest",
|
||||
]
|
||||
|
||||
[[tool.hatch.envs.all.matrix]]
|
||||
python = ["3.8", "3.9", "3.10", "3.11", "3.12"]
|
||||
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
pythonpath = [".", "src"]
|
||||
@@ -0,0 +1,37 @@
|
||||
from bsdd import Client
|
||||
|
||||
client = Client()
|
||||
|
||||
ifc4x3_uri = [l["uri"] for l in client.get_dictionary()["dictionaries"] if "4.3" in l["uri"]][0]
|
||||
nbs_uri = [l["uri"] for l in client.get_dictionary()["dictionaries"] if 'Uniclass 2015' == l["name"]][0]
|
||||
|
||||
|
||||
def get_ifc_classes():
|
||||
return client.get_classes(ifc4x3_uri, use_nested_classes= False, class_type="Class")
|
||||
|
||||
def get_nbs_classes():
|
||||
return client.get_classes(nbs_uri, use_nested_classes= False, class_type="Class", offset=0, limit=5)
|
||||
|
||||
def test_get_dictionary():
|
||||
li_names = [l["name"] for l in client.get_dictionary()["dictionaries"]]
|
||||
assert 'Uniclass 2015' and 'IFC' in li_names
|
||||
|
||||
def test_get_ifc_classes():
|
||||
ifc4x3_classes = get_ifc_classes()
|
||||
assert "IfcBoiler" and "IfcLightFixture" in [l["code"] for l in ifc4x3_classes["classes"]]
|
||||
|
||||
def test_get_nbs_classes():
|
||||
nbs_classes = get_nbs_classes()
|
||||
assert "Ac" in [l["code"] for l in nbs_classes["classes"]]
|
||||
|
||||
def test_get_class():
|
||||
uri_light_fixture = [l for l in get_ifc_classes()["classes"] if "IfcLightFixture" == l["code"]][0]["uri"]
|
||||
ifc4x3_light_fixture = client.get_class(uri_light_fixture)
|
||||
assert 'Maintenance Factor' and 'Light Fixture Mounting Type' in [l["name"] for l in ifc4x3_light_fixture["classProperties"]]
|
||||
|
||||
def test_search_class():
|
||||
ss_heat_pump_sys = client.search_class("Ss_60_40_36", [nbs_uri] )
|
||||
li = [l + "source heat pump systems" for l in ["Air ", "Ground ", "Water "]]
|
||||
assert len(li) < 8 # I think it should be 4 but just validating it isn't overfetching with some space for future change
|
||||
for l in li:
|
||||
assert l in [_["name"] for _ in ss_heat_pump_sys["classes"]]
|
||||
@@ -73,7 +73,7 @@ int main() {
|
||||
|
||||
// Lateron changing the name of the IfcProject can be done by obtaining a reference to the
|
||||
// project, which has been created automatically.
|
||||
file.getSingle<IfcSchema::IfcProject>()->setName("IfcOpenHouse"s);
|
||||
file.getSingle<IfcSchema::IfcProject>()->setName("IfcAdvancedHouse"s);
|
||||
|
||||
// To demonstrate the ability to serialize arbitrary opencascade solids a building envelope is
|
||||
// constructed by applying boolean operations. Naturally, in IFC, building elements should be
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user