mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-28 07:49:59 +00:00
Compare commits
218 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| baf75f552e | |||
| 4ea0c73081 | |||
| 33852be3f1 | |||
| 48d476c2b3 | |||
| 9db07883b7 | |||
| 7af53c543d | |||
| e7f6ed73ac | |||
| 8afa1a4f7d | |||
| 1dd85f6c48 | |||
| 8bd826fb20 | |||
| cbc6eefca7 | |||
| 4f86dd143a | |||
| c45c73c563 | |||
| 38f1c622ac | |||
| 38170db32a | |||
| 43d0c098a0 | |||
| 533111a1fb | |||
| 0bba31e5f9 | |||
| 9b9f0058f9 | |||
| 0c8273eb70 | |||
| c072554f21 | |||
| 18c9a57bea | |||
| eaa3301b8f | |||
| efb89acfa3 | |||
| 96256d365a | |||
| fa6ec25a39 | |||
| 44afa0df5d | |||
| c478e5bf1b | |||
| ea530858bd | |||
| 0b4cfea3a9 | |||
| e909f25432 | |||
| 2985bba157 | |||
| a1ba61636d | |||
| 1b5c20e14f | |||
| a43fae68b6 | |||
| fdd03d7294 | |||
| 2049bdd976 | |||
| d45a439f6c | |||
| 8fa5873e7e | |||
| 3fa8fce994 | |||
| b8aa90de00 | |||
| dcc1ed5607 | |||
| 2bfff12988 | |||
| 074a55b816 | |||
| a53fd97e5a | |||
| b39a0d1a7c | |||
| 44b786a21b | |||
| bddc64cad3 | |||
| 3713ec00f9 | |||
| b5a58998d5 | |||
| ca3b4d17ba | |||
| 983a1530b8 | |||
| 74e18a56ce | |||
| 05834d071f | |||
| 8f70f462cc | |||
| 456f45b346 | |||
| 53a9175802 | |||
| 379d6d9392 | |||
| 38b708ff1f | |||
| 233e73f9f6 | |||
| db85aa90d3 | |||
| 6fb1e34ab4 | |||
| 9cc683ea6e | |||
| e343494ab4 | |||
| bc9e403a58 | |||
| 8ffd9dc1f9 | |||
| 57f6c07240 | |||
| f518bbfb00 | |||
| 0f34420834 | |||
| 3ef6077a0d | |||
| 3c26cb672b | |||
| 673f757a51 | |||
| 578ab140cc | |||
| 96902bf38a | |||
| b5133c6874 | |||
| 5dfe85691b | |||
| 3b7bf8844a | |||
| 8038b77504 | |||
| b64601ee0a | |||
| 320bb7a086 | |||
| ad135f70f1 | |||
| 15b24d8ba4 | |||
| 3c2b25d923 | |||
| 3282d75aa5 | |||
| 72fc617196 | |||
| 47cad88c38 | |||
| e81dd9c7df | |||
| d16f92a445 | |||
| 8be79f9aee | |||
| ccc6f2c4c6 | |||
| deb0597000 | |||
| 348373d47e | |||
| 9e22e9486a | |||
| 3cf9017f27 | |||
| 21479482b4 | |||
| 97c660fec8 | |||
| 32af2025ec | |||
| ffc82dfb1e | |||
| 2a69af43f6 | |||
| 5c9984a12a | |||
| c938d6ae9b | |||
| 63d65f0c39 | |||
| 472a66b7ed | |||
| ddaed9e93d | |||
| 4d6eba0a06 | |||
| ed09b7ba31 | |||
| 2c2954b11a | |||
| 5317746a82 | |||
| 5af7e95bee | |||
| 618d902a57 | |||
| 68baa416fa | |||
| 7f212963d5 | |||
| d3a43c6e81 | |||
| a44a13f04c | |||
| 126ba8d88f | |||
| caf04200ce | |||
| 05f3cd41e2 | |||
| 80bc4cdb1b | |||
| dd7a47ab99 | |||
| 4eaf0e7a9c | |||
| 3db252bcc0 | |||
| d03685abfa | |||
| 3e4b1d95cb | |||
| b151ec2b0e | |||
| f909adc2dc | |||
| 913a428794 | |||
| fe6d9ae926 | |||
| 4a679812ca | |||
| 6be87c035f | |||
| 6ce8a3621a | |||
| 60ea856e51 | |||
| 419502ddb7 | |||
| 9f530d0578 | |||
| 524483e9cc | |||
| 7116a9f71a | |||
| 868ced2990 | |||
| 6a8292d24c | |||
| fca4d9dea0 | |||
| 89d5c44855 | |||
| 3e99393895 | |||
| 098ab2c237 | |||
| 94ff8349d3 | |||
| d333cb1b78 | |||
| 3f4c4a8e52 | |||
| b103e0dde5 | |||
| 883b8a523c | |||
| 8c0b06b36e | |||
| 72397f3aff | |||
| 77821caae4 | |||
| 10138e28c9 | |||
| f9ce0903f7 | |||
| a2c0045a40 | |||
| 3e10f52965 | |||
| fd8aa6d142 | |||
| ddb38b2ea0 | |||
| 74174b1e09 | |||
| 96179db7ad | |||
| 116252f857 | |||
| 538024e035 | |||
| e9670a6906 | |||
| c7428dd160 | |||
| bf9d46b506 | |||
| 7ee589cfc2 | |||
| 3083b66f61 | |||
| 074fe0d3a6 | |||
| 46e1a46dd2 | |||
| 677a9d715c | |||
| a210cf654e | |||
| 44e4ee70ae | |||
| 27e6d702e9 | |||
| fb76f985c7 | |||
| 9279aeccf6 | |||
| 92a1739d93 | |||
| dfba9827a6 | |||
| 19e007a67c | |||
| d3ef3f1501 | |||
| f2e9f76631 | |||
| 52739418d8 | |||
| 7f1fddf426 | |||
| ee4f48b566 | |||
| 1d47ad3cd4 | |||
| 36a7985f5a | |||
| e129f4afa9 | |||
| 8fd4351195 | |||
| 01f25528e8 | |||
| 3222c9ce99 | |||
| 28e6bb9e3d | |||
| 5884af0678 | |||
| f88a1038a8 | |||
| cafe8ce5f2 | |||
| 8569bcf350 | |||
| e38759c39d | |||
| 50ed8eccfd | |||
| d63d62ddd6 | |||
| 0bdb4b9fcf | |||
| 7e7c54a535 | |||
| 817fdead9b | |||
| 68a43b92ee | |||
| 595c72e08d | |||
| 5709efbdcb | |||
| 67952ed159 | |||
| 000e3acb2b | |||
| b337d5a174 | |||
| 6b0bdc564d | |||
| ab5db2fa23 | |||
| 810c71dfa2 | |||
| 5b281260f8 | |||
| 07264dafb1 | |||
| 9572fef222 | |||
| 1bbbc82b72 | |||
| aa6d03ade2 | |||
| 4c40fc1629 | |||
| 90e5b5576d | |||
| 531805fefb | |||
| 2852f53afa | |||
| 8b06aa9e0b | |||
| 52ea69bcec | |||
| 9ebe0aaa95 |
@@ -29,7 +29,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
pyver: [py37, py39]
|
||||
pyver: [py37, py39, py310]
|
||||
config:
|
||||
- {
|
||||
name: "Windows Build",
|
||||
|
||||
@@ -82,6 +82,8 @@ jobs:
|
||||
sudo /usr/bin/python -m pip install src/bcf
|
||||
sudo /usr/bin/python -m pip install pytest
|
||||
sudo /usr/bin/python -m pip install isodate
|
||||
sudo /usr/bin/python -m pip install lark
|
||||
sudo /usr/bin/python -m pip install networkx
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
|
||||
@@ -8,3 +8,9 @@
|
||||
[submodule "src/svgfill"]
|
||||
path = src/svgfill
|
||||
url = https://github.com/IfcOpenShell/svgfill
|
||||
[submodule "src/ifcopenshell-python/ifcopenshell/Sample-BIM-Files"]
|
||||
path = src/ifcopenshell-python/ifcopenshell/Sample-BIM-Files
|
||||
url = https://github.com/atomczak/Sample-BIM-Files
|
||||
[submodule "src/ifcopenshell-python/test/Sample-BIM-Files"]
|
||||
path = src/ifcopenshell-python/test/Sample-BIM-Files
|
||||
url = https://github.com/atomczak/Sample-BIM-Files
|
||||
|
||||
@@ -39,6 +39,8 @@ Note 1: The path where the source code is cloned to can contain spaces but non-A
|
||||
|
||||
Note 2: If you had not used `git clone --recursive https://github.com/IfcOpenShell/IfcOpenshell.git`, update the submodules by running `git submodule init & git submodule update`.
|
||||
|
||||
Note 3: Be careful with special characters is the path when using the nix or win build scripts, because the OpenCASCADE build will fail on paths containing ++ and likely other situations.
|
||||
|
||||
### Compiling on Windows
|
||||
The preferred way to fetch and build this project's dependencies is to use the build scripts
|
||||
in win/ folder. **See [win/readme.md] for more information**.
|
||||
|
||||
+13
-9
@@ -62,6 +62,10 @@ endif()
|
||||
OPTION(USERSPACE_PYTHON_PREFIX "Installs IfcPython for the current user only instead of system-wide." OFF)
|
||||
OPTION(ADD_COMMIT_SHA "Add commit sha and branch in version number, warning results in many rebuilds, requires git" OFF)
|
||||
|
||||
IF(NOT CMAKE_BUILD_TYPE)
|
||||
SET(CMAKE_BUILD_TYPE "Release")
|
||||
ENDIF()
|
||||
|
||||
# TODO QtViewer is deprecated ATM as it uses the 0.4 API
|
||||
# OPTION(BUILD_QTVIEWER "Build IfcOpenShell Qt GUI Viewer (requires Qt 4 framework)." OFF)
|
||||
include(GNUInstallDirs)
|
||||
@@ -444,13 +448,17 @@ if(HDF5_SUPPORT)
|
||||
else()
|
||||
set(lib_ext a)
|
||||
endif()
|
||||
|
||||
if ("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
|
||||
set(debug_postfix "_debug")
|
||||
endif()
|
||||
|
||||
SET(HDF5_LIBRARIES
|
||||
"${HDF5_LIBRARY_DIR}/libhdf5_cpp.${lib_ext}"
|
||||
"${HDF5_LIBRARY_DIR}/libhdf5.${lib_ext}"
|
||||
"${HDF5_LIBRARY_DIR}/libz${zlib_post}.${lib_ext}"
|
||||
"${HDF5_LIBRARY_DIR}/libsz.${lib_ext}"
|
||||
"${HDF5_LIBRARY_DIR}/libaec.${lib_ext}"
|
||||
"${HDF5_LIBRARY_DIR}/libhdf5_cpp${debug_postfix}.${lib_ext}"
|
||||
"${HDF5_LIBRARY_DIR}/libhdf5${debug_postfix}.${lib_ext}"
|
||||
"${HDF5_LIBRARY_DIR}/libz${zlib_post}${debug_postfix}.${lib_ext}"
|
||||
"${HDF5_LIBRARY_DIR}/libsz${debug_postfix}.${lib_ext}"
|
||||
"${HDF5_LIBRARY_DIR}/libaec${debug_postfix}.${lib_ext}"
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -470,10 +478,6 @@ if(HDF5_SUPPORT)
|
||||
set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_HDF5)
|
||||
endif()
|
||||
|
||||
IF(NOT CMAKE_BUILD_TYPE)
|
||||
SET(CMAKE_BUILD_TYPE "Release")
|
||||
ENDIF()
|
||||
|
||||
if(ENABLE_BUILD_OPTIMIZATIONS)
|
||||
if(MSVC)
|
||||
# NOTE: RelWithDebInfo and Release use O2 (= /Ox /Gl /Gy/ = Og /Oi /Ot /Oy /Ob2 /Gs /GF /Gy) by default,
|
||||
|
||||
+7
-1
@@ -24,13 +24,19 @@ requirements:
|
||||
- swig >=3.0.9
|
||||
- libboost
|
||||
- icu
|
||||
- xmlschema
|
||||
- lxml
|
||||
- numpy
|
||||
run:
|
||||
- libgcc # [osx]
|
||||
- python
|
||||
- oce ==0.18.3
|
||||
- libboost
|
||||
- icu
|
||||
|
||||
- xmlschema
|
||||
- lxml
|
||||
- numpy
|
||||
|
||||
about:
|
||||
home: http://ifcopenshell.org
|
||||
license: LGPL
|
||||
|
||||
+33
-3
@@ -27,6 +27,10 @@ ifeq ($(PYVERSION), py39)
|
||||
PYLIBDIR:=python3.9
|
||||
PYNUMBER:=39
|
||||
endif
|
||||
ifeq ($(PYVERSION), py310)
|
||||
PYLIBDIR:=python3.10
|
||||
PYNUMBER:=31
|
||||
endif
|
||||
|
||||
ifeq ($(PLATFORM), linux)
|
||||
ifeq ($(PYVERSION), py37)
|
||||
@@ -41,6 +45,12 @@ EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/linux-64/ei
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/linux-64/boost-1.74.0-py39h5472131_3.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/19/d9/a69c6aff5673554df48120565a14a50eaa41d29ae03b02faa0b023666318/lxml-4.6.3-cp39-cp39-manylinux2014_x86_64.whl
|
||||
endif
|
||||
ifeq ($(PYVERSION), py310)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.8.0/download/linux-64/hpp-fcl-1.8.0-py310hdaf7e41_1.tar.bz2
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.11/download/linux-64/eigenpy-2.6.11-py310hf3e5c9c_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/linux-64/boost-1.74.0-py310h7c3ba0c_5.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/25/1e/19b46d8e8881fe0df2e20945d51919eeb1817836d62a90efa8506530e45c/lxml-4.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl
|
||||
endif
|
||||
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/linux-64/assimp-5.0.1-hedfc422_6.tar.bz2
|
||||
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.7/download/linux-64/octomap-1.9.7-h4bd325d_0.tar.bz2
|
||||
ZLIB_URL:=https://anaconda.org/conda-forge/zlib/1.2.11/download/linux-64/zlib-1.2.11-h516909a_1010.tar.bz2
|
||||
@@ -59,6 +69,12 @@ EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/osx-64/eige
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/osx-64/boost-1.74.0-py39ha641261_3.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/b8/74/a71f7ad72e8db54ce899efab84507b801660750cbbfa6a39e6717557d36a/lxml-4.6.3-cp39-cp39-macosx_10_9_x86_64.whl
|
||||
endif
|
||||
ifeq ($(PYVERSION), py310)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.8.0/download/osx-64/hpp-fcl-1.8.0-py310h651ac30_1.tar.bz2
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.11/download/osx-64/eigenpy-2.6.11-py310hc03097c_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/osx-64/boost-1.74.0-py310h509978a_5.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/a1/44/17b7dac7a18807d30e2fe10c3328c152808f5464565e230bfd0e77f178c6/lxml-4.8.0-cp310-cp310-macosx_10_15_x86_64.whl
|
||||
endif
|
||||
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/osx-64/assimp-5.0.1-h1224e73_6.tar.bz2
|
||||
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.7/download/osx-64/octomap-1.9.7-h940c156_0.tar.bz2
|
||||
ZLIB_URL:=https://anaconda.org/conda-forge/zlib/1.2.11/download/osx-64/zlib-1.2.11-h7795811_1010.tar.bz2
|
||||
@@ -77,6 +93,12 @@ EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/win-64/eige
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/win-64/boost-1.74.0-py39hefe7e4c_3.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/72/d4/426ecb8849c47c3e370c87aa0ac05d85768df917ffea27fcd6686a5e6495/lxml-4.6.3-cp39-cp39-win_amd64.whl
|
||||
endif
|
||||
ifeq ($(PYVERSION), py310)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.8.0/download/win-64/hpp-fcl-1.8.0-py310hc5a3c62_1.tar.bz2
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.11/download/win-64/eigenpy-2.6.11-py310hbd43d28_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/win-64/boost-1.74.0-py310hc781a3c_5.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/f6/71/65c80a4caa1617a4c6e8fe1500cebb179db96232e2f623bfe6a1f4294e39/lxml-4.8.0-cp310-cp310-win_amd64.whl
|
||||
endif
|
||||
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/win-64/assimp-5.0.1-hc2aa0de_6.tar.bz2
|
||||
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.7/download/win-64/octomap-1.9.7-h5362a0b_0.tar.bz2
|
||||
ZLIB_URL:=https://anaconda.org/conda-forge/zlib/1.2.11/download/win-64/zlib-1.2.11-h62dcd97_1010.tar.bz2
|
||||
@@ -93,9 +115,9 @@ endif
|
||||
cp -r blenderbim/* dist/blenderbim/
|
||||
|
||||
# Provides IfcOpenShell Python functionality
|
||||
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcblender-python-$(PYNUMBER)-v0.7.0-b9ea741-$(PLATFORM)64.zip
|
||||
cd dist/working && unzip ifcblender*
|
||||
cp -r dist/working/io_import_scene_ifc/ifcopenshell dist/blenderbim/libs/site/packages/
|
||||
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-$(PYNUMBER)-v0.7.0-883b8a5-$(PLATFORM)64.zip
|
||||
cd dist/working && unzip ifcopenshell-python*
|
||||
cp -r dist/working/ifcopenshell dist/blenderbim/libs/site/packages/
|
||||
|
||||
# See bug #812
|
||||
cd dist/working && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.7.0/src/blenderbim/occ_utils.py
|
||||
@@ -488,6 +510,14 @@ endif
|
||||
cd dist/working/owlrl-6.0.2/ && cp -r owlrl ../../blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by brickschema
|
||||
# This is a bit of a dodgy one, it should be provided by setuptools which Blender doesn't ship.
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://blenderbim.org/builds/pkg_resources.tar.gz
|
||||
cd dist/working && tar -xzvf pkg_resources*
|
||||
cd dist/working/ && cp -r pkg_resources ../blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
cd dist/blenderbim && sed -i "s/999999/$(VERSION)/" __init__.py
|
||||
cd dist && zip -r blenderbim-$(VERSION)-$(PYVERSION)-$(PLATFORM).zip ./*
|
||||
rm -rf dist/blenderbim
|
||||
|
||||
@@ -68,6 +68,9 @@ modules = {
|
||||
"covetool": None,
|
||||
"augin": None,
|
||||
"debug": None,
|
||||
# Uncomment this line to enable loading of the demo module. Happy hacking!
|
||||
# The name "demo" must correlate to a folder name in `bim/module/`.
|
||||
# "demo": None,
|
||||
}
|
||||
|
||||
|
||||
@@ -76,17 +79,18 @@ for name in modules.keys():
|
||||
|
||||
|
||||
classes = [
|
||||
operator.OpenUri,
|
||||
operator.SelectDataDir,
|
||||
operator.SelectSchemaDir,
|
||||
operator.SelectIfcFile,
|
||||
operator.OpenUpstream,
|
||||
operator.AddSectionPlane,
|
||||
operator.RemoveSectionPlane,
|
||||
operator.ReloadIfcFile,
|
||||
operator.AddIfcFile,
|
||||
operator.RemoveIfcFile,
|
||||
operator.BIM_OT_add_section_plane,
|
||||
operator.BIM_OT_remove_section_plane,
|
||||
operator.ConfigureVisibility,
|
||||
operator.OpenUpstream,
|
||||
operator.OpenUri,
|
||||
operator.ReloadIfcFile,
|
||||
operator.RemoveIfcFile,
|
||||
operator.SelectDataDir,
|
||||
operator.SelectIfcFile,
|
||||
operator.SelectSchemaDir,
|
||||
operator.SelectURIAttribute,
|
||||
prop.StrProperty,
|
||||
prop.ObjProperty,
|
||||
prop.Attribute,
|
||||
@@ -102,22 +106,22 @@ classes = [
|
||||
ui.BIM_UL_generic,
|
||||
ui.BIM_UL_topics,
|
||||
ui.BIM_ADDON_preferences,
|
||||
#scene panel groups -->
|
||||
# Scene panel groups
|
||||
ui.BIM_PT_project_info,
|
||||
ui.BIM_PT_project_setup,
|
||||
ui.BIM_PT_utilities,
|
||||
ui.BIM_PT_collaboration,
|
||||
ui.BIM_PT_geometry,
|
||||
ui.BIM_PT_4D5D,
|
||||
ui.BIM_PT_structural,
|
||||
ui.BIM_PT_services,
|
||||
ui.BIM_PT_misc,
|
||||
ui.BIM_PT_structural,
|
||||
ui.BIM_PT_4D5D,
|
||||
ui.BIM_PT_quality_control,
|
||||
#object panel groups -->
|
||||
ui.BIM_PT_integrations,
|
||||
# Object panel groups
|
||||
ui.BIM_PT_object_metadata,
|
||||
ui.BIM_PT_geometry_object,
|
||||
ui.BIM_PT_services_object,
|
||||
ui.BIM_PT_utilities_object,
|
||||
ui.BIM_PT_misc_object
|
||||
ui.BIM_PT_misc_object,
|
||||
]
|
||||
|
||||
for mod in modules.values():
|
||||
|
||||
@@ -1,10 +1,44 @@
|
||||
<link href="jsgantt.css" rel="stylesheet" type="text/css"/>
|
||||
<script src="jsgantt.js" type="text/javascript"></script>
|
||||
<style>
|
||||
.gtaskcellwkend,
|
||||
.gtaskcellcurrent,
|
||||
.gminorheadingwkend {
|
||||
background-color: #e1e1e1;
|
||||
}
|
||||
|
||||
.gitemhighlight td {
|
||||
background-color: #ffdaaa;
|
||||
}
|
||||
|
||||
.gtaskblue {
|
||||
background: #4281A4;
|
||||
}
|
||||
|
||||
.gtaskred {
|
||||
background: #C1666B;
|
||||
}
|
||||
|
||||
.gtaskgreen {
|
||||
background: #48A9A6;
|
||||
}
|
||||
|
||||
.gtaskyellow {
|
||||
background: #D4B483;
|
||||
}
|
||||
|
||||
.gmainleft {
|
||||
overflow: visible;
|
||||
flex: 0 1 auto;
|
||||
}
|
||||
|
||||
</style>
|
||||
<a href="#print" id="print">Print Mode</a>
|
||||
<div style="position:relative" class="gantt" id="GanttChartDIV"></div>
|
||||
<script type="text/javascript">
|
||||
var g = new JSGantt.GanttChart(document.getElementById('GanttChartDIV'), 'day');
|
||||
g.setOptions({
|
||||
vCaptionType: 'Complete', // Set to Show Caption : None,Caption,Resource,Duration,Complete,
|
||||
vCaptionType: 'Caption', // Set to Show Caption : None,Caption,Resource,Duration,Complete,
|
||||
vQuarterColWidth: 36,
|
||||
vDateTaskDisplayFormat: 'day dd month yyyy', // Shown in tool tip box
|
||||
vDayMajorDateDisplayFormat: 'mon yyyy - Week ww',// Set format to dates in the "Major" header of the "Day" view
|
||||
@@ -14,7 +48,16 @@ g.setOptions({
|
||||
vShowEndWeekDate: 0, // Show/Hide the date for the last day of the week in header for daily
|
||||
vUseSingleCell: 10000, // Set the threshold cell per table row (Helps performance for large data.
|
||||
vFormatArr: ['Day', 'Week', 'Month', 'Quarter'], // Even with setUseSingleCell using Hour format on such a large chart can cause issues in some browsers,
|
||||
vTotalHeight: 1000,
|
||||
vShowRes: false, // Disable the resource column.
|
||||
vShowComp: false, // Disable the completion column.
|
||||
vShowDur: false, // Disable the duration column, because jsgantt doesn't calculate durations the way we want.
|
||||
vAdditionalHeaders: {ifcduration: {title: 'Duration'}},
|
||||
vUseToolTip: false, // Disable tooltips.
|
||||
vTotalHeight: 900,
|
||||
});
|
||||
document.getElementById('print').addEventListener('click', function() {
|
||||
g.setTotalHeight("");
|
||||
g.Draw();
|
||||
});
|
||||
var json_data = `
|
||||
{{{json_data}}}
|
||||
|
||||
@@ -28,6 +28,7 @@ import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.placement
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.geometry
|
||||
import blenderbim.core.aggregate
|
||||
import blenderbim.core.spatial
|
||||
import blenderbim.core.style
|
||||
@@ -47,17 +48,17 @@ class IfcExporter:
|
||||
self.sync_deletions()
|
||||
self.sync_all_objects()
|
||||
self.sync_edited_objects()
|
||||
extension = self.ifc_export_settings.output_file.split(".")[-1]
|
||||
extension = self.ifc_export_settings.output_file.split(".")[-1].lower()
|
||||
if extension == "ifczip":
|
||||
with tempfile.TemporaryDirectory() as unzipped_path:
|
||||
filename, ext = os.path.splitext(os.path.basename(self.ifc_export_settings.output_file))
|
||||
tmp_name = "{}.ifc".format(filename)
|
||||
tmp_file = os.path.join(unzipped_path, tmp_name)
|
||||
tmp_filename = f"{filename}.ifc"
|
||||
tmp_file = os.path.join(unzipped_path, tmp_filename)
|
||||
self.file.write(tmp_file)
|
||||
with zipfile.ZipFile(
|
||||
self.ifc_export_settings.output_file, mode="w", compression=zipfile.ZIP_DEFLATED, compresslevel=9
|
||||
) as zf:
|
||||
zf.write(tmp_file)
|
||||
zf.write(tmp_file, arcname=tmp_filename)
|
||||
elif extension == "ifc":
|
||||
self.file.write(self.ifc_export_settings.output_file)
|
||||
elif extension == "ifcjson":
|
||||
@@ -179,6 +180,7 @@ class IfcExporter:
|
||||
self.sync_object_placement(grid_obj)
|
||||
if grid_obj.matrix_world != obj.matrix_world:
|
||||
bpy.ops.bim.update_representation(obj=obj.name)
|
||||
tool.Geometry.record_object_position(obj)
|
||||
|
||||
def get_application_name(self):
|
||||
return "BlenderBIM"
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
import bpy
|
||||
import json
|
||||
import math
|
||||
import zipfile
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.attribute
|
||||
from mathutils import geometry
|
||||
@@ -47,6 +48,9 @@ def draw_attribute(attribute, layout, copy_operator=None):
|
||||
if copy_operator:
|
||||
op = layout.operator(f"{copy_operator}", text="", icon="COPYDOWN")
|
||||
op.name = attribute.name
|
||||
if attribute.is_uri:
|
||||
op = layout.operator("bim.select_uri_attribute", text="", icon="FILE_FOLDER")
|
||||
op.data_path = attribute.path_from_id("string_value")
|
||||
|
||||
|
||||
def import_attributes(ifc_class, props, data, callback=None):
|
||||
@@ -77,6 +81,8 @@ def import_attribute(attribute, props, data, callback=None):
|
||||
props.remove(len(props) - 1)
|
||||
elif data_type == "string":
|
||||
new.string_value = "" if new.is_null else data[attribute.name()]
|
||||
if attribute.type_of_attribute().declared_type().name() == "IfcURIReference":
|
||||
new.is_uri = True
|
||||
elif data_type == "boolean":
|
||||
new.bool_value = False if new.is_null else data[attribute.name()]
|
||||
elif data_type == "integer":
|
||||
@@ -99,35 +105,43 @@ def export_attributes(props, callback=None):
|
||||
return attributes
|
||||
|
||||
|
||||
class IFCHeaderSpecs:
|
||||
class IfcHeaderExtractor:
|
||||
def __init__(self, filepath: str):
|
||||
# According to https://www.steptools.com/stds/step/IS_final_p21e3.html#clause-8
|
||||
self.description = ""
|
||||
self.implementation_level = ""
|
||||
self.name = ""
|
||||
self.time_stamp = ""
|
||||
self.author = ""
|
||||
self.organization = ""
|
||||
self.preprocessor_version = ""
|
||||
self.originating_system = ""
|
||||
self.authorization = ""
|
||||
self.schema_name = ""
|
||||
with open(filepath) as ifc_file:
|
||||
max_lines_to_parse = 50
|
||||
for _ in range(max_lines_to_parse):
|
||||
line = next(ifc_file)
|
||||
if line.startswith("FILE_DESCRIPTION"):
|
||||
for i, part in enumerate(line.split("'")):
|
||||
if i == 1:
|
||||
self.description = part
|
||||
elif i == 3:
|
||||
self.implementation_level = part
|
||||
elif line.startswith("FILE_NAME"):
|
||||
for i, part in enumerate(line.split("'")):
|
||||
if i == 1:
|
||||
self.name = part
|
||||
elif i == 3:
|
||||
self.time_stamp = part
|
||||
elif line.startswith("FILE_SCHEMA"):
|
||||
self.schema_name = line.split("'")[1]
|
||||
break
|
||||
self.filepath = filepath
|
||||
|
||||
def extract(self):
|
||||
extension = self.filepath.split(".")[-1]
|
||||
if extension.lower() == "ifc":
|
||||
with open(self.filepath) as ifc_file:
|
||||
return self.extract_ifc_spf(ifc_file)
|
||||
elif extension.lower() == "ifczip":
|
||||
return self.extract_ifc_zip()
|
||||
|
||||
def extract_ifc_spf(self, ifc_file):
|
||||
# https://www.steptools.com/stds/step/IS_final_p21e3.html#clause-8
|
||||
data = {}
|
||||
max_lines_to_parse = 50
|
||||
for _ in range(max_lines_to_parse):
|
||||
line = next(ifc_file)
|
||||
if isinstance(line, bytes):
|
||||
line = line.decode("utf-8")
|
||||
if line.startswith("FILE_DESCRIPTION"):
|
||||
for i, part in enumerate(line.split("'")):
|
||||
if i == 1:
|
||||
data["description"] = part
|
||||
elif i == 3:
|
||||
data["implementation_level"] = part
|
||||
elif line.startswith("FILE_NAME"):
|
||||
for i, part in enumerate(line.split("'")):
|
||||
if i == 1:
|
||||
data["name"] = part
|
||||
elif i == 3:
|
||||
data["time_stamp"] = part
|
||||
elif line.startswith("FILE_SCHEMA"):
|
||||
data["schema_name"] = line.split("'")[1]
|
||||
break
|
||||
return data
|
||||
|
||||
def extract_ifc_zip(self):
|
||||
archive = zipfile.ZipFile(self.filepath, "r")
|
||||
return self.extract_ifc_spf(archive.open(archive.filelist[0]))
|
||||
|
||||
@@ -186,6 +186,23 @@ class IfcStore:
|
||||
data["guid"] = element.GlobalId
|
||||
IfcStore.commit_link_element(data)
|
||||
|
||||
@staticmethod
|
||||
def delete_element(element):
|
||||
IfcStore.deleted_ids.add(element.id())
|
||||
if IfcStore.history:
|
||||
data = {"id": element.id()}
|
||||
IfcStore.history[-1]["operations"].append(
|
||||
{"rollback": IfcStore.rollback_delete_element, "commit": IfcStore.commit_delete_element, "data": data}
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def rollback_delete_element(data):
|
||||
IfcStore.deleted_ids.remove(data["id"])
|
||||
|
||||
@staticmethod
|
||||
def commit_delete_element(data):
|
||||
IfcStore.deleted_ids.add(data["id"])
|
||||
|
||||
@staticmethod
|
||||
def link_element(element, obj):
|
||||
IfcStore.id_map[element.id()] = obj
|
||||
|
||||
@@ -16,11 +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 re
|
||||
import bpy
|
||||
import time
|
||||
import bmesh
|
||||
import shutil
|
||||
import logging
|
||||
import threading
|
||||
import mathutils
|
||||
import numpy as np
|
||||
@@ -32,6 +34,7 @@ import ifcopenshell.util.element
|
||||
import ifcopenshell.util.selector
|
||||
import ifcopenshell.util.geolocation
|
||||
import blenderbim.tool as tool
|
||||
from itertools import chain, accumulate
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.drawing.prop import get_diagram_scales
|
||||
|
||||
@@ -109,7 +112,7 @@ class MaterialCreator:
|
||||
|
||||
def parse_representation(self, representation):
|
||||
has_parsed = False
|
||||
representation_items = self.resolve_mapped_representation_items(representation)
|
||||
representation_items = self.resolve_all_representation_items(representation)
|
||||
for item in representation_items:
|
||||
if self.parse_representation_item(item):
|
||||
has_parsed = True
|
||||
@@ -143,13 +146,12 @@ class MaterialCreator:
|
||||
]
|
||||
self.mesh.polygons.foreach_set("material_index", material_index)
|
||||
|
||||
def resolve_mapped_representation_items(self, representation):
|
||||
def resolve_all_representation_items(self, representation):
|
||||
items = []
|
||||
for item in representation.Items:
|
||||
if item.is_a("IfcMappedItem"):
|
||||
items.extend(item.MappingSource.MappedRepresentation.Items)
|
||||
else:
|
||||
items.append(item)
|
||||
items.append(item)
|
||||
return items
|
||||
|
||||
|
||||
@@ -161,6 +163,8 @@ class IfcImporter:
|
||||
self.settings = ifcopenshell.geom.settings()
|
||||
self.settings.set_deflection_tolerance(self.ifc_import_settings.deflection_tolerance)
|
||||
self.settings.set_angular_tolerance(self.ifc_import_settings.angular_tolerance)
|
||||
# See https://github.com/IfcOpenShell/IfcOpenShell/issues/2053
|
||||
# self.settings.set(self.settings.STRICT_TOLERANCE, True)
|
||||
self.settings_native = ifcopenshell.geom.settings()
|
||||
self.settings_native.set(self.settings_native.INCLUDE_CURVES, True)
|
||||
self.settings_2d = ifcopenshell.geom.settings()
|
||||
@@ -280,6 +284,14 @@ class IfcImporter:
|
||||
for c in self.file.by_type("IfcGeometricRepresentationSubContext")
|
||||
if c.ContextIdentifier in ["Body", "Facetation"]
|
||||
]
|
||||
# Ideally, all representations should be in a subcontext, but some BIM programs don't do this correctly
|
||||
self.body_contexts.extend(
|
||||
[
|
||||
c.id()
|
||||
for c in self.file.by_type("IfcGeometricRepresentationContext", include_subtypes=False)
|
||||
if c.ContextType == "Model"
|
||||
]
|
||||
)
|
||||
if self.body_contexts:
|
||||
self.settings.set_context_ids(self.body_contexts)
|
||||
# Annotation is to accommodate broken Revit files
|
||||
@@ -346,16 +358,17 @@ class IfcImporter:
|
||||
# FacetedBreps (without voids) are meshes. See #841.
|
||||
# Commented out as seems currently too slow.
|
||||
# if self.is_native_faceted_brep(representations):
|
||||
# self.native_data[element.GlobalId] = {
|
||||
# "representations": representations,
|
||||
# "representation": self.get_body_representation(element.Representation.Representations),
|
||||
# "type": "IfcFacetedBrep",
|
||||
# }
|
||||
# return True
|
||||
# self.native_data[element.GlobalId] = {
|
||||
# "representations": representations,
|
||||
# "representation": self.get_body_representation(element.Representation.Representations),
|
||||
# "type": "IfcFacetedBrep",
|
||||
# }
|
||||
# return True
|
||||
|
||||
def is_native_swept_disk_solid(self, representations):
|
||||
for representation in representations:
|
||||
if len(representation["raw"].Items) == 1 and representation["raw"].Items[0].is_a("IfcSweptDiskSolid"):
|
||||
items = representation["raw"].Items or [] # Be forgiving of invalid IFCs because Revit :(
|
||||
if len(items) == 1 and items[0].is_a("IfcSweptDiskSolid"):
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -452,7 +465,9 @@ class IfcImporter:
|
||||
def apply_blender_offset_to_matrix_world(self, obj, matrix):
|
||||
props = bpy.context.scene.BIMGeoreferenceProperties
|
||||
if props.has_blender_offset:
|
||||
if self.is_point_far_away((matrix[0, 3], matrix[1, 3], matrix[2, 3])):
|
||||
if obj.data and obj.data.get("has_cartesian_point_offset", None):
|
||||
obj.BIMObjectProperties.blender_offset_type = "CARTESIAN_POINT"
|
||||
elif self.is_point_far_away((matrix[0, 3], matrix[1, 3], matrix[2, 3])):
|
||||
obj.BIMObjectProperties.blender_offset_type = "OBJECT_PLACEMENT"
|
||||
matrix = ifcopenshell.util.geolocation.global2local(
|
||||
matrix,
|
||||
@@ -462,8 +477,6 @@ class IfcImporter:
|
||||
float(props.blender_x_axis_abscissa),
|
||||
float(props.blender_x_axis_ordinate),
|
||||
)
|
||||
else:
|
||||
obj.BIMObjectProperties.blender_offset_type = "CARTESIAN_POINT"
|
||||
|
||||
if self.ifc_import_settings.should_offset_model:
|
||||
matrix[0, 3] += self.ifc_import_settings.model_offset_coordinates[0]
|
||||
@@ -562,7 +575,6 @@ class IfcImporter:
|
||||
def create_native_elements(self):
|
||||
total = 0
|
||||
checkpoint = time.time()
|
||||
bm = bmesh.new()
|
||||
for element in self.native_elements:
|
||||
total += 1
|
||||
if total % 250 == 0:
|
||||
@@ -584,14 +596,6 @@ class IfcImporter:
|
||||
mesh.name = mesh_name
|
||||
self.meshes[mesh_name] = mesh
|
||||
self.create_product(element, mesh=mesh)
|
||||
if native_data["type"] == "IfcFacetedBrep":
|
||||
# The current implementation doesn't reuse vertices, so we weld it after assigning materials.
|
||||
# This welding isn't true to the representation, but is easy and seems inexpensive.
|
||||
bm.from_mesh(mesh)
|
||||
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.001)
|
||||
bm.to_mesh(mesh)
|
||||
bm.clear()
|
||||
bm.free()
|
||||
print("Done creating geometry")
|
||||
|
||||
def create_spatial_elements(self):
|
||||
@@ -853,78 +857,138 @@ class IfcImporter:
|
||||
|
||||
def create_native_faceted_brep(self, element, mesh_name):
|
||||
# TODO: georeferencing?
|
||||
# Note: to make this algorithm simpler (it's already confusing) we don't reuse / weld verts
|
||||
# co [x y z x y z x y z ...]
|
||||
# vertex_index [i i i i i ...]
|
||||
# loop_start [0 3 6 9 ...] (for tris)
|
||||
# loop_total [3 3 3 3 ...] (for tris)
|
||||
co = []
|
||||
vertex_index = []
|
||||
loop_start = []
|
||||
loop_total = []
|
||||
total_verts = 0
|
||||
total_polygons = 0
|
||||
materials = []
|
||||
material_ids = []
|
||||
item_index = 0
|
||||
self.mesh_data = {
|
||||
"co": [],
|
||||
"vertex_index": [],
|
||||
"loop_start": [],
|
||||
"loop_total": [],
|
||||
"total_verts": 0,
|
||||
"total_polygons": 0,
|
||||
"materials": [],
|
||||
"material_ids": [],
|
||||
}
|
||||
|
||||
for representation in self.native_data[element.GlobalId]["representations"]:
|
||||
for item in representation["raw"].Items:
|
||||
# TODO: if I reimplement native faceted breps, recheck this material implementation
|
||||
materials.append(self.get_representation_item_material_name(item) or "NULLMAT")
|
||||
mesh = item.get_info_2(recursive=True) # See bug #841
|
||||
total_item_polygons = 0
|
||||
for face in mesh["Outer"]["CfsFaces"]:
|
||||
# Blender cannot handle faces with holes.
|
||||
if len(face["Bounds"]) > 1:
|
||||
inner_bounds = []
|
||||
for bound in face["Bounds"]:
|
||||
if bound["type"] == "IfcFaceOuterBound":
|
||||
outer_bound = [[p["Coordinates"] for p in bound["Bound"]["Polygon"]]]
|
||||
else:
|
||||
inner_bounds.append([p["Coordinates"] for p in bound["Bound"]["Polygon"]])
|
||||
points = outer_bound[0].copy()
|
||||
[points.extend(p) for p in inner_bounds]
|
||||
tessellated_polygons = mathutils.geometry.tessellate_polygon(outer_bound + inner_bounds)
|
||||
tessellated_faces = [({"Coordinates": points[pi]} for pi in t) for t in tessellated_polygons]
|
||||
else:
|
||||
tessellated_faces = [face["Bounds"][0]["Bound"]["Polygon"]]
|
||||
for representation in element.Representation.Representations:
|
||||
if representation.ContextOfItems.id() not in self.body_contexts:
|
||||
continue
|
||||
self.convert_representation(representation)
|
||||
|
||||
for tessellated_face in tessellated_faces:
|
||||
loop_start.append(total_verts)
|
||||
loop_count = 0
|
||||
total_polygons += 1
|
||||
total_item_polygons += 1
|
||||
for point in tessellated_face:
|
||||
co.extend(
|
||||
representation["matrix"]
|
||||
@ mathutils.Vector((c * self.unit_scale for c in point["Coordinates"]))
|
||||
)
|
||||
total_verts += 1
|
||||
loop_count += 1
|
||||
loop_total.append(loop_count)
|
||||
vertex_index = range(0, total_verts)
|
||||
if materials[item_index] == "NULLMAT":
|
||||
# Magic number -1 represents no material, until this has a better approach
|
||||
material_ids += [-1] * total_item_polygons
|
||||
else:
|
||||
material_ids += [item_index] * total_item_polygons
|
||||
item_index += 1
|
||||
mesh = bpy.data.meshes.new("Tester")
|
||||
|
||||
mesh.vertices.add(total_verts)
|
||||
mesh.vertices.foreach_set("co", co)
|
||||
mesh.loops.add(total_verts)
|
||||
mesh.loops.foreach_set("vertex_index", vertex_index)
|
||||
mesh.polygons.add(total_polygons)
|
||||
mesh.polygons.foreach_set("loop_start", loop_start)
|
||||
mesh.polygons.foreach_set("loop_total", loop_total)
|
||||
mesh = bpy.data.meshes.new("Native")
|
||||
mesh.vertices.add(self.mesh_data["total_verts"])
|
||||
mesh.vertices.foreach_set("co", [c * self.unit_scale for c in self.mesh_data["co"]])
|
||||
# mesh.vertices.foreach_set("co", self.mesh_data["co"])
|
||||
mesh.loops.add(len(self.mesh_data["vertex_index"]))
|
||||
mesh.loops.foreach_set("vertex_index", self.mesh_data["vertex_index"])
|
||||
mesh.polygons.add(self.mesh_data["total_polygons"])
|
||||
mesh.polygons.foreach_set("loop_start", self.mesh_data["loop_start"])
|
||||
mesh.polygons.foreach_set("loop_total", self.mesh_data["loop_total"])
|
||||
mesh.update()
|
||||
|
||||
mesh["ios_materials"] = materials
|
||||
mesh["ios_material_ids"] = material_ids
|
||||
mesh["ios_materials"] = self.mesh_data["materials"]
|
||||
mesh["ios_material_ids"] = self.mesh_data["material_ids"]
|
||||
return mesh
|
||||
|
||||
def convert_representation(self, representation):
|
||||
for item in representation.Items:
|
||||
self.convert_representation_item(item)
|
||||
|
||||
def convert_representation_item(self, item):
|
||||
if item.is_a("IfcMappedItem"):
|
||||
# mapping_target = matrix
|
||||
self.convert_representation(item.MappingSource.MappedRepresentation)
|
||||
elif item.is_a() == "IfcFacetedBrep":
|
||||
self.convert_representation_item_faceted_brep(item)
|
||||
|
||||
def convert_representation_item_faceted_brep(self, item):
|
||||
# On a few occasions, we flatten a list. This seems to be the most efficient way to do it.
|
||||
# https://stackoverflow.com/questions/20112776/how-do-i-flatten-a-list-of-lists-nested-lists
|
||||
mesh = item.get_info_2(recursive=True)
|
||||
|
||||
# For huge face sets it might be better to do a "flatmap" instead of sum()
|
||||
# bounds = sum((f["Bounds"] for f in mesh["Outer"]["CfsFaces"] if len(f["Bounds"]) == 1), ())
|
||||
bounds = tuple(chain.from_iterable(f["Bounds"] for f in mesh["Outer"]["CfsFaces"] if len(f["Bounds"]) == 1))
|
||||
# Here are some untested alternatives, are they faster?
|
||||
# bounds = tuple((f["Bounds"] for f in mesh["Outer"]["CfsFaces"] if len(f["Bounds"]) == 1))[0]
|
||||
# bounds = chain.from_iterable(f["Bounds"] for f in mesh["Outer"]["CfsFaces"] if len(f["Bounds"]) == 1)
|
||||
|
||||
polygons = [[(p["id"], p["Coordinates"]) for p in b["Bound"]["Polygon"]] for b in bounds]
|
||||
|
||||
for face in mesh["Outer"]["CfsFaces"]:
|
||||
# Blender cannot handle faces with holes.
|
||||
if len(face["Bounds"]) > 1:
|
||||
inner_bounds = []
|
||||
inner_bound_point_ids = []
|
||||
for bound in face["Bounds"]:
|
||||
if bound["type"] == "IfcFaceOuterBound":
|
||||
outer_bound = [[p["Coordinates"] for p in bound["Bound"]["Polygon"]]]
|
||||
outer_bound_point_ids = [[p["id"] for p in bound["Bound"]["Polygon"]]]
|
||||
else:
|
||||
inner_bounds.append([p["Coordinates"] for p in bound["Bound"]["Polygon"]])
|
||||
inner_bound_point_ids.append([p["id"] for p in bound["Bound"]["Polygon"]])
|
||||
points = outer_bound[0].copy()
|
||||
[points.extend(p) for p in inner_bounds]
|
||||
point_ids = outer_bound_point_ids[0].copy()
|
||||
[point_ids.extend(p) for p in inner_bound_point_ids]
|
||||
|
||||
tessellated_polygons = mathutils.geometry.tessellate_polygon(outer_bound + inner_bounds)
|
||||
polygons.extend([[(point_ids[pi], points[pi]) for pi in t] for t in tessellated_polygons])
|
||||
|
||||
# Clever vertex welding algorithm by Thomas Krijnen. See #841.
|
||||
|
||||
# by id
|
||||
di0 = {}
|
||||
# by coords
|
||||
di1 = {}
|
||||
|
||||
vertex_index_offset = self.mesh_data["total_verts"]
|
||||
|
||||
def lookup(id_coords):
|
||||
idx = di0.get(id_coords[0])
|
||||
if idx is None:
|
||||
idx = di1.get(id_coords[1])
|
||||
if idx is None:
|
||||
l = len(di0)
|
||||
di0[id_coords[0]] = l
|
||||
di1[id_coords[1]] = l
|
||||
return l + vertex_index_offset
|
||||
else:
|
||||
return idx + vertex_index_offset
|
||||
else:
|
||||
return idx + vertex_index_offset
|
||||
|
||||
mapped_polygons = [list(map(lookup, p)) for p in polygons]
|
||||
|
||||
self.mesh_data["vertex_index"].extend(chain.from_iterable(mapped_polygons))
|
||||
|
||||
# Flattened vertex coords
|
||||
self.mesh_data["co"].extend(chain.from_iterable(di1.keys()))
|
||||
self.mesh_data["total_verts"] += len(di1.keys())
|
||||
loop_total = [len(p) for p in mapped_polygons]
|
||||
total_polygons = len(mapped_polygons)
|
||||
self.mesh_data["total_polygons"] += total_polygons
|
||||
|
||||
self.mesh_data["materials"].append(self.get_representation_item_material_name(item) or "NULLMAT")
|
||||
material_index = len(self.mesh_data["materials"]) - 1
|
||||
if self.mesh_data["materials"][material_index] == "NULLMAT":
|
||||
# Magic number -1 represents no material, until this has a better approach
|
||||
self.mesh_data["material_ids"] += [-1] * total_polygons
|
||||
else:
|
||||
self.mesh_data["material_ids"] += [material_index] * total_polygons
|
||||
|
||||
if self.mesh_data["loop_start"]:
|
||||
loop_start_offset = self.mesh_data["loop_start"][-1] + self.mesh_data["loop_total"][-1]
|
||||
else:
|
||||
loop_start_offset = 0
|
||||
|
||||
loop_start = [loop_start_offset] + [loop_start_offset + i for i in list(accumulate(loop_total[0:-1]))]
|
||||
self.mesh_data["loop_total"].extend(loop_total)
|
||||
self.mesh_data["loop_start"].extend(loop_start)
|
||||
# list(di1.keys())
|
||||
|
||||
def create_native_swept_disk_solid(self, element, mesh_name):
|
||||
# TODO: georeferencing?
|
||||
curve = bpy.data.curves.new(mesh_name, type="CURVE")
|
||||
@@ -1242,18 +1306,168 @@ class IfcImporter:
|
||||
|
||||
blender_material.BIMMaterialProperties.ifc_style_id = style.id()
|
||||
self.material_creator.styles[style.id()] = blender_material
|
||||
|
||||
rendering_style = None
|
||||
texture_style = None
|
||||
|
||||
for surface_style in style.Styles:
|
||||
if surface_style.is_a("IfcSurfaceStyleShading"):
|
||||
alpha = 1.0
|
||||
# Transparency was added in IFC4
|
||||
if hasattr(surface_style, "Transparency") and surface_style.Transparency:
|
||||
alpha = 1 - surface_style.Transparency
|
||||
blender_material.diffuse_color = (
|
||||
surface_style.SurfaceColour.Red,
|
||||
surface_style.SurfaceColour.Green,
|
||||
surface_style.SurfaceColour.Blue,
|
||||
alpha,
|
||||
if surface_style.is_a() == "IfcSurfaceStyleShading":
|
||||
self.create_surface_style_shading(blender_material, surface_style)
|
||||
elif surface_style.is_a("IfcSurfaceStyleRendering"):
|
||||
rendering_style = surface_style
|
||||
self.create_surface_style_rendering(blender_material, surface_style)
|
||||
elif surface_style.is_a("IfcSurfaceStyleWithTextures"):
|
||||
texture_style = surface_style
|
||||
|
||||
if rendering_style and texture_style:
|
||||
self.create_surface_style_with_textures(blender_material, rendering_style, texture_style)
|
||||
|
||||
def create_surface_style_shading(self, blender_material, surface_style):
|
||||
alpha = 1.0
|
||||
# Transparency was added in IFC4
|
||||
if hasattr(surface_style, "Transparency") and surface_style.Transparency:
|
||||
alpha = 1 - surface_style.Transparency
|
||||
blender_material.diffuse_color = (
|
||||
surface_style.SurfaceColour.Red,
|
||||
surface_style.SurfaceColour.Green,
|
||||
surface_style.SurfaceColour.Blue,
|
||||
alpha,
|
||||
)
|
||||
|
||||
def create_surface_style_rendering(self, blender_material, surface_style):
|
||||
self.create_surface_style_shading(blender_material, surface_style)
|
||||
if surface_style.ReflectanceMethod in ["PHYSICAL", "NOTDEFINED"]:
|
||||
blender_material.use_nodes = True
|
||||
bsdf = blender_material.node_tree.nodes["Principled BSDF"]
|
||||
if surface_style.DiffuseColour and surface_style.DiffuseColour.is_a("IfcColourRgb"):
|
||||
bsdf.inputs["Base Color"].default_value = (
|
||||
surface_style.DiffuseColour.Red,
|
||||
surface_style.DiffuseColour.Green,
|
||||
surface_style.DiffuseColour.Blue,
|
||||
1,
|
||||
)
|
||||
if surface_style.SpecularColour and surface_style.SpecularColour.is_a("IfcNormalisedRatioMeasure"):
|
||||
bsdf.inputs["Metallic"].default_value = surface_style.SpecularColour.wrappedValue
|
||||
if surface_style.SpecularHighlight and surface_style.SpecularHighlight.is_a("IfcSpecularRoughness"):
|
||||
bsdf.inputs["Roughness"].default_value = surface_style.SpecularHighlight.wrappedValue
|
||||
if hasattr(surface_style, "Transparency") and surface_style.Transparency:
|
||||
bsdf.inputs["Alpha"].default_value = 1 - surface_style.Transparency
|
||||
blender_material.blend_method = "BLEND"
|
||||
elif surface_style.ReflectanceMethod == "FLAT":
|
||||
blender_material.use_nodes = True
|
||||
|
||||
output = {n.type: n for n in blender_material.node_tree.nodes}.get("OUTPUT_MATERIAL", None)
|
||||
bsdf = blender_material.node_tree.nodes["Principled BSDF"]
|
||||
|
||||
mix = blender_material.node_tree.nodes.new(type="ShaderNodeMixShader")
|
||||
mix.location = bsdf.location
|
||||
blender_material.node_tree.links.new(mix.outputs[0], output.inputs["Surface"])
|
||||
|
||||
blender_material.node_tree.nodes.remove(bsdf)
|
||||
|
||||
lightpath = blender_material.node_tree.nodes.new(type="ShaderNodeLightPath")
|
||||
lightpath.location = mix.location - mathutils.Vector((200, -200))
|
||||
blender_material.node_tree.links.new(lightpath.outputs[0], mix.inputs[0])
|
||||
|
||||
bsdf = blender_material.node_tree.nodes.new(type="ShaderNodeBsdfTransparent")
|
||||
bsdf.location = mix.location - mathutils.Vector((200, 0))
|
||||
blender_material.node_tree.links.new(bsdf.outputs[0], mix.inputs[1])
|
||||
|
||||
rgb = blender_material.node_tree.nodes.new(type="ShaderNodeRGB")
|
||||
rgb.location = mix.location - mathutils.Vector((200, 200))
|
||||
blender_material.node_tree.links.new(rgb.outputs[0], mix.inputs[2])
|
||||
|
||||
if surface_style.DiffuseColour and surface_style.DiffuseColour.is_a("IfcColourRgb"):
|
||||
rgb.outputs[0].default_value = (
|
||||
surface_style.DiffuseColour.Red,
|
||||
surface_style.DiffuseColour.Green,
|
||||
surface_style.DiffuseColour.Blue,
|
||||
1,
|
||||
)
|
||||
|
||||
def create_surface_style_with_textures(self, blender_material, rendering_style, texture_style):
|
||||
for texture in texture_style.Textures:
|
||||
mode = getattr(texture, "Mode", None)
|
||||
node = None
|
||||
|
||||
if texture.is_a("IfcImageTexture"):
|
||||
image_url = texture.URLReference
|
||||
if not os.path.abspath(texture.URLReference) and tool.Ifc.get_path():
|
||||
image_url = os.path.join(os.path.dirname(tool.Ifc.get_path()), texture.URLReference)
|
||||
|
||||
if rendering_style.ReflectanceMethod in ["PHYSICAL", "NOTDEFINED"]:
|
||||
bsdf = blender_material.node_tree.nodes["Principled BSDF"]
|
||||
if mode == "NORMAL":
|
||||
normalmap = blender_material.node_tree.nodes.new(type="ShaderNodeNormalMap")
|
||||
normalmap.location = bsdf.location - mathutils.Vector((200, 0))
|
||||
blender_material.node_tree.links.new(normalmap.outputs[0], bsdf.inputs["Normal"])
|
||||
|
||||
node = blender_material.node_tree.nodes.new(type="ShaderNodeTexImage")
|
||||
node.location = normalmap.location - mathutils.Vector((200, 0))
|
||||
image = bpy.data.images.load(image_url)
|
||||
image.colorspace_settings.name = "Non-Color"
|
||||
node.image = image
|
||||
blender_material.node_tree.links.new(node.outputs[0], normalmap.inputs["Color"])
|
||||
elif mode == "EMISSIVE":
|
||||
output = {n.type: n for n in blender_material.node_tree.nodes}.get("OUTPUT_MATERIAL", None)
|
||||
|
||||
add = blender_material.node_tree.nodes.new(type="ShaderNodeAddShader")
|
||||
add.location = bsdf.location + mathutils.Vector((200, 0))
|
||||
blender_material.node_tree.links.new(bsdf.outputs[0], add.inputs[1])
|
||||
blender_material.node_tree.links.new(add.outputs[0], output.inputs[0])
|
||||
|
||||
emission = blender_material.node_tree.nodes.new(type="ShaderNodeEmission")
|
||||
emission.location = add.location - mathutils.Vector((200, 0))
|
||||
blender_material.node_tree.links.new(emission.outputs[0], add.inputs[0])
|
||||
|
||||
node = blender_material.node_tree.nodes.new(type="ShaderNodeTexImage")
|
||||
node.location = emission.location - mathutils.Vector((200, 0))
|
||||
image = bpy.data.images.load(image_url)
|
||||
node.image = image
|
||||
blender_material.node_tree.links.new(node.outputs[0], emission.inputs[0])
|
||||
elif mode == "METALLICROUGHNESS":
|
||||
separate = blender_material.node_tree.nodes.new(type="ShaderNodeSeparateRGB")
|
||||
separate.location = bsdf.location - mathutils.Vector((200, 0))
|
||||
blender_material.node_tree.links.new(separate.outputs[1], bsdf.inputs["Roughness"])
|
||||
blender_material.node_tree.links.new(separate.outputs[2], bsdf.inputs["Metallic"])
|
||||
|
||||
node = blender_material.node_tree.nodes.new(type="ShaderNodeTexImage")
|
||||
node.location = separate.location - mathutils.Vector((200, 0))
|
||||
image = bpy.data.images.load(image_url)
|
||||
image.colorspace_settings.name = "Non-Color"
|
||||
node.image = image
|
||||
blender_material.node_tree.links.new(node.outputs[0], separate.inputs[0])
|
||||
elif mode == "OCCLUSION":
|
||||
# TODO work out how to implement glTF settings here
|
||||
# https://docs.blender.org/manual/en/dev/addons/import_export/scene_gltf2.html
|
||||
pass
|
||||
elif mode == "DIFFUSE":
|
||||
node = blender_material.node_tree.nodes.new(type="ShaderNodeTexImage")
|
||||
node.location = bsdf.location - mathutils.Vector((400, 0))
|
||||
image = bpy.data.images.load(image_url)
|
||||
node.image = image
|
||||
blender_material.node_tree.links.new(node.outputs[0], bsdf.inputs["Base Color"])
|
||||
blender_material.node_tree.links.new(node.outputs[1], bsdf.inputs["Alpha"])
|
||||
blender_material.blend_method = "BLEND"
|
||||
elif rendering_style.ReflectanceMethod == "FLAT":
|
||||
bsdf = blender_material.node_tree.nodes["Mix Shader"]
|
||||
if mode == "EMISSIVE":
|
||||
node = blender_material.node_tree.nodes.new(type="ShaderNodeTexImage")
|
||||
node.location = bsdf.location - mathutils.Vector((200, 0))
|
||||
image = bpy.data.images.load(image_url)
|
||||
node.image = image
|
||||
blender_material.node_tree.links.new(node.outputs[0], bsdf.inputs[2])
|
||||
|
||||
if node and getattr(texture, "IsMappedBy", None):
|
||||
coordinates = texture.IsMappedBy[0]
|
||||
coord = blender_material.node_tree.nodes.new(type="ShaderNodeTexCoord")
|
||||
coord.location = node.location - mathutils.Vector((200, 0))
|
||||
if coordinates.is_a("IfcTextureCoordinateGenerator") and coordinates.Mode == "COORD":
|
||||
blender_material.node_tree.links.new(coord.outputs["Generated"], node.inputs["Vector"])
|
||||
elif coordinates.is_a("IfcTextureCoordinateGenerator") and coordinates.Mode == "COORD-EYE":
|
||||
blender_material.node_tree.links.new(coord.outputs["Camera"], node.inputs["Vector"])
|
||||
else:
|
||||
blender_material.node_tree.links.new(coord.outputs["UV"], node.inputs["Vector"])
|
||||
|
||||
def get_name(self, element):
|
||||
return "{}/{}".format(element.is_a(), element.Name)
|
||||
@@ -1510,20 +1724,22 @@ class IfcImporter:
|
||||
and geometry.verts
|
||||
and self.is_point_far_away((geometry.verts[0], geometry.verts[1], geometry.verts[2]))
|
||||
):
|
||||
ordinate_index = 0
|
||||
verts = [None] * len(geometry.verts)
|
||||
offset_point = (
|
||||
float(props.blender_eastings) * self.unit_scale,
|
||||
float(props.blender_northings) * self.unit_scale,
|
||||
float(props.blender_orthogonal_height) * self.unit_scale,
|
||||
)
|
||||
for i, vert in enumerate(geometry.verts):
|
||||
if ordinate_index > 2:
|
||||
ordinate_index = 0
|
||||
verts[i] = vert - offset_point[ordinate_index]
|
||||
ordinate_index += 1
|
||||
for i in range(0, len(geometry.verts), 3):
|
||||
verts[i], verts[i + 1], verts[i + 2] = ifcopenshell.util.geolocation.enh2xyz(
|
||||
geometry.verts[i],
|
||||
geometry.verts[i + 1],
|
||||
geometry.verts[i + 2],
|
||||
float(props.blender_eastings) * self.unit_scale,
|
||||
float(props.blender_northings) * self.unit_scale,
|
||||
float(props.blender_orthogonal_height) * self.unit_scale,
|
||||
float(props.blender_x_axis_abscissa),
|
||||
float(props.blender_x_axis_ordinate),
|
||||
)
|
||||
mesh["has_cartesian_point_offset"] = True
|
||||
else:
|
||||
verts = geometry.verts
|
||||
mesh["has_cartesian_point_offset"] = False
|
||||
|
||||
if geometry.faces:
|
||||
num_vertices = len(verts) // 3
|
||||
@@ -1611,7 +1827,8 @@ class IfcImporter:
|
||||
if "-" in geometry.id:
|
||||
mesh.BIMMeshProperties.ifc_definition_id = int(geometry.id.split("-")[0])
|
||||
else:
|
||||
mesh.BIMMeshProperties.ifc_definition_id = int(geometry.id)
|
||||
# TODO: See #2002
|
||||
mesh.BIMMeshProperties.ifc_definition_id = int(geometry.id.replace(",", ""))
|
||||
|
||||
def set_matrix_world(self, obj, matrix_world):
|
||||
obj.matrix_world = matrix_world
|
||||
@@ -1638,11 +1855,13 @@ class IfcImportSettings:
|
||||
self.collection_mode = "DECOMPOSITION"
|
||||
|
||||
@staticmethod
|
||||
def factory(context, input_file, logger):
|
||||
scene_diff = context.scene.DiffProperties
|
||||
props = context.scene.BIMProjectProperties
|
||||
def factory(context=None, input_file=None, logger=None):
|
||||
scene_diff = bpy.context.scene.DiffProperties
|
||||
props = bpy.context.scene.BIMProjectProperties
|
||||
settings = IfcImportSettings()
|
||||
settings.input_file = input_file
|
||||
if logger is None:
|
||||
logger = logging.getLogger("ImportIFC")
|
||||
settings.logger = logger
|
||||
settings.diff_file = scene_diff.diff_json_file
|
||||
settings.collection_mode = props.collection_mode
|
||||
|
||||
@@ -20,11 +20,14 @@ import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.AssignObject,
|
||||
operator.UnassignObject,
|
||||
operator.EnableEditingAggregate,
|
||||
operator.DisableEditingAggregate,
|
||||
operator.AddAggregate,
|
||||
operator.BIM_OT_assign_object,
|
||||
operator.BIM_OT_unassign_object,
|
||||
operator.BIM_OT_enable_editing_aggregate,
|
||||
operator.BIM_OT_disable_editing_aggregate,
|
||||
operator.BIM_OT_add_aggregate,
|
||||
operator.BIM_OT_select_parts,
|
||||
operator.BIM_OT_select_aggregate,
|
||||
operator.BIM_OT_add_part_to_object,
|
||||
prop.BIMObjectAggregateProperties,
|
||||
ui.BIM_PT_aggregate,
|
||||
)
|
||||
|
||||
@@ -32,24 +32,50 @@ class AggregateData:
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {
|
||||
"has_aggregate": cls.has_aggregate(),
|
||||
"label": cls.get_label(),
|
||||
"has_relating_object": cls.has_relating_object(),
|
||||
"relating_object_label": cls.get_relating_object_label(),
|
||||
"relating_object_id": cls.get_relating_object_id(),
|
||||
"has_related_objects": cls.has_related_objects(),
|
||||
"related_objects_amount": cls.get_related_objects_amount(),
|
||||
"ifc_class": cls.ifc_class(),
|
||||
}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def has_aggregate(cls):
|
||||
def get_relating_object(cls):
|
||||
return ifcopenshell.util.element.get_aggregate(tool.Ifc.get_entity(bpy.context.active_object))
|
||||
|
||||
@classmethod
|
||||
def get_label(cls):
|
||||
aggregate = ifcopenshell.util.element.get_aggregate(tool.Ifc.get_entity(bpy.context.active_object))
|
||||
def has_relating_object(cls) -> bool:
|
||||
return cls.get_relating_object() is not None
|
||||
|
||||
@classmethod
|
||||
def get_relating_object_label(cls) -> str:
|
||||
aggregate = cls.get_relating_object()
|
||||
if aggregate:
|
||||
return f"{aggregate.is_a()}/{aggregate.Name or ''}"
|
||||
|
||||
@classmethod
|
||||
def get_relating_object_id(cls):
|
||||
aggregate = ifcopenshell.util.element.get_aggregate(tool.Ifc.get_entity(bpy.context.active_object))
|
||||
def get_relating_object_id(cls) -> int:
|
||||
aggregate = cls.get_relating_object()
|
||||
if aggregate:
|
||||
return aggregate.id()
|
||||
|
||||
@classmethod
|
||||
def get_related_objects(cls):
|
||||
return ifcopenshell.util.element.get_parts(tool.Ifc.get_entity(bpy.context.active_object))
|
||||
|
||||
@classmethod
|
||||
def get_related_objects_amount(cls):
|
||||
parts = cls.get_related_objects()
|
||||
return len(parts) if parts else 0
|
||||
|
||||
@classmethod
|
||||
def has_related_objects(cls) -> bool:
|
||||
return bool(cls.get_related_objects())
|
||||
|
||||
@classmethod
|
||||
def ifc_class(cls) -> str:
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
if element:
|
||||
return element.is_a()
|
||||
|
||||
@@ -33,7 +33,9 @@ class Operator:
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AssignObject(bpy.types.Operator, Operator):
|
||||
class BIM_OT_assign_object(bpy.types.Operator, Operator):
|
||||
"""Create aggregation relationship between two ifc elements"""
|
||||
|
||||
bl_idname = "bim.assign_object"
|
||||
bl_label = "Assign Object"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
@@ -50,7 +52,9 @@ class AssignObject(bpy.types.Operator, Operator):
|
||||
)
|
||||
|
||||
|
||||
class UnassignObject(bpy.types.Operator, Operator):
|
||||
class BIM_OT_unassign_object(bpy.types.Operator, Operator):
|
||||
"""Remove aggregation relationship between two ifc elements"""
|
||||
|
||||
bl_idname = "bim.unassign_object"
|
||||
bl_label = "Unassign Object"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
@@ -66,7 +70,9 @@ class UnassignObject(bpy.types.Operator, Operator):
|
||||
)
|
||||
|
||||
|
||||
class EnableEditingAggregate(bpy.types.Operator, Operator):
|
||||
class BIM_OT_enable_editing_aggregate(bpy.types.Operator, Operator):
|
||||
"""Enable editing aggregation relationship"""
|
||||
|
||||
bl_idname = "bim.enable_editing_aggregate"
|
||||
bl_label = "Enable Editing Aggregate"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
@@ -75,7 +81,9 @@ class EnableEditingAggregate(bpy.types.Operator, Operator):
|
||||
core.enable_editing_aggregate(tool.Aggregate, obj=context.active_object)
|
||||
|
||||
|
||||
class DisableEditingAggregate(bpy.types.Operator, Operator):
|
||||
class BIM_OT_disable_editing_aggregate(bpy.types.Operator, Operator):
|
||||
"""Disable editing aggregation relationship"""
|
||||
|
||||
bl_idname = "bim.disable_editing_aggregate"
|
||||
bl_label = "Disable Editing Aggregate"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
@@ -84,7 +92,9 @@ class DisableEditingAggregate(bpy.types.Operator, Operator):
|
||||
core.disable_editing_aggregate(tool.Aggregate, obj=context.active_object)
|
||||
|
||||
|
||||
class AddAggregate(bpy.types.Operator):
|
||||
class BIM_OT_add_aggregate(bpy.types.Operator):
|
||||
"""Add aggregate to IFC element"""
|
||||
|
||||
bl_idname = "bim.add_aggregate"
|
||||
bl_label = "Add Aggregate"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
@@ -99,12 +109,7 @@ class AddAggregate(bpy.types.Operator):
|
||||
if not element:
|
||||
return {"FINISHED"}
|
||||
|
||||
aggregate_collection = bpy.data.collections.new("IfcElementAssembly/Assembly")
|
||||
context.scene.collection.children.link(aggregate_collection)
|
||||
aggregate = bpy.data.objects.new("Assembly", None)
|
||||
aggregate_collection.objects.link(aggregate)
|
||||
bpy.ops.bim.assign_class(obj=aggregate.name, ifc_class="IfcElementAssembly")
|
||||
|
||||
aggregate = self.create_aggregate(context)
|
||||
tool.Collector.sync(obj)
|
||||
current_aggregate = ifcopenshell.util.element.get_aggregate(element)
|
||||
current_container = ifcopenshell.util.element.get_container(element)
|
||||
@@ -126,3 +131,73 @@ class AddAggregate(bpy.types.Operator):
|
||||
)
|
||||
core.assign_object(tool.Ifc, tool.Aggregate, tool.Collector, relating_obj=aggregate, related_obj=obj)
|
||||
return {"FINISHED"}
|
||||
|
||||
def create_aggregate(self, context):
|
||||
aggregate_collection = bpy.data.collections.new("IfcElementAssembly/Assembly")
|
||||
context.scene.collection.children.link(aggregate_collection)
|
||||
aggregate = bpy.data.objects.new("Assembly", None)
|
||||
aggregate_collection.objects.link(aggregate)
|
||||
bpy.ops.bim.assign_class(obj=aggregate.name, ifc_class="IfcElementAssembly")
|
||||
return aggregate
|
||||
|
||||
|
||||
class BIM_OT_select_parts(bpy.types.Operator):
|
||||
"""Select Parts"""
|
||||
|
||||
bl_idname = "bim.select_parts"
|
||||
bl_label = "Select Parts"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
obj = bpy.data.objects.get(self.obj) or context.active_object
|
||||
parts = ifcopenshell.util.element.get_parts(tool.Ifc.get_entity(obj))
|
||||
parts_objs = set(tool.Ifc.get_object(part) for part in parts)
|
||||
selectable_parts_objs = set(context.selectable_objects).intersection(parts_objs)
|
||||
for selectable_part_obj in selectable_parts_objs:
|
||||
selectable_part_obj.select_set(True)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class BIM_OT_select_aggregate(bpy.types.Operator):
|
||||
"""Select Aggregate"""
|
||||
|
||||
bl_idname = "bim.select_aggregate"
|
||||
bl_label = "Select Aggregate"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
obj = bpy.data.objects.get(self.obj) or context.active_object
|
||||
aggregate = ifcopenshell.util.element.get_aggregate(tool.Ifc.get_entity(obj))
|
||||
aggregate_obj = tool.Ifc.get_object(aggregate)
|
||||
if aggregate_obj in context.selectable_objects:
|
||||
aggregate_obj.select_set(True)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class BIM_OT_add_part_to_object(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.add_part_to_object"
|
||||
bl_label = "Add Part to Object"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
part_class: bpy.props.StringProperty(name="Class", options={"HIDDEN"})
|
||||
part_name: bpy.props.StringProperty(name="Name")
|
||||
obj: bpy.props.StringProperty(options={"HIDDEN"})
|
||||
|
||||
def invoke(self, context, event):
|
||||
self.part_name = "My " + self.part_class.lstrip("Ifc")
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
def _execute(self, context):
|
||||
obj = bpy.data.objects.get(self.obj) or context.active_object
|
||||
core.add_part_to_object(
|
||||
tool.Ifc,
|
||||
tool.Aggregate,
|
||||
tool.Collector,
|
||||
tool.Blender,
|
||||
obj=obj,
|
||||
part_class=self.part_class,
|
||||
part_name=self.part_name,
|
||||
)
|
||||
|
||||
@@ -32,6 +32,17 @@ from bpy.props import (
|
||||
)
|
||||
|
||||
|
||||
def update_relating_object(self, context):
|
||||
def message(self, context):
|
||||
self.layout.label(text="Please select a valid Ifc Element")
|
||||
|
||||
if self.relating_object is None:
|
||||
return
|
||||
if not self.relating_object.BIMObjectProperties.ifc_definition_id:
|
||||
context.window_manager.popup_menu(message, title="Invalid Element Selected", icon="INFO")
|
||||
self.relating_object = None
|
||||
|
||||
|
||||
class BIMObjectAggregateProperties(PropertyGroup):
|
||||
is_editing: BoolProperty(name="Is Editing")
|
||||
relating_object: PointerProperty(name="Aggregate", type=bpy.types.Object)
|
||||
relating_object: PointerProperty(name="Aggregate", type=bpy.types.Object, update=update_relating_object)
|
||||
|
||||
@@ -44,13 +44,14 @@ class BIM_PT_aggregate(Panel):
|
||||
return True
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
if not AggregateData.is_loaded:
|
||||
AggregateData.load()
|
||||
|
||||
props = context.active_object.BIMObjectAggregateProperties
|
||||
|
||||
if props.is_editing:
|
||||
row = self.layout.row(align=True)
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "relating_object", text="")
|
||||
if props.relating_object:
|
||||
op = row.operator("bim.assign_object", icon="CHECKMARK", text="")
|
||||
@@ -58,15 +59,37 @@ class BIM_PT_aggregate(Panel):
|
||||
op.related_object = context.active_object.BIMObjectProperties.ifc_definition_id
|
||||
row.operator("bim.disable_editing_aggregate", icon="CANCEL", text="")
|
||||
else:
|
||||
row = self.layout.row(align=True)
|
||||
if AggregateData.data["has_aggregate"]:
|
||||
row.label(text=AggregateData.data["label"])
|
||||
row = layout.row(align=True)
|
||||
if AggregateData.data["has_relating_object"]:
|
||||
row.label(text=AggregateData.data["relating_object_label"], icon="TRIA_UP")
|
||||
op = row.operator("bim.select_aggregate", icon="RESTRICT_SELECT_OFF", text="")
|
||||
op.obj = context.active_object.name
|
||||
row.operator("bim.enable_editing_aggregate", icon="GREASEPENCIL", text="")
|
||||
row.operator("bim.add_aggregate", icon="ADD", text="")
|
||||
op = row.operator("bim.unassign_object", icon="X", text="")
|
||||
op.relating_object = AggregateData.data["relating_object_id"]
|
||||
op.related_object = context.active_object.BIMObjectProperties.ifc_definition_id
|
||||
else:
|
||||
row.label(text="No Aggregate Found")
|
||||
row.label(text="No Aggregate", icon="TRIA_UP")
|
||||
row.operator("bim.enable_editing_aggregate", icon="GREASEPENCIL", text="")
|
||||
row.operator("bim.add_aggregate", icon="ADD", text="")
|
||||
|
||||
row = layout.row(align=True)
|
||||
parts = AggregateData.data["related_objects_amount"]
|
||||
row.label(text=f"{parts or 'No'} Part{'s' if parts > 1 else ''}", icon="TRIA_DOWN")
|
||||
if AggregateData.data["has_related_objects"]:
|
||||
op = row.operator("bim.select_parts", icon="RESTRICT_SELECT_OFF", text="")
|
||||
op.obj = context.active_object.name
|
||||
|
||||
ifc_class = AggregateData.data["ifc_class"]
|
||||
part_class = ""
|
||||
if ifc_class == "IfcBuilding":
|
||||
part_class = "IfcBuildingStorey"
|
||||
elif ifc_class == "IfcSite":
|
||||
part_class = "IfcBuilding"
|
||||
elif ifc_class == "IfcProject":
|
||||
part_class = "IfcSite"
|
||||
if part_class != "":
|
||||
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
|
||||
|
||||
@@ -60,7 +60,6 @@ class BIM_PT_object_attributes(Panel):
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
bl_parent_id = "BIM_PT_object_metadata"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -82,8 +81,6 @@ class BIM_PT_material_attributes(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "material"
|
||||
bl_parent_id = "BIM_PT_object_metadata"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
|
||||
@@ -26,6 +26,7 @@ class BIM_PT_augin(bpy.types.Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_integrations"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
from blenderbim.bim.prop import StrProperty
|
||||
from blenderbim.bim.module.root.prop import getIfcClasses
|
||||
from blenderbim.bim.module.root.prop import get_ifc_classes
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
@@ -33,14 +33,11 @@ from bpy.props import (
|
||||
)
|
||||
|
||||
scenarios_enum = []
|
||||
classes_enum = []
|
||||
|
||||
|
||||
def purge():
|
||||
global scenarios_enum
|
||||
global classes_enum
|
||||
scenarios_enum = []
|
||||
classes_enum = []
|
||||
|
||||
|
||||
def getScenarios(self, context):
|
||||
@@ -68,7 +65,7 @@ class BimTesterProperties(PropertyGroup):
|
||||
feature: StringProperty(default="", name="Feature / IDS", update=refreshScenarios)
|
||||
steps: StringProperty(default="", name="Custom Steps")
|
||||
ifc_file: StringProperty(default="", name="IFC File")
|
||||
audit_ifc_class: EnumProperty(items=getIfcClasses, name="Audit Class")
|
||||
audit_ifc_class: EnumProperty(items=get_ifc_classes, name="Audit Class")
|
||||
qa_reject_element_reason: StringProperty(name="Element Rejection Reason")
|
||||
scenario: EnumProperty(items=getScenarios, name="Scenario")
|
||||
should_load_from_memory: BoolProperty(default=False, name="Load from Memory")
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
|
||||
# Copyright (C) 2020, 2021, 2022 Cyril Waechter <cyril@biminsight.ch>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
@@ -16,16 +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 logging
|
||||
import bpy
|
||||
import mathutils
|
||||
import ifcopenshell.util.attribute
|
||||
import logging
|
||||
import ifcopenshell.api
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.api.boundary.data import Data
|
||||
import blenderbim.bim.import_ifc as import_ifc
|
||||
from blenderbim.bim.module.geometry.helper import Helper
|
||||
|
||||
|
||||
def get_boundaries_collection(blender_space):
|
||||
@@ -47,6 +44,19 @@ class Loader:
|
||||
self.load_settings()
|
||||
self.load_importer()
|
||||
|
||||
def create_mesh(self, boundary):
|
||||
# ConnectionGeometry is optional in IFC schema for some reasons.
|
||||
if not boundary.ConnectionGeometry:
|
||||
return None
|
||||
surface = boundary.ConnectionGeometry.SurfaceOnRelatingElement
|
||||
# workaround for unvalid geometry provided by Revit. See https://github.com/IfcOpenShell/IfcOpenShell/issues/635#issuecomment-770366838
|
||||
if surface.is_a("IfcCurveBoundedPlane") and not getattr(surface, "InnerBoundaries", None):
|
||||
surface.InnerBoundaries = ()
|
||||
shape = ifcopenshell.geom.create_shape(self.settings, surface)
|
||||
mesh = self.ifc_importer.create_mesh(None, shape)
|
||||
self.ifc_importer.link_mesh(shape, mesh)
|
||||
return mesh
|
||||
|
||||
def load_settings(self):
|
||||
self.settings = ifcopenshell.geom.settings()
|
||||
self.settings.set(self.settings.EXCLUDE_SOLIDS_AND_SURFACES, False)
|
||||
@@ -64,12 +74,7 @@ class Loader:
|
||||
obj = tool.Ifc.get_object(boundary)
|
||||
if obj:
|
||||
return obj
|
||||
surface = boundary.ConnectionGeometry.SurfaceOnRelatingElement
|
||||
# workaround for unvalid geometry provided by Revit. See https://github.com/IfcOpenShell/IfcOpenShell/issues/635#issuecomment-770366838
|
||||
if surface.is_a("IfcCurveBoundedPlane") and not getattr(surface, "InnerBoundaries", None):
|
||||
surface.InnerBoundaries = ()
|
||||
shape = ifcopenshell.geom.create_shape(self.settings, surface)
|
||||
mesh = self.ifc_importer.create_mesh(None, shape)
|
||||
mesh = self.create_mesh(boundary)
|
||||
obj = bpy.data.objects.new(f"{boundary.is_a()}/{boundary.Name}", mesh)
|
||||
obj.matrix_world = blender_space.matrix_world
|
||||
boundaries_collection = get_boundaries_collection(blender_space)
|
||||
@@ -267,20 +272,11 @@ class EditBoundaryAttributes(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
def polyline_from_indexes(mesh, indexes):
|
||||
return tuple(mesh.vertices[i].co for i in indexes)
|
||||
|
||||
|
||||
def polyline_to_2d(polyline, placement_matrix):
|
||||
matrix_inv = placement_matrix.inverted()
|
||||
return tuple((matrix_inv @ v).to_2d() for v in polyline)
|
||||
|
||||
|
||||
class UpdateBoundaryGeometry(bpy.types.Operator):
|
||||
bl_idname = "bim.update_boundary_geometry"
|
||||
bl_label = "Update boundary geometry"
|
||||
bl_description = """
|
||||
Update boundary connection geometry from mesh.
|
||||
Update boundary connection geometry from mesh.
|
||||
Mesh must lie on a single plane. It should look like a face or a face with holes.
|
||||
"""
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
@@ -289,32 +285,6 @@ class UpdateBoundaryGeometry(bpy.types.Operator):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
ifc_file = tool.Ifc.get()
|
||||
helper = Helper(ifc_file)
|
||||
mesh = context.active_object.data
|
||||
curves = helper.auto_detect_curve_bounded_plane(mesh)
|
||||
outer_boundary = polyline_from_indexes(mesh, curves["outer_curve"])
|
||||
inner_boundaries = tuple(polyline_from_indexes(mesh, boundary) for boundary in curves["inner_curves"])
|
||||
|
||||
# Create placement matrix
|
||||
location = outer_boundary[0]
|
||||
i = (outer_boundary[1] - outer_boundary[0]).normalized()
|
||||
k = mesh.polygons[0].normal
|
||||
j = k.cross(i)
|
||||
matrix = mathutils.Matrix()
|
||||
matrix[0].xyz = i
|
||||
matrix[1].xyz = j
|
||||
matrix[2].xyz = k
|
||||
matrix.transpose()
|
||||
matrix.translation = location
|
||||
|
||||
settings = {
|
||||
"rel_space_boundary": tool.Ifc.get_entity(context.active_object),
|
||||
"outer_boundary": polyline_to_2d(outer_boundary, matrix),
|
||||
"inner_boundaries": tuple(polyline_to_2d(boundary, matrix) for boundary in inner_boundaries),
|
||||
"location": location,
|
||||
"axis": k,
|
||||
"ref_direction": i,
|
||||
}
|
||||
ifcopenshell.api.run("boundary.assign_connection_geometry", ifc_file, **settings)
|
||||
settings = tool.Boundary.get_assign_connection_geometry_settings(context.active_object)
|
||||
ifcopenshell.api.run("boundary.assign_connection_geometry", tool.Ifc.get(), **settings)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -29,6 +29,7 @@ classes = (
|
||||
operator.LoadBrickProject,
|
||||
operator.NewBrickFile,
|
||||
operator.RefreshBrickViewer,
|
||||
operator.RemoveBrick,
|
||||
operator.RewindBrickClass,
|
||||
operator.ViewBrickClass,
|
||||
operator.ViewBrickItem,
|
||||
|
||||
@@ -148,7 +148,10 @@ class BrickschemaReferencesData:
|
||||
@classmethod
|
||||
def libraries(cls):
|
||||
results = []
|
||||
for library in tool.Ifc.get().by_type("IfcLibraryInformation"):
|
||||
ifc = tool.Ifc.get()
|
||||
if not ifc:
|
||||
return results
|
||||
for library in ifc.by_type("IfcLibraryInformation"):
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
results.append((str(library.id()), library.Name or "Unnamed", ""))
|
||||
elif ".ttl" in library.Location:
|
||||
|
||||
@@ -174,3 +174,18 @@ class RefreshBrickViewer(bpy.types.Operator, Operator):
|
||||
|
||||
def _execute(self, context):
|
||||
core.refresh_brick_viewer(tool.Brick)
|
||||
|
||||
|
||||
class RemoveBrick(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.remove_brick"
|
||||
bl_label = "Remove Brick"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMBrickProperties
|
||||
core.remove_brick(
|
||||
tool.Ifc,
|
||||
tool.Brick,
|
||||
library=tool.Ifc.get().by_id(int(props.libraries)) if props.libraries else None,
|
||||
brick_uri=props.bricks[props.active_brick_index].uri,
|
||||
)
|
||||
|
||||
@@ -51,7 +51,11 @@ class BIM_PT_brickschema(Panel):
|
||||
row.prop(self.props, "namespace", text="")
|
||||
row.prop(self.props, "brick_equipment_class", text="")
|
||||
row.operator("bim.add_brick", text="", icon="ADD")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.alignment = "RIGHT"
|
||||
row.operator("bim.add_brick_feed", text="", icon="PLUGIN")
|
||||
row.operator("bim.remove_brick", text="", icon="X")
|
||||
|
||||
self.layout.template_list("BIM_UL_bricks", "", self.props, "bricks", self.props, "active_brick_index")
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ class BIM_PT_constraints(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_misc"
|
||||
bl_parent_id = "BIM_PT_project_setup"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
|
||||
@@ -26,6 +26,7 @@ class BIM_PT_covetool(bpy.types.Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_integrations"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# ############################################################################ #
|
||||
|
||||
# Hey there! Welcome to the BlenderBIM Add-on code. Please feel free to reach
|
||||
# out if you have any questions or need further guidance. Happy hacking!
|
||||
|
||||
# ############################################################################ #
|
||||
|
||||
# Every module has a __init__.py file to load all of its classes. Every
|
||||
# operation, property, and interface needs to be registered with the Blender
|
||||
# system when the add-on loads. This is where it happens.
|
||||
|
||||
import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
# You'll need to provide a list of every one of your classes here. If you forget
|
||||
# to specify your class, it won't load and you won't be able to use that
|
||||
# operator, property, or interface.
|
||||
classes = (
|
||||
operator.DemonstrateHelloWorld,
|
||||
operator.DemonstrateRenameProject,
|
||||
prop.BIMDemoProperties,
|
||||
ui.BIM_PT_demo,
|
||||
)
|
||||
|
||||
|
||||
# When the add-on loads, this register function is called. This allows you to
|
||||
# perform additional tasks during startup. If you need to store custom
|
||||
# properties, this is where you tell Blender where they are going to be stored.
|
||||
# You might see more advanced registrations happening in other modules.
|
||||
def register():
|
||||
# Properties are usually stored on bpy.types.Scene when they are something
|
||||
# that affects everything in the project, or bpy.types.Object when they
|
||||
# affect a single BIM element.
|
||||
bpy.types.Scene.BIMDemoProperties = bpy.props.PointerProperty(type=prop.BIMDemoProperties)
|
||||
|
||||
|
||||
# When someone disables the add-on, we need to unload everything we loaded. This
|
||||
# does the reverse of the register function.
|
||||
def unregister():
|
||||
del bpy.types.Scene.BIMDemoProperties
|
||||
@@ -0,0 +1,78 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# ############################################################################ #
|
||||
|
||||
# Hey there! Welcome to the BlenderBIM Add-on code. Please feel free to reach
|
||||
# out if you have any questions or need further guidance. Happy hacking!
|
||||
|
||||
# ############################################################################ #
|
||||
|
||||
# Every module has a data.py file to load data for its interface panels to
|
||||
# display. When a panel needs to show information that doesn't come from a
|
||||
# Blender property, it gets that data from one of these classes. This separation
|
||||
# of "fetching data" and "displaying data" is a common concept that makes UI
|
||||
# code much simpler, more efficient, and less error-prone to refresh data.
|
||||
|
||||
import blenderbim.tool as tool
|
||||
|
||||
|
||||
# All data must have a refresh function. The refresh function simply sets all
|
||||
# data class to be not yet loaded. So the next time the interface is drawn to
|
||||
# the user, it will force the data to be reloaded.
|
||||
def refresh():
|
||||
# When you define your own data classes, just add to this list!
|
||||
DemoData.is_loaded = False
|
||||
|
||||
|
||||
# This is a sample data class. It correlates to a single interface panel. Panels
|
||||
# should not share data classes. This makes it easy to write your interface
|
||||
# without having your code mixed in with other parts of the interface. As a
|
||||
# convention, the class is named the same name as the panel.
|
||||
class DemoData:
|
||||
# All data classes must have two variables. One to store all the data it has
|
||||
# loaded and another to store the load state.
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
# Every data class must have a load function. This lets us load data in a
|
||||
# predictable manner.
|
||||
@classmethod
|
||||
def load(cls):
|
||||
# The load function always has two responsibilities: populate the data,
|
||||
# and set is_loaded to true.
|
||||
cls.data = {
|
||||
"has_project": cls.has_project(),
|
||||
"project_name": cls.project_name(),
|
||||
}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def has_project(cls):
|
||||
# Here, we check whether or not there is an active IFC project in our
|
||||
# Blender session.
|
||||
return bool(tool.Ifc.get())
|
||||
|
||||
@classmethod
|
||||
def project_name(cls):
|
||||
# Imagine how messy our UI code would be if all of this was mixed in
|
||||
# with our layout code. Here, it's isolated and testable, and the UX
|
||||
# becomes easier to maintain and change.
|
||||
ifc = tool.Ifc.get()
|
||||
if ifc:
|
||||
return ifc.by_type("IfcProject")[0].Name or "Unnamed"
|
||||
@@ -0,0 +1,79 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# ############################################################################ #
|
||||
|
||||
# Hey there! Welcome to the BlenderBIM Add-on code. Please feel free to reach
|
||||
# out if you have any questions or need further guidance. Happy hacking!
|
||||
|
||||
# ############################################################################ #
|
||||
|
||||
# Every module has an operator.py file to define all of the buttons that a user
|
||||
# can click on from the Blender interface. Blender calls these buttons
|
||||
# "Operators", since they correlate to a single user operation.
|
||||
|
||||
import bpy
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.demo as core
|
||||
import blenderbim.bim.handler
|
||||
|
||||
|
||||
# Each button correlates to a class like the one below. In this case, we're
|
||||
# creating a new button that will execute a hello world feature.
|
||||
class DemonstrateHelloWorld(bpy.types.Operator, tool.Ifc.Operator):
|
||||
# Every operator has a unique ID. If you enable Python tooltips and hover
|
||||
# over any button in the interface, you will see each button will run a
|
||||
# function that uses this ID. For example, hovering over this button will
|
||||
# show that the code it executes is "bpy.ops.bim.demonstrate_hello_world()"
|
||||
bl_idname = "bim.demonstrate_hello_world"
|
||||
|
||||
# In the interface, this button will have the text "Print Hello World".
|
||||
bl_label = "Print Hello World"
|
||||
|
||||
# This code means that the user can undo or redo after pressing the button.
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
# When hovering over the button, this helpful description will be shown.
|
||||
bl_description = "Prints the text 'Hello World'"
|
||||
|
||||
# When the button is pressed, this _execute() function will run.
|
||||
def _execute(self, context):
|
||||
# Every operator should do one thing only: execute a core function. In
|
||||
# order to execute a core function, the operator's responsibility is to
|
||||
# pass in all of the inputs the core needs to do its job.
|
||||
|
||||
# A core function simply tells tools what to do, so a core function will
|
||||
# always need at least one tool as an input.
|
||||
core.demonstrate_hello_world(tool.Demo)
|
||||
|
||||
|
||||
# This class, just like the one above, will correlate to another button in the
|
||||
# interface. It has a different ID and a different label.
|
||||
class DemonstrateRenameProject(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.demonstrate_rename_project"
|
||||
bl_label = "Rename IFC Project"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Renames your IFC project name and prints the name"
|
||||
|
||||
def _execute(self, context):
|
||||
# This core function requires three inputs: two tools, and a name
|
||||
# string. In this case, the name is taken from some custom Blender
|
||||
# properties. Generally, the inputs to the core function will come from
|
||||
# properties (such as an input field) or data from the scene (like the
|
||||
# actively selected object).
|
||||
core.demonstrate_rename_project(tool.Ifc, tool.Demo, name=bpy.context.scene.BIMDemoProperties.name)
|
||||
@@ -0,0 +1,61 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# ############################################################################ #
|
||||
|
||||
# Hey there! Welcome to the BlenderBIM Add-on code. Please feel free to reach
|
||||
# out if you have any questions or need further guidance. Happy hacking!
|
||||
|
||||
# ############################################################################ #
|
||||
|
||||
# Every module has a prop.py file to define Blender properties. Any time you
|
||||
# want an interface widget like an input field, dropdown, checkbox, or number
|
||||
# slider, you need a Blender property to store that widget's data. If you want
|
||||
# to store data that will affect the Blender interface, you also need a
|
||||
# property. Properties are stored in the .blend file, so when your user closes
|
||||
# their Blender session, and reopens it, things are how they left it.
|
||||
|
||||
import bpy
|
||||
from bpy.types import PropertyGroup
|
||||
|
||||
# Properties have many different data types. We won't use all of them in this
|
||||
# demo module, but this is a list for your reference.
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
StringProperty,
|
||||
EnumProperty,
|
||||
BoolProperty,
|
||||
IntProperty,
|
||||
FloatProperty,
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
|
||||
|
||||
# All properties must belong in a property group. Usually, you'd have a group
|
||||
# named after your module.
|
||||
class BIMDemoProperties(PropertyGroup):
|
||||
# This first property is a string. This means that in the interface, it will
|
||||
# represent a text input field. We can give it a name and a default value.
|
||||
# The name will be the label shown next to the input field in the interface.
|
||||
name: StringProperty(name="Name", default="New Project Name")
|
||||
# Not all properties need to be shown using their equivalent input widget.
|
||||
# In this case, we can store a message string, but we will never show it as
|
||||
# an input text field in the ui.py.
|
||||
message: StringProperty(name="Message")
|
||||
show_hints: BoolProperty(name="Show Hints", default=False)
|
||||
@@ -0,0 +1,131 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# ############################################################################ #
|
||||
|
||||
# Hey there! Welcome to the BlenderBIM Add-on code. Please feel free to reach
|
||||
# out if you have any questions or need further guidance. Happy hacking!
|
||||
|
||||
# ############################################################################ #
|
||||
|
||||
# Every module has a ui.py file to define its interface. Interfaces describe how
|
||||
# panels, buttons, labels, and input fields are laid out.
|
||||
|
||||
import bpy
|
||||
from blenderbim.bim.module.demo.data import DemoData
|
||||
|
||||
# Every panel in the interface correlates to one of these classes. If you enable
|
||||
# the "Developer Extras" option in Blender, then you can actually right click on
|
||||
# any panel in Blender, and click "Edit Source". This will bring you to a ui.py
|
||||
# file like this one where you can see this code. Pretty neat!
|
||||
class BIM_PT_demo(bpy.types.Panel):
|
||||
# Every panel has a title.
|
||||
bl_label = "BlenderBIM Demo"
|
||||
|
||||
# Every panel must have an ID. It must be unique. For example, you may want
|
||||
# to later reference the panel (such as if you want to create a nested
|
||||
# subpanel).
|
||||
bl_idname = "BIM_PT_demo"
|
||||
|
||||
# This tells the panel to appear in the properties section (in the bottom
|
||||
# right by default) of the Blender interface.
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
|
||||
# This tells the panel to appear in the "scene" tab of the properties panel.
|
||||
bl_context = "scene"
|
||||
|
||||
# Every panel has a draw function. This draws all the layout of the panel,
|
||||
# including all of its labels, buttons, and so on. Note that if the panel is
|
||||
# hidden, this draw function will not be called.
|
||||
def draw(self, context):
|
||||
# Before drawing any content, the panel must load any dynamic variables
|
||||
# that it will show. Most panels have dynamic content, which display
|
||||
# changing data from your BIM model. You must load that data first. The
|
||||
# two lines are always the same, "if not loaded, then load".
|
||||
if not DemoData.is_loaded:
|
||||
# Each panel should have its own data class that it loads data from.
|
||||
# Note that we only load data if it hasn't already been loaded.
|
||||
# This is because interface panels are drawn continuously. This
|
||||
# draw() function will be called every time you scroll or move your
|
||||
# mouse over it. Loading data is slow, so we only refresh data when
|
||||
# we have to.
|
||||
DemoData.load()
|
||||
|
||||
# Interface panels often show properties. For convenience, define where
|
||||
# the properties are stored for the module.
|
||||
self.props = context.scene.BIMDemoProperties
|
||||
|
||||
# This defines a new "row" in our layout. When a new row is defined, the
|
||||
# things on that row, like buttons, labels, and input fields, show on a
|
||||
# new line.
|
||||
row = self.layout.row()
|
||||
# This is the simplest interface element - a label placed in our row.
|
||||
# If you enable the "Icon Viewer" add-on, you can view a list of
|
||||
# Blender's built-in icons to choose from in the Blender text editor's
|
||||
# side panel.
|
||||
row.label(text="This is a demo panel", icon="INFO")
|
||||
|
||||
# Our interface can contain simple logic. For example, if we don't have
|
||||
# an IFC project, show an error message and don't draw anything else.
|
||||
# The ui.py should always have very simple logic. Details like how to
|
||||
# determine whether we have a project is delegated to the data loader.
|
||||
if not DemoData.data["has_project"]:
|
||||
row = self.layout.row()
|
||||
row.label(text="Load or create an IFC project first", icon="ERROR")
|
||||
return
|
||||
|
||||
row = self.layout.row()
|
||||
# When you want to show a button in the interface, you have to specify
|
||||
# an operator. Operators are defined in operator.py. You reference an
|
||||
# operator using its unique ID.
|
||||
row.operator("bim.demonstrate_hello_world")
|
||||
|
||||
# Blender properties can be used to affect what your panel shows. Notice
|
||||
# how the logic is very simple. Only use simple boolean predicates and
|
||||
# loops in your layout. No complex logic should ever been seen in the
|
||||
# interface code.
|
||||
if self.props.message:
|
||||
row = self.layout.row()
|
||||
row.label(text=self.props.message)
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Project Name")
|
||||
# Sometimes you need data that doesn't come from Blender properties,
|
||||
# like from your IFC model. In this case, just get some data from your
|
||||
# data loader. For those familiar with how templating languages work,
|
||||
# this is exactly the same.
|
||||
row.label(text=DemoData.data["project_name"])
|
||||
|
||||
row = self.layout.row()
|
||||
# We've seen how to show text labels and buttons using operators. What
|
||||
# about text input fields, number sliders, dropdowns, checkboxes, and
|
||||
# scrollable lists? The way Blender works is that you simply show a
|
||||
# Blender property in your interface. The data type of that property
|
||||
# determines which UI widget is shown. A text property will show a text
|
||||
# field. A number property will show a number slider. An enum property
|
||||
# will show a drop down. And so on. In this case, our name property is a
|
||||
# text data type, so expect to see a text input field show up here.
|
||||
row.prop(self.props, "name")
|
||||
row = self.layout.row()
|
||||
# Here's another button, referencing another operator.
|
||||
row.operator("bim.demonstrate_rename_project")
|
||||
|
||||
if self.props.show_hints:
|
||||
row = self.layout.row()
|
||||
row.label(text="Name cannot be blank!")
|
||||
@@ -20,35 +20,29 @@ import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.LoadInformation,
|
||||
operator.LoadDocumentReferences,
|
||||
operator.DisableDocumentEditingUI,
|
||||
operator.EnableEditingDocument,
|
||||
operator.DisableEditingDocument,
|
||||
operator.AddInformation,
|
||||
operator.AddDocumentReference,
|
||||
operator.EditInformation,
|
||||
operator.EditDocumentReference,
|
||||
operator.RemoveDocument,
|
||||
operator.EnableAssigningDocument,
|
||||
operator.DisableAssigningDocument,
|
||||
operator.AddInformation,
|
||||
operator.AssignDocument,
|
||||
operator.DisableDocumentEditingUI,
|
||||
operator.DisableEditingDocument,
|
||||
operator.EditDocument,
|
||||
operator.EnableEditingDocument,
|
||||
operator.LoadDocument,
|
||||
operator.LoadParentDocument,
|
||||
operator.LoadProjectDocuments,
|
||||
operator.RemoveDocument,
|
||||
operator.UnassignDocument,
|
||||
prop.Document,
|
||||
prop.BIMDocumentProperties,
|
||||
prop.BIMObjectDocumentProperties,
|
||||
ui.BIM_PT_documents,
|
||||
ui.BIM_PT_object_documents,
|
||||
ui.BIM_UL_documents,
|
||||
ui.BIM_UL_object_documents,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
bpy.types.Scene.BIMDocumentProperties = bpy.props.PointerProperty(type=prop.BIMDocumentProperties)
|
||||
bpy.types.Object.BIMObjectDocumentProperties = bpy.props.PointerProperty(type=prop.BIMObjectDocumentProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
del bpy.types.Scene.BIMDocumentProperties
|
||||
del bpy.types.Object.BIMObjectDocumentProperties
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.schema
|
||||
import blenderbim.tool as tool
|
||||
|
||||
|
||||
def refresh():
|
||||
DocumentData.is_loaded = False
|
||||
ObjectDocumentData.is_loaded = False
|
||||
|
||||
|
||||
class DocumentData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {
|
||||
"total_information": cls.total_information(),
|
||||
"parent_document": cls.parent_document(),
|
||||
}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def total_information(cls):
|
||||
return len(
|
||||
[
|
||||
rel
|
||||
for rel in tool.Ifc.get().by_type("IfcProject")[0].HasAssociations or []
|
||||
if rel.is_a("IfcRelAssociatesDocument") and rel.RelatingDocument.is_a("IfcDocumentInformation")
|
||||
]
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def parent_document(cls):
|
||||
props = bpy.context.scene.BIMDocumentProperties
|
||||
if len(props.breadcrumbs):
|
||||
parent = tool.Ifc.get().by_id(int(props.breadcrumbs[-1].name))
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
return str(parent.DocumentId)
|
||||
return str(parent.Identification)
|
||||
return ""
|
||||
|
||||
|
||||
class ObjectDocumentData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {
|
||||
"documents": cls.documents(),
|
||||
}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def documents(cls):
|
||||
results = []
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
if not element:
|
||||
return results
|
||||
for rel in getattr(element, "HasAssociations", []):
|
||||
if rel.is_a("IfcRelAssociatesDocument"):
|
||||
if not rel.RelatingDocument.is_a("IfcDocumentReference"):
|
||||
continue
|
||||
|
||||
name = rel.RelatingDocument.Name
|
||||
if not name and rel.RelatingDocument.ReferencedDocument:
|
||||
name = rel.RelatingDocument.ReferencedDocument.Name
|
||||
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
identification = rel.RelatingDocument.ItemReference
|
||||
if not identification and rel.RelatingDocument.ReferencedDocument:
|
||||
identification = rel.RelatingDocument.ReferencedDocument.DocumentId
|
||||
else:
|
||||
identification = rel.RelatingDocument.Identification
|
||||
if not identification and rel.RelatingDocument.ReferencedDocument:
|
||||
identification = rel.RelatingDocument.ReferencedDocument.Identification
|
||||
|
||||
location = rel.RelatingDocument.Location
|
||||
if location is None and rel.RelatingDocument.ReferencedDocument:
|
||||
location = rel.RelatingDocument.ReferencedDocument.Location
|
||||
if location:
|
||||
if not "://" in location:
|
||||
if not os.path.isabs(location):
|
||||
location = os.path.abspath(os.path.join(os.path.dirname(tool.Ifc.get_path()), location))
|
||||
location = "file://" + location
|
||||
|
||||
results.append(
|
||||
{
|
||||
"id": rel.RelatingDocument.id(),
|
||||
"identification": identification,
|
||||
"name": name,
|
||||
"location": location,
|
||||
}
|
||||
)
|
||||
return results
|
||||
@@ -1,5 +1,5 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
|
||||
# Copyright (C) 2020, 2021, 2022 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
@@ -20,303 +20,133 @@ import bpy
|
||||
import json
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.attribute
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.document as core
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.api.document.data import Data
|
||||
|
||||
|
||||
class LoadInformation(bpy.types.Operator):
|
||||
bl_idname = "bim.load_information"
|
||||
bl_label = "Load Information"
|
||||
class LoadProjectDocuments(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.load_project_documents"
|
||||
bl_label = "Load Project Documents"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
props = context.scene.BIMDocumentProperties
|
||||
props.documents.clear()
|
||||
for information_id, information in Data.information.items():
|
||||
new = props.documents.add()
|
||||
new.name = information["Name"] or "Unnamed"
|
||||
if self.file.schema == "IFC2X3":
|
||||
new.identification = information["DocumentId"] or "*"
|
||||
else:
|
||||
new.identification = information["Identification"] or "*"
|
||||
new.ifc_definition_id = information_id
|
||||
props.is_editing = "information"
|
||||
bpy.ops.bim.disable_editing_document()
|
||||
return {"FINISHED"}
|
||||
def _execute(self, context):
|
||||
core.load_project_documents(tool.Document)
|
||||
|
||||
|
||||
class LoadDocumentReferences(bpy.types.Operator):
|
||||
bl_idname = "bim.load_document_references"
|
||||
bl_label = "Load Document References"
|
||||
class LoadDocument(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.load_document"
|
||||
bl_label = "Load Document"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
document: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.load_document(tool.Document, document=tool.Ifc.get().by_id(self.document))
|
||||
|
||||
|
||||
class LoadParentDocument(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.load_parent_document"
|
||||
bl_label = "Load Parent Document"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
props = context.scene.BIMDocumentProperties
|
||||
props.documents.clear()
|
||||
for reference_id, reference in Data.references.items():
|
||||
new = props.documents.add()
|
||||
new.name = reference["Name"] or "Unnamed"
|
||||
if self.file.schema == "IFC2X3":
|
||||
new.identification = reference["ItemReference"] or "*"
|
||||
else:
|
||||
new.identification = reference["Identification"] or "*"
|
||||
new.ifc_definition_id = reference_id
|
||||
props.is_editing = "reference"
|
||||
bpy.ops.bim.disable_editing_document()
|
||||
return {"FINISHED"}
|
||||
def _execute(self, context):
|
||||
core.load_parent_document(tool.Document)
|
||||
|
||||
|
||||
class DisableDocumentEditingUI(bpy.types.Operator):
|
||||
class DisableDocumentEditingUI(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.disable_document_editing_ui"
|
||||
bl_label = "Disable Document Editing UI"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
context.scene.BIMDocumentProperties.is_editing = ""
|
||||
bpy.ops.bim.disable_editing_document()
|
||||
return {"FINISHED"}
|
||||
def _execute(self, context):
|
||||
core.disable_document_editing_ui(tool.Document)
|
||||
|
||||
|
||||
class EnableEditingDocument(bpy.types.Operator):
|
||||
class EnableEditingDocument(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.enable_editing_document"
|
||||
bl_label = "Enable Editing Document"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
document: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMDocumentProperties
|
||||
props.document_attributes.clear()
|
||||
|
||||
if props.is_editing == "information":
|
||||
data = Data.information[self.document]
|
||||
ifc_class = "IfcDocumentInformation"
|
||||
elif props.is_editing == "reference":
|
||||
data = Data.references[self.document]
|
||||
ifc_class = "IfcDocumentReference"
|
||||
|
||||
for attribute in IfcStore.get_schema().declaration_by_name(ifc_class).all_attributes():
|
||||
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
|
||||
if data_type == "entity":
|
||||
continue
|
||||
new = props.document_attributes.add()
|
||||
new.name = attribute.name()
|
||||
new.is_null = data[attribute.name()] is None
|
||||
new.is_optional = attribute.optional()
|
||||
new.data_type = data_type
|
||||
if data_type == "string":
|
||||
new.string_value = "" if new.is_null else data[attribute.name()]
|
||||
elif data_type == "enum":
|
||||
new.enum_items = json.dumps(ifcopenshell.util.attribute.get_enum_items(attribute))
|
||||
if data[attribute.name()]:
|
||||
new.enum_value = data[attribute.name()]
|
||||
props.active_document_id = self.document
|
||||
return {"FINISHED"}
|
||||
def _execute(self, context):
|
||||
core.enable_editing_document(tool.Document, document=tool.Ifc.get().by_id(self.document))
|
||||
|
||||
|
||||
class DisableEditingDocument(bpy.types.Operator):
|
||||
class DisableEditingDocument(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.disable_editing_document"
|
||||
bl_label = "Disable Editing Document"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
context.scene.BIMDocumentProperties.active_document_id = 0
|
||||
return {"FINISHED"}
|
||||
def _execute(self, context):
|
||||
core.disable_editing_document(tool.Document)
|
||||
|
||||
|
||||
class AddInformation(bpy.types.Operator):
|
||||
class AddInformation(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.add_information"
|
||||
bl_label = "Add Information"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
result = ifcopenshell.api.run("document.add_information", IfcStore.get_file())
|
||||
Data.load(IfcStore.get_file())
|
||||
bpy.ops.bim.load_information()
|
||||
bpy.ops.bim.enable_editing_document(document=result.id())
|
||||
return {"FINISHED"}
|
||||
core.add_information(tool.Ifc, tool.Document)
|
||||
|
||||
|
||||
class AddDocumentReference(bpy.types.Operator):
|
||||
class AddDocumentReference(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.add_document_reference"
|
||||
bl_label = "Add Document Reference"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
result = ifcopenshell.api.run("document.add_reference", IfcStore.get_file())
|
||||
Data.load(IfcStore.get_file())
|
||||
bpy.ops.bim.load_document_references()
|
||||
bpy.ops.bim.enable_editing_document(document=result.id())
|
||||
return {"FINISHED"}
|
||||
core.add_reference(tool.Ifc, tool.Document)
|
||||
|
||||
|
||||
class EditInformation(bpy.types.Operator):
|
||||
bl_idname = "bim.edit_information"
|
||||
class EditDocument(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.edit_document"
|
||||
bl_label = "Edit Information"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMDocumentProperties
|
||||
attributes = {}
|
||||
for attribute in props.document_attributes:
|
||||
if attribute.is_null:
|
||||
attributes[attribute.name] = None
|
||||
elif attribute.enum_items:
|
||||
attributes[attribute.name] = attribute.enum_value
|
||||
else:
|
||||
attributes[attribute.name] = attribute.string_value
|
||||
self.file = IfcStore.get_file()
|
||||
ifcopenshell.api.run(
|
||||
"document.edit_information",
|
||||
self.file,
|
||||
**{"information": self.file.by_id(props.active_document_id), "attributes": attributes}
|
||||
)
|
||||
Data.load(IfcStore.get_file())
|
||||
bpy.ops.bim.load_information()
|
||||
return {"FINISHED"}
|
||||
core.edit_document(tool.Ifc, tool.Document, document=tool.Ifc.get().by_id(props.active_document_id))
|
||||
|
||||
|
||||
class EditDocumentReference(bpy.types.Operator):
|
||||
bl_idname = "bim.edit_document_reference"
|
||||
bl_label = "Edit Document Reference"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMDocumentProperties
|
||||
attributes = {}
|
||||
for attribute in props.document_attributes:
|
||||
if attribute.is_null:
|
||||
attributes[attribute.name] = None
|
||||
else:
|
||||
attributes[attribute.name] = attribute.string_value
|
||||
self.file = IfcStore.get_file()
|
||||
ifcopenshell.api.run(
|
||||
"document.edit_reference",
|
||||
self.file,
|
||||
**{"reference": self.file.by_id(props.active_document_id), "attributes": attributes}
|
||||
)
|
||||
Data.load(IfcStore.get_file())
|
||||
bpy.ops.bim.load_document_references()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RemoveDocument(bpy.types.Operator):
|
||||
class RemoveDocument(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.remove_document"
|
||||
bl_label = "Remove Document"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
document: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMDocumentProperties
|
||||
self.file = IfcStore.get_file()
|
||||
ifcopenshell.api.run("document.remove_document", self.file, **{"document": self.file.by_id(self.document)})
|
||||
Data.load(IfcStore.get_file())
|
||||
if props.is_editing == "information":
|
||||
bpy.ops.bim.load_information()
|
||||
elif props.is_editing == "reference":
|
||||
bpy.ops.bim.load_document_references()
|
||||
return {"FINISHED"}
|
||||
core.remove_document(tool.Ifc, tool.Document, document=tool.Ifc.get().by_id(self.document))
|
||||
|
||||
|
||||
class EnableAssigningDocument(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_assigning_document"
|
||||
bl_label = "Enable Assigning Document"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
|
||||
props = obj.BIMObjectDocumentProperties
|
||||
if props.available_document_types == "IfcDocumentInformation":
|
||||
bpy.ops.bim.load_information()
|
||||
elif props.available_document_types == "IfcDocumentReference":
|
||||
bpy.ops.bim.load_document_references()
|
||||
props.is_adding = props.available_document_types
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DisableAssigningDocument(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_assigning_document"
|
||||
bl_label = "Disable Assigning Document"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
|
||||
props = obj.BIMObjectDocumentProperties
|
||||
props.is_adding = ""
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AssignDocument(bpy.types.Operator):
|
||||
class AssignDocument(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.assign_document"
|
||||
bl_label = "Assign Document"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
document: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
document = tool.Ifc.get().by_id(self.document)
|
||||
objs = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects
|
||||
for obj in objs:
|
||||
obj_id = obj.BIMObjectProperties.ifc_definition_id
|
||||
if not obj_id:
|
||||
continue
|
||||
ifcopenshell.api.run(
|
||||
"document.assign_document",
|
||||
self.file,
|
||||
**{
|
||||
"product": self.file.by_id(obj_id),
|
||||
"document": self.file.by_id(self.document),
|
||||
}
|
||||
)
|
||||
Data.load(self.file, obj_id)
|
||||
return {"FINISHED"}
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element:
|
||||
core.assign_document(tool.Ifc, product=element, document=document)
|
||||
|
||||
|
||||
class UnassignDocument(bpy.types.Operator):
|
||||
class UnassignDocument(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.unassign_document"
|
||||
bl_label = "Unassign Document"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
document: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
document = tool.Ifc.get().by_id(self.document)
|
||||
objs = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects
|
||||
for obj in objs:
|
||||
obj_id = obj.BIMObjectProperties.ifc_definition_id
|
||||
if not obj_id:
|
||||
continue
|
||||
ifcopenshell.api.run(
|
||||
"document.unassign_document",
|
||||
self.file,
|
||||
**{
|
||||
"product": self.file.by_id(obj_id),
|
||||
"document": self.file.by_id(self.document),
|
||||
}
|
||||
)
|
||||
Data.load(self.file, obj_id)
|
||||
return {"FINISHED"}
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element:
|
||||
core.unassign_document(tool.Ifc, product=element, document=document)
|
||||
|
||||
@@ -34,6 +34,7 @@ from bpy.props import (
|
||||
class Document(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
identification: StringProperty(name="Identification")
|
||||
is_information: BoolProperty(name="Is Information")
|
||||
ifc_definition_id: IntProperty(name="IFC Definition ID")
|
||||
|
||||
|
||||
@@ -41,12 +42,6 @@ class BIMDocumentProperties(PropertyGroup):
|
||||
document_attributes: CollectionProperty(name="Document Attributes", type=Attribute)
|
||||
active_document_id: IntProperty(name="Active Document Id")
|
||||
documents: CollectionProperty(name="Documents", type=Document)
|
||||
breadcrumbs: CollectionProperty(name="Breadcrumbs", type=StrProperty)
|
||||
active_document_index: IntProperty(name="Active Document Index")
|
||||
is_editing: StringProperty(name="Is Editing")
|
||||
|
||||
|
||||
class BIMObjectDocumentProperties(PropertyGroup):
|
||||
is_adding: StringProperty(name="Is Adding")
|
||||
available_document_types: EnumProperty(
|
||||
items=[(d, d, "") for d in ["IfcDocumentInformation", "IfcDocumentReference"]], name="Available Document Types"
|
||||
)
|
||||
is_editing: BoolProperty(name="Is Editing", default=False)
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
from bpy.types import Panel, UIList
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.helper import draw_attributes
|
||||
from ifcopenshell.api.document.data import Data
|
||||
from blenderbim.bim.module.document.data import DocumentData, ObjectDocumentData
|
||||
|
||||
|
||||
class BIM_PT_documents(Panel):
|
||||
@@ -29,46 +29,50 @@ class BIM_PT_documents(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_collaboration"
|
||||
bl_parent_id = "BIM_PT_project_setup"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return IfcStore.get_file()
|
||||
|
||||
def draw(self, context):
|
||||
if not Data.is_loaded:
|
||||
Data.load(IfcStore.get_file())
|
||||
if not DocumentData.is_loaded:
|
||||
DocumentData.load()
|
||||
|
||||
self.props = context.scene.BIMDocumentProperties
|
||||
|
||||
if not self.props.is_editing or self.props.is_editing == "information":
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="{} Documents Found".format(len(Data.information)), icon="FILE")
|
||||
if self.props.is_editing == "information":
|
||||
row.operator("bim.add_information", text="", icon="ADD")
|
||||
row.operator("bim.disable_document_editing_ui", text="", icon="CANCEL")
|
||||
else:
|
||||
row.operator("bim.load_information", text="", icon="IMPORT")
|
||||
|
||||
if not self.props.is_editing or self.props.is_editing == "reference":
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="{} References Found".format(len(Data.references)), icon="FILE_HIDDEN")
|
||||
if self.props.is_editing == "reference":
|
||||
row.operator("bim.add_document_reference", text="", icon="ADD")
|
||||
row.operator("bim.disable_document_editing_ui", text="", icon="CANCEL")
|
||||
else:
|
||||
row.operator("bim.load_document_references", text="", icon="IMPORT")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="{} Documents Found".format(DocumentData.data["total_information"]), icon="FILE")
|
||||
if self.props.is_editing:
|
||||
self.layout.template_list(
|
||||
"BIM_UL_documents", "", self.props, "documents", self.props, "active_document_index"
|
||||
)
|
||||
row.operator("bim.disable_document_editing_ui", text="", icon="CANCEL")
|
||||
else:
|
||||
row.operator("bim.load_project_documents", text="", icon="IMPORT")
|
||||
|
||||
if not self.props.is_editing:
|
||||
return
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
if self.props.breadcrumbs:
|
||||
row.operator("bim.load_parent_document", text="", icon="FRAME_PREV")
|
||||
row.label(text=DocumentData.data["parent_document"])
|
||||
else:
|
||||
row.alignment = "RIGHT"
|
||||
row.operator("bim.add_information", text="", icon="ADD")
|
||||
if self.props.breadcrumbs:
|
||||
row.operator("bim.add_document_reference", text="", icon="FILE_HIDDEN")
|
||||
|
||||
if self.props.active_document_id:
|
||||
self.draw_editable_ui(context)
|
||||
row.operator("bim.edit_document", text="", icon="CHECKMARK")
|
||||
row.operator("bim.disable_editing_document", text="", icon="CANCEL")
|
||||
elif self.props.documents and self.props.active_document_index < len(self.props.documents):
|
||||
ifc_definition_id = self.props.documents[self.props.active_document_index].ifc_definition_id
|
||||
row.operator("bim.enable_editing_document", text="", icon="GREASEPENCIL").document = ifc_definition_id
|
||||
row.operator("bim.remove_document", text="", icon="X").document = ifc_definition_id
|
||||
|
||||
def draw_editable_ui(self, context):
|
||||
draw_attributes(self.props.document_attributes, self.layout)
|
||||
self.layout.template_list("BIM_UL_documents", "", self.props, "documents", self.props, "active_document_index")
|
||||
|
||||
if self.props.active_document_id:
|
||||
draw_attributes(self.props.document_attributes, self.layout)
|
||||
|
||||
|
||||
class BIM_PT_object_documents(Panel):
|
||||
@@ -89,88 +93,64 @@ class BIM_PT_object_documents(Panel):
|
||||
return bool(context.active_object.BIMObjectProperties.ifc_definition_id)
|
||||
|
||||
def draw(self, context):
|
||||
if not ObjectDocumentData.is_loaded:
|
||||
ObjectDocumentData.load()
|
||||
|
||||
obj = context.active_object
|
||||
self.oprops = obj.BIMObjectProperties
|
||||
self.sprops = context.scene.BIMDocumentProperties
|
||||
self.props = obj.BIMObjectDocumentProperties
|
||||
self.props = context.scene.BIMDocumentProperties
|
||||
self.file = IfcStore.get_file()
|
||||
if not Data.is_loaded:
|
||||
Data.load(IfcStore.get_file())
|
||||
if self.oprops.ifc_definition_id not in Data.products:
|
||||
Data.load(IfcStore.get_file(), self.oprops.ifc_definition_id)
|
||||
|
||||
self.draw_add_ui()
|
||||
|
||||
document_ids = Data.products[self.oprops.ifc_definition_id]
|
||||
|
||||
if not document_ids:
|
||||
if not ObjectDocumentData.data["documents"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="No Documents", icon="FILE")
|
||||
|
||||
for document_id in document_ids:
|
||||
try:
|
||||
document = Data.information[document_id]
|
||||
document_type = "IfcDocumentInformation"
|
||||
icon = "FILE"
|
||||
except:
|
||||
document = Data.references[document_id]
|
||||
document_type = "IfcDocumentReference"
|
||||
icon = "FILE_HIDDEN"
|
||||
for document in ObjectDocumentData.data["documents"]:
|
||||
row = self.layout.row(align=True)
|
||||
if self.file.schema == "IFC2X3":
|
||||
if document_type == "IfcDocumentInformation":
|
||||
row.label(text=document.get("DocumentId") or "*", icon=icon)
|
||||
elif document_type == "IfcDocumentReference":
|
||||
row.label(text=document.get("ItemReference") or "*", icon=icon)
|
||||
else:
|
||||
row.label(text=document.get("Identification") or "*", icon=icon)
|
||||
row.label(text=document.get("Name") or "Unnamed")
|
||||
row.operator("bim.unassign_document", text="", icon="X").document = document_id
|
||||
row.label(text=document["identification"] or "*", icon="FILE")
|
||||
row.label(text=document["name"] or "Unnamed")
|
||||
if document["location"]:
|
||||
row.operator("bim.open_uri", icon="URL", text="").uri = document["location"]
|
||||
row.operator("bim.unassign_document", text="", icon="X").document = document["id"]
|
||||
|
||||
def draw_add_ui(self):
|
||||
if self.props.is_adding:
|
||||
if not self.props.is_editing:
|
||||
row = self.layout.row(align=True)
|
||||
icon = "FILE" if self.props.is_adding == "IfcDocumentInformation" else "FILE_HIDDEN"
|
||||
row.label(text="Adding {}".format(self.props.is_adding), icon=icon)
|
||||
row.operator("bim.disable_assigning_document", text="", icon="CANCEL")
|
||||
self.layout.template_list(
|
||||
"BIM_UL_object_documents",
|
||||
"",
|
||||
self.sprops,
|
||||
"documents",
|
||||
self.sprops,
|
||||
"active_document_index",
|
||||
)
|
||||
row.operator("bim.load_project_documents", text="Assign Document References", icon="ADD")
|
||||
return
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
if self.props.breadcrumbs:
|
||||
row.operator("bim.load_parent_document", text="", icon="FRAME_PREV")
|
||||
row.label(text=DocumentData.data["parent_document"])
|
||||
else:
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.props, "available_document_types", text="")
|
||||
row.operator("bim.enable_assigning_document", text="", icon="ADD")
|
||||
row.alignment = "RIGHT"
|
||||
|
||||
if self.props.documents and self.props.active_document_index < len(self.props.documents):
|
||||
document = self.props.documents[self.props.active_document_index]
|
||||
if not document.is_information:
|
||||
row.operator("bim.assign_document", text="", icon="ADD").document = document.ifc_definition_id
|
||||
row.operator("bim.disable_document_editing_ui", text="", icon="CANCEL")
|
||||
|
||||
self.layout.template_list("BIM_UL_documents", "", self.props, "documents", self.props, "active_document_index")
|
||||
|
||||
|
||||
class BIM_UL_documents(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if item:
|
||||
row = layout.row(align=True)
|
||||
row.label(text=item.identification)
|
||||
row.label(text=item.name)
|
||||
if context.scene.BIMDocumentProperties.active_document_id == item.ifc_definition_id:
|
||||
if context.scene.BIMDocumentProperties.is_editing == "information":
|
||||
row.operator("bim.edit_information", text="", icon="CHECKMARK")
|
||||
elif context.scene.BIMDocumentProperties.is_editing == "reference":
|
||||
row.operator("bim.edit_document_reference", text="", icon="CHECKMARK")
|
||||
row.operator("bim.disable_editing_document", text="", icon="CANCEL")
|
||||
elif context.scene.BIMDocumentProperties.active_document_id:
|
||||
row.operator("bim.remove_document", text="", icon="X").document = item.ifc_definition_id
|
||||
else:
|
||||
op = row.operator("bim.enable_editing_document", text="", icon="GREASEPENCIL")
|
||||
|
||||
if item.is_information:
|
||||
op = row.operator("bim.load_document", text="", emboss=False, icon="DISCLOSURE_TRI_RIGHT")
|
||||
op.document = item.ifc_definition_id
|
||||
row.operator("bim.remove_document", text="", icon="X").document = item.ifc_definition_id
|
||||
row.label(text="", icon="FILE")
|
||||
else:
|
||||
row.label(text="", icon="BLANK1")
|
||||
row.label(text="", icon="FILE_HIDDEN")
|
||||
|
||||
|
||||
class BIM_UL_object_documents(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if item:
|
||||
row = layout.row(align=True)
|
||||
row.label(text=item.identification)
|
||||
row.label(text=item.name)
|
||||
row.operator("bim.assign_document", text="", icon="ADD").document = item.ifc_definition_id
|
||||
split1 = row.split(factor=0.1)
|
||||
split1.label(text=item.identification)
|
||||
split2 = split1.split(factor=0.9)
|
||||
split2.label(text=item.name)
|
||||
|
||||
@@ -36,6 +36,8 @@ classes = (
|
||||
operator.CopyGrid,
|
||||
operator.CreateDrawing,
|
||||
operator.CreateSheets,
|
||||
operator.DisableEditingDrawings,
|
||||
operator.DisableEditingSheets,
|
||||
operator.DisableEditingText,
|
||||
operator.DisableEditingTextProduct,
|
||||
operator.EditText,
|
||||
@@ -44,10 +46,10 @@ classes = (
|
||||
operator.EnableEditingText,
|
||||
operator.EnableEditingTextProduct,
|
||||
operator.GenerateReferences,
|
||||
operator.LoadDrawings,
|
||||
operator.LoadSheets,
|
||||
operator.OpenSheet,
|
||||
operator.OpenView,
|
||||
operator.OpenViewCamera,
|
||||
operator.RefreshDrawingList,
|
||||
operator.RemoveDrawing,
|
||||
operator.RemoveDrawingStyle,
|
||||
operator.RemoveDrawingStyleAttribute,
|
||||
@@ -67,12 +69,13 @@ classes = (
|
||||
prop.BIMTextProperties,
|
||||
ui.BIM_PT_camera,
|
||||
ui.BIM_PT_drawing_underlay,
|
||||
ui.BIM_PT_annotation_utilities,
|
||||
ui.BIM_PT_sheets,
|
||||
ui.BIM_PT_drawings,
|
||||
ui.BIM_PT_schedules,
|
||||
ui.BIM_PT_sheets,
|
||||
ui.BIM_PT_text,
|
||||
ui.BIM_PT_annotation_utilities,
|
||||
ui.BIM_UL_drawinglist,
|
||||
ui.BIM_UL_sheets,
|
||||
gizmos.UglyDotGizmo,
|
||||
gizmos.DotGizmo,
|
||||
gizmos.DimensionLabelGizmo,
|
||||
|
||||
@@ -35,13 +35,13 @@ class Annotator:
|
||||
return float(sizes[str(size)])
|
||||
|
||||
@staticmethod
|
||||
def add_text(context, related_element=None):
|
||||
def add_text(related_element=None):
|
||||
curve = bpy.data.curves.new(type="FONT", name="Text")
|
||||
curve.body = "TEXT"
|
||||
obj = bpy.data.objects.new("Text", curve)
|
||||
obj.matrix_world = context.scene.camera.matrix_world
|
||||
obj.matrix_world = bpy.context.scene.camera.matrix_world
|
||||
if related_element is None:
|
||||
location, _, _, _ = Annotator.get_placeholder_coords(context)
|
||||
location, _, _, _ = Annotator.get_placeholder_coords(bpy.context)
|
||||
else:
|
||||
obj.data.BIMTextProperties.related_element = related_element
|
||||
location = related_element.location
|
||||
@@ -50,12 +50,12 @@ class Annotator:
|
||||
font = bpy.data.fonts.get("OpenGost TypeB TT")
|
||||
if not font:
|
||||
font = bpy.data.fonts.load(
|
||||
os.path.join(context.scene.BIMProperties.data_dir, "fonts", "OpenGost Type B TT.ttf")
|
||||
os.path.join(bpy.context.scene.BIMProperties.data_dir, "fonts", "OpenGost Type B TT.ttf")
|
||||
)
|
||||
font.name = "OpenGost Type B TT"
|
||||
obj.data.font = font
|
||||
obj.data.BIMTextProperties.font_size = "2.5"
|
||||
collection = context.scene.camera.users_collection[0]
|
||||
collection = bpy.context.scene.camera.users_collection[0]
|
||||
collection.objects.link(obj)
|
||||
Annotator.resize_text(obj)
|
||||
return obj
|
||||
@@ -83,9 +83,9 @@ class Annotator:
|
||||
text_obj.data.size = font_size
|
||||
|
||||
@staticmethod
|
||||
def add_line_to_annotation(obj, context, co1=None, co2=None):
|
||||
def add_line_to_annotation(obj, co1=None, co2=None):
|
||||
if co1 is None:
|
||||
co1, co2, _, _ = Annotator.get_placeholder_coords(context)
|
||||
co1, co2, _, _ = Annotator.get_placeholder_coords()
|
||||
co1 = obj.matrix_world.inverted() @ co1
|
||||
co2 = obj.matrix_world.inverted() @ co2
|
||||
if isinstance(obj.data, bpy.types.Mesh):
|
||||
@@ -102,8 +102,8 @@ class Annotator:
|
||||
return obj
|
||||
|
||||
@staticmethod
|
||||
def add_plane_to_annotation(obj, context):
|
||||
co1, co2, co3, co4 = Annotator.get_placeholder_coords(context)
|
||||
def add_plane_to_annotation(obj):
|
||||
co1, co2, co3, co4 = Annotator.get_placeholder_coords()
|
||||
co1 = obj.matrix_world.inverted() @ co1 # bot left
|
||||
co2 = obj.matrix_world.inverted() @ co2 # top left
|
||||
co3 = obj.matrix_world.inverted() @ co3 # bot right
|
||||
@@ -151,8 +151,8 @@ class Annotator:
|
||||
return obj
|
||||
|
||||
@staticmethod
|
||||
def get_annotation_obj(object_type, data_type, context):
|
||||
collection = context.scene.camera.users_collection[0]
|
||||
def get_annotation_obj(object_type, data_type):
|
||||
collection = bpy.context.scene.camera.users_collection[0]
|
||||
if object_type == "TEXT":
|
||||
obj = bpy.data.objects.new(object_type, None)
|
||||
collection.objects.link(obj)
|
||||
@@ -179,11 +179,15 @@ class Annotator:
|
||||
return obj
|
||||
|
||||
@staticmethod
|
||||
def get_placeholder_coords(context):
|
||||
camera = context.scene.camera
|
||||
def get_placeholder_coords():
|
||||
camera = bpy.context.scene.camera
|
||||
z_offset = camera.matrix_world.to_quaternion() @ Vector((0, 0, -1))
|
||||
if context.scene.render.resolution_x > context.scene.render.resolution_y:
|
||||
y = camera.data.ortho_scale * (context.scene.render.resolution_y / context.scene.render.resolution_x) / 4
|
||||
if bpy.context.scene.render.resolution_x > bpy.context.scene.render.resolution_y:
|
||||
y = (
|
||||
camera.data.ortho_scale
|
||||
* (bpy.context.scene.render.resolution_y / bpy.context.scene.render.resolution_x)
|
||||
/ 4
|
||||
)
|
||||
else:
|
||||
y = camera.data.ortho_scale / 4
|
||||
y_offset = camera.matrix_world.to_quaternion() @ Vector((0, y, 0))
|
||||
|
||||
@@ -23,6 +23,8 @@ import blenderbim.tool as tool
|
||||
|
||||
def refresh():
|
||||
TextData.is_loaded = False
|
||||
SheetsData.is_loaded = False
|
||||
DrawingsData.is_loaded = False
|
||||
|
||||
|
||||
class TextData:
|
||||
@@ -54,3 +56,41 @@ class TextData:
|
||||
for rel in element.HasAssignments:
|
||||
if rel.is_a("IfcRelAssignsToProduct"):
|
||||
return rel.RelatingProduct.Name or "Unnamed"
|
||||
|
||||
|
||||
class SheetsData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {"total_sheets": cls.total_sheets()}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def total_sheets(cls):
|
||||
return len([d for d in tool.Ifc.get().by_type("IfcDocumentInformation") if d.Scope == "DOCUMENTATION"])
|
||||
|
||||
|
||||
class DrawingsData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {"total_drawings": cls.total_drawings(), "location_hint": cls.location_hint()}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def total_drawings(cls):
|
||||
return len([e for e in tool.Ifc.get().by_type("IfcAnnotation") if e.ObjectType == "DRAWING"])
|
||||
|
||||
@classmethod
|
||||
def location_hint(cls):
|
||||
if bpy.context.scene.DocProperties.target_view in ["PLAN_VIEW", "REFLECTED_PLAN_VIEW"]:
|
||||
results = [("0", "Origin", "")]
|
||||
results.extend(
|
||||
[(str(s.id()), s.Name or "Unnamed", "") for s in tool.Ifc.get().by_type("IfcBuildingStorey")]
|
||||
)
|
||||
return results
|
||||
return [(h.upper(), h, "") for h in ["North", "South", "East", "West"]]
|
||||
|
||||
@@ -45,6 +45,3 @@ def set_active_camera_resolution(scene):
|
||||
scene.render.resolution_x = scene.camera.data.BIMCameraProperties.raster_x
|
||||
scene.render.resolution_y = scene.camera.data.BIMCameraProperties.raster_y
|
||||
current_drawing = scene.DocProperties.drawings[scene.DocProperties.current_drawing_index]
|
||||
if scene.camera != current_drawing.camera:
|
||||
scene.DocProperties.current_drawing_index = scene.DocProperties.drawings.find(scene.camera.name.split("/")[1])
|
||||
bpy.ops.bim.activate_view(drawing_index=scene.DocProperties.current_drawing_index)
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
import bpy
|
||||
import math
|
||||
import mathutils.geometry
|
||||
import blenderbim.tool as tool
|
||||
from mathutils import Vector
|
||||
|
||||
# Code taken and updated from https://blenderartists.org/t/detecting-intersection-of-bounding-boxes/457520/2
|
||||
@@ -263,7 +264,8 @@ def get_active_drawing(scene):
|
||||
return None, None
|
||||
try:
|
||||
drawing = props.active_drawing
|
||||
return drawing.camera.users_collection[0], drawing.camera
|
||||
camera = tool.Ifc.get_object(tool.Ifc.get().by_id(drawing.ifc_definition_id))
|
||||
return camera.users_collection[0], camera
|
||||
except (KeyError, IndexError):
|
||||
raise RuntimeError("missing drawing collection")
|
||||
|
||||
|
||||
@@ -65,67 +65,23 @@ class Operator:
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddDrawing(bpy.types.Operator):
|
||||
class AddDrawing(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.add_drawing"
|
||||
bl_label = "Add Drawing"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return IfcStore.get_file()
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
new = context.scene.DocProperties.drawings.add()
|
||||
new.name = "DRAWING {}".format(len(context.scene.DocProperties.drawings))
|
||||
if not bpy.data.collections.get("Views"):
|
||||
context.scene.collection.children.link(bpy.data.collections.new("Views"))
|
||||
views_collection = bpy.data.collections.get("Views")
|
||||
view_collection = bpy.data.collections.new("IfcGroup/" + new.name)
|
||||
views_collection.children.link(view_collection)
|
||||
camera = bpy.data.objects.new(new.name, bpy.data.cameras.new(new.name))
|
||||
camera.location = (0, 0, 1.5) # The view shall be 1.5m above the origin
|
||||
camera.data.type = "ORTHO"
|
||||
camera.data.ortho_scale = 50 # The default of 6m is too small
|
||||
camera.data.clip_end = 10 # A slightly more reasonable default
|
||||
if context.scene.unit_settings.system == "IMPERIAL":
|
||||
camera.data.BIMCameraProperties.diagram_scale = '1/8"=1\'-0"|1/96'
|
||||
else:
|
||||
camera.data.BIMCameraProperties.diagram_scale = "1:100|1/100"
|
||||
context.scene.camera = camera
|
||||
view_collection.objects.link(camera)
|
||||
area = next(area for area in context.screen.areas if area.type == "VIEW_3D")
|
||||
area.spaces[0].region_3d.view_perspective = "CAMERA"
|
||||
new.camera = camera
|
||||
bpy.ops.bim.assign_class(obj=camera.name, ifc_class="IfcAnnotation", predefined_type="DRAWING")
|
||||
bpy.ops.bim.activate_drawing_style()
|
||||
|
||||
bpy.ops.bim.add_group()
|
||||
group = self.file.by_id(sorted(GroupData.groups.keys())[-1])
|
||||
ifcopenshell.api.run("group.edit_group", self.file, **{"group": group, "attributes": {"Name": new.name}})
|
||||
bpy.ops.bim.assign_group(product=camera.name, group=group.id())
|
||||
pset = ifcopenshell.api.run(
|
||||
"pset.add_pset",
|
||||
self.file,
|
||||
**{
|
||||
"product": self.file.by_id(camera.BIMObjectProperties.ifc_definition_id),
|
||||
"name": "EPset_Drawing",
|
||||
},
|
||||
self.props = context.scene.DocProperties
|
||||
hint = self.props.location_hint
|
||||
if self.props.target_view in ["PLAN_VIEW", "REFLECTED_PLAN_VIEW"]:
|
||||
hint = int(hint)
|
||||
core.add_drawing(
|
||||
tool.Ifc,
|
||||
tool.Collector,
|
||||
tool.Drawing,
|
||||
target_view=self.props.target_view,
|
||||
location_hint=hint,
|
||||
)
|
||||
ifcopenshell.api.run(
|
||||
"pset.edit_pset",
|
||||
self.file,
|
||||
**{
|
||||
"pset": pset,
|
||||
"properties": {"TargetView": "PLAN_VIEW", "Scale": "1/100"},
|
||||
"pset_template": blenderbim.bim.schema.ifc.psetqto.get_by_name("EPset_Drawing"),
|
||||
},
|
||||
)
|
||||
PsetData.load(IfcStore.get_file(), camera.BIMObjectProperties.ifc_definition_id)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class CreateDrawing(bpy.types.Operator):
|
||||
@@ -144,6 +100,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
camera = context.scene.camera
|
||||
return (
|
||||
IfcStore.get_file()
|
||||
and camera
|
||||
and camera.type == "CAMERA"
|
||||
and camera.data.type == "ORTHO"
|
||||
and camera.BIMObjectProperties.ifc_definition_id
|
||||
@@ -328,9 +285,10 @@ class CreateDrawing(bpy.types.Operator):
|
||||
# A drawing prioritises a target view context first, followed by a body context as a fallback
|
||||
body_contexts = []
|
||||
target_view_contexts = []
|
||||
target_view = ifcopenshell.util.element.get_psets(self.camera_element)["EPset_Drawing"]["TargetView"]
|
||||
for rep_context in ifc.by_type("IfcGeometricRepresentationContext"):
|
||||
if rep_context.is_a("IfcGeometricRepresentationSubContext"):
|
||||
if rep_context.TargetView == context.scene.camera.data.BIMCameraProperties.target_view:
|
||||
if rep_context.TargetView == target_view:
|
||||
target_view_contexts.append(rep_context.id())
|
||||
continue
|
||||
if rep_context.ContextIdentifier in ["Body", "Facetation"]:
|
||||
@@ -441,7 +399,8 @@ class CreateDrawing(bpy.types.Operator):
|
||||
# https://stackoverflow.com/questions/36018627/sorting-child-elements-with-lxml-based-on-attribute-value
|
||||
group = root.find("{http://www.w3.org/2000/svg}g")
|
||||
# group[:] = sorted(group, key=lambda e : "projection" in e.get("class"))
|
||||
group[:] = reversed(group)
|
||||
if group:
|
||||
group[:] = reversed(group)
|
||||
|
||||
def canonicalise_class_name(self, name):
|
||||
return re.sub("[^0-9a-zA-Z]+", "", name)
|
||||
@@ -562,7 +521,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
return "mat-" + str(element.id())
|
||||
|
||||
|
||||
class AddAnnotation(bpy.types.Operator):
|
||||
class AddAnnotation(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.add_annotation"
|
||||
bl_label = "Add Annotation"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
@@ -573,85 +532,31 @@ class AddAnnotation(bpy.types.Operator):
|
||||
def poll(cls, context):
|
||||
return IfcStore.get_file() and context.scene.camera
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
context.scene.DocProperties.should_draw_decorations = True
|
||||
subcontext = ifcopenshell.util.representation.get_context(
|
||||
IfcStore.get_file(), "Plan", "Annotation", context.scene.camera.data.BIMCameraProperties.target_view
|
||||
)
|
||||
if not subcontext:
|
||||
return {"FINISHED"}
|
||||
# TODO: reimplement bulk smart tagging
|
||||
# if self.data_type == "text":
|
||||
# if context.selected_objects:
|
||||
# for selected_object in context.selected_objects:
|
||||
# obj = annotation.Annotator.add_text(context, related_element=selected_object)
|
||||
# else:
|
||||
# obj = annotation.Annotator.add_text(context)
|
||||
# else:
|
||||
obj = annotation.Annotator.get_annotation_obj(self.object_type, self.data_type, context)
|
||||
if self.object_type == "BREAKLINE":
|
||||
obj = annotation.Annotator.add_plane_to_annotation(obj, context)
|
||||
elif self.object_type != "TEXT":
|
||||
obj = annotation.Annotator.add_line_to_annotation(obj, context)
|
||||
|
||||
if not obj.BIMObjectProperties.ifc_definition_id:
|
||||
ifc_representation_class = ""
|
||||
if self.object_type == "TEXT":
|
||||
ifc_representation_class = "IfcTextLiteral"
|
||||
elif self.object_type == "TEXT_LEADER":
|
||||
ifc_representation_class = "IfcGeometricCurveSet/IfcTextLiteral"
|
||||
bpy.ops.bim.assign_class(
|
||||
obj=obj.name,
|
||||
ifc_class="IfcAnnotation",
|
||||
context_id=subcontext.id(),
|
||||
ifc_representation_class=ifc_representation_class,
|
||||
)
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
element.ObjectType = self.object_type
|
||||
camera = tool.Ifc.get_entity(context.scene.camera)
|
||||
group = [r for r in camera.HasAssignments if r.is_a("IfcRelAssignsToGroup")][0].RelatingGroup
|
||||
bpy.ops.bim.assign_group(product=obj.name, group=group.id())
|
||||
else:
|
||||
bpy.ops.bim.update_representation(obj=obj.name)
|
||||
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
context.view_layer.objects.active = obj
|
||||
obj.select_set(True)
|
||||
if obj.data:
|
||||
bpy.ops.object.mode_set(mode="EDIT")
|
||||
return {"FINISHED"}
|
||||
drawing = tool.Ifc.get_entity(context.scene.camera)
|
||||
if not drawing:
|
||||
return
|
||||
core.add_annotation(tool.Ifc, tool.Collector, tool.Drawing, drawing=drawing, object_type=self.object_type)
|
||||
|
||||
|
||||
class AddSheet(bpy.types.Operator):
|
||||
class AddSheet(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.add_sheet"
|
||||
bl_label = "Add Sheet"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
# TODO: check undo redo
|
||||
|
||||
def execute(self, context):
|
||||
scene = context.scene
|
||||
new = scene.DocProperties.sheets.add()
|
||||
new.name = "{} - SHEET".format(len(scene.DocProperties.sheets))
|
||||
sheet_builder = sheeter.SheetBuilder()
|
||||
sheet_builder.data_dir = scene.BIMProperties.data_dir
|
||||
sheet_builder.create(new.name, scene.DocProperties.titleblock)
|
||||
return {"FINISHED"}
|
||||
def _execute(self, context):
|
||||
core.add_sheet(tool.Ifc, tool.Drawing, titleblock=context.scene.DocProperties.titleblock)
|
||||
|
||||
|
||||
class OpenSheet(bpy.types.Operator):
|
||||
class OpenSheet(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.open_sheet"
|
||||
bl_label = "Open Sheet"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.DocProperties
|
||||
open_with_user_command(
|
||||
context.preferences.addons["blenderbim"].preferences.svg_command,
|
||||
os.path.join(context.scene.BIMProperties.data_dir, "sheets", props.active_sheet.name + ".svg"),
|
||||
)
|
||||
return {"FINISHED"}
|
||||
def _execute(self, context):
|
||||
self.props = context.scene.DocProperties
|
||||
sheet = tool.Ifc.get().by_id(self.props.sheets[self.props.active_sheet_index].ifc_definition_id)
|
||||
core.open_sheet(tool.Drawing, sheet=sheet)
|
||||
|
||||
|
||||
class AddDrawingToSheet(bpy.types.Operator):
|
||||
@@ -738,40 +643,14 @@ class OpenView(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class OpenViewCamera(bpy.types.Operator):
|
||||
"""Select this drawing's camera object and expand its drawing properties"""
|
||||
|
||||
bl_idname = "bim.open_view_camera"
|
||||
bl_label = "Open View Camera"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
view_name: bpy.props.StringProperty()
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.mode == "OBJECT"
|
||||
|
||||
def execute(self, context):
|
||||
doc_props = context.scene.DocProperties
|
||||
doc_props.active_drawing_index = doc_props.drawings.find(self.view_name)
|
||||
drawing = doc_props.active_drawing
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
drawing.camera.select_set(True)
|
||||
context.view_layer.objects.active = drawing.camera
|
||||
for area in context.screen.areas:
|
||||
if area.ui_type == "PROPERTIES":
|
||||
for space in area.spaces:
|
||||
space.context = "DATA"
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ActivateView(bpy.types.Operator):
|
||||
bl_idname = "bim.activate_view"
|
||||
bl_label = "Activate View"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
drawing_index: bpy.props.IntProperty()
|
||||
drawing: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
camera = context.scene.DocProperties.drawings[self.drawing_index].camera
|
||||
camera = tool.Ifc.get_object(tool.Ifc.get().by_id(self.drawing))
|
||||
if not camera:
|
||||
return {"FINISHED"}
|
||||
area = next(area for area in context.screen.areas if area.type == "VIEW_3D")
|
||||
@@ -799,6 +678,7 @@ class ActivateView(bpy.types.Operator):
|
||||
project_collection.children["Views"].children[camera.users_collection[0].name].hide_viewport = False
|
||||
bpy.data.collections.get(camera.users_collection[0].name).hide_render = False
|
||||
bpy.ops.bim.activate_drawing_style()
|
||||
core.sync_references(tool.Ifc, tool.Collector, tool.Drawing, drawing=tool.Ifc.get().by_id(self.drawing))
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -894,21 +774,16 @@ class ResizeText(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RemoveDrawing(bpy.types.Operator):
|
||||
class RemoveDrawing(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.remove_drawing"
|
||||
bl_label = "Remove Drawing"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
index: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
def _execute(self, context):
|
||||
props = context.scene.DocProperties
|
||||
camera = props.drawings[self.index].camera
|
||||
collection = camera.users_collection[0]
|
||||
for obj in collection.objects:
|
||||
bpy.data.objects.remove(obj)
|
||||
bpy.data.collections.remove(collection, do_unlink=True)
|
||||
props.drawings.remove(self.index)
|
||||
return {"FINISHED"}
|
||||
drawing = tool.Ifc.get().by_id(props.drawings[self.index].ifc_definition_id)
|
||||
core.remove_drawing(tool.Ifc, tool.Drawing, drawing=drawing)
|
||||
|
||||
|
||||
class AddDrawingStyle(bpy.types.Operator):
|
||||
@@ -1057,16 +932,16 @@ class EditVectorStyle(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RemoveSheet(bpy.types.Operator):
|
||||
class RemoveSheet(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.remove_sheet"
|
||||
bl_label = "Remove Sheet"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
index: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.DocProperties
|
||||
props.sheets.remove(self.index)
|
||||
return {"FINISHED"}
|
||||
def _execute(self, context):
|
||||
self.props = context.scene.DocProperties
|
||||
sheet = tool.Ifc.get().by_id(self.props.sheets[self.props.active_sheet_index].ifc_definition_id)
|
||||
core.remove_sheet(tool.Ifc, tool.Drawing, sheet=sheet)
|
||||
|
||||
|
||||
class AddSchedule(bpy.types.Operator):
|
||||
@@ -1170,23 +1045,6 @@ class RemoveDrawingStyleAttribute(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RefreshDrawingList(bpy.types.Operator):
|
||||
bl_idname = "bim.refresh_drawing_list"
|
||||
bl_label = "Refresh Drawing List"
|
||||
|
||||
def execute(self, context):
|
||||
doc_props = context.scene.DocProperties
|
||||
doc_props.drawings.clear()
|
||||
for obj in context.scene.objects:
|
||||
if not isinstance(obj.data, bpy.types.Camera):
|
||||
continue
|
||||
if "IfcAnnotation/" in obj.name:
|
||||
new = doc_props.drawings.add()
|
||||
new.name = "/".join(obj.name.split("/")[1:])
|
||||
new.camera = obj
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class CleanWireframes(bpy.types.Operator):
|
||||
bl_idname = "bim.clean_wireframes"
|
||||
bl_label = "Clean Wireframes"
|
||||
@@ -1213,8 +1071,10 @@ class CopyGrid(bpy.types.Operator):
|
||||
return helper.get_active_drawing(context.scene)[0] is not None
|
||||
|
||||
def execute(self, context):
|
||||
drawing = tool.Ifc.get_entity(context.scene.camera)
|
||||
target_view = tool.Drawing.get_drawing_target_view(drawing)
|
||||
subcontext = ifcopenshell.util.representation.get_context(
|
||||
IfcStore.get_file(), "Plan", "Annotation", context.scene.camera.data.BIMCameraProperties.target_view
|
||||
IfcStore.get_file(), "Plan", "Annotation", target_view
|
||||
)
|
||||
if not subcontext:
|
||||
return {"FINISHED"}
|
||||
@@ -1223,8 +1083,8 @@ class CopyGrid(bpy.types.Operator):
|
||||
view_coll, camera = helper.get_active_drawing(context.scene)
|
||||
is_ortho = camera.data.type == "ORTHO"
|
||||
bounds = helper.ortho_view_frame(camera.data) if is_ortho else None
|
||||
clipping = is_ortho and camera.data.BIMCameraProperties.target_view in ("PLAN_VIEW", "REFLECTED_PLAN_VIEW")
|
||||
elevating = is_ortho and camera.data.BIMCameraProperties.target_view in ("ELEVATION_VIEW", "SECTION_VIEW")
|
||||
clipping = is_ortho and target_view in ("PLAN_VIEW", "REFLECTED_PLAN_VIEW")
|
||||
elevating = is_ortho and target_view in ("ELEVATION_VIEW", "SECTION_VIEW")
|
||||
|
||||
def grep(coll):
|
||||
results = []
|
||||
@@ -1420,3 +1280,39 @@ class DisableEditingTextProduct(bpy.types.Operator, Operator):
|
||||
|
||||
def _execute(self, context):
|
||||
core.disable_editing_text_product(tool.Drawing, obj=context.active_object)
|
||||
|
||||
|
||||
class LoadSheets(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.load_sheets"
|
||||
bl_label = "Load Sheets"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.load_sheets(tool.Drawing)
|
||||
|
||||
|
||||
class DisableEditingSheets(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.disable_editing_sheets"
|
||||
bl_label = "Disable Editing Text Product"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.disable_editing_sheets(tool.Drawing)
|
||||
|
||||
|
||||
class LoadDrawings(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.load_drawings"
|
||||
bl_label = "Load Drawings"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.load_drawings(tool.Drawing)
|
||||
|
||||
|
||||
class DisableEditingDrawings(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.disable_editing_drawings"
|
||||
bl_label = "Disable Editing Text Product"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.disable_editing_drawings(tool.Drawing)
|
||||
|
||||
@@ -19,11 +19,13 @@
|
||||
import os
|
||||
import bpy
|
||||
import json
|
||||
import enum
|
||||
import ifcopenshell
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.drawing as core
|
||||
import blenderbim.bim.module.drawing.annotation as annotation
|
||||
import blenderbim.bim.module.drawing.decoration as decoration
|
||||
import enum
|
||||
from blenderbim.bim.module.drawing.data import DrawingsData
|
||||
from pathlib import Path
|
||||
from blenderbim.bim.prop import Attribute, StrProperty
|
||||
from bpy.types import PropertyGroup
|
||||
@@ -56,6 +58,16 @@ def purge():
|
||||
vector_styles_enum = []
|
||||
|
||||
|
||||
def update_target_view(self, context):
|
||||
DrawingsData.data["location_hint"] = DrawingsData.location_hint()
|
||||
|
||||
|
||||
def get_location_hint(self, context):
|
||||
if not DrawingsData.is_loaded:
|
||||
DrawingsData.load()
|
||||
return DrawingsData.data["location_hint"]
|
||||
|
||||
|
||||
def update_diagram_scale(self, context):
|
||||
scale = self.diagram_scale
|
||||
if scale == "CUSTOM":
|
||||
@@ -132,31 +144,18 @@ def get_diagram_scales(self, context):
|
||||
return diagram_scales_enum
|
||||
|
||||
|
||||
def updateDrawingName(self, context):
|
||||
if not self.camera:
|
||||
return
|
||||
if self.camera.name == self.name:
|
||||
return
|
||||
self.camera.name = "IfcAnnotation/{}".format(self.name)
|
||||
unique_name = "/".join(self.camera.name.split("/")[1:])
|
||||
self.camera.users_collection[0].name = "IfcGroup/{}".format(unique_name)
|
||||
if self.name != unique_name:
|
||||
self.name = unique_name
|
||||
|
||||
|
||||
def refreshActiveDrawingIndex(self, context):
|
||||
bpy.ops.bim.activate_view(drawing_index=context.scene.DocProperties.active_drawing_index)
|
||||
def update_drawing_name(self, context):
|
||||
drawing = tool.Ifc.get().by_id(self.ifc_definition_id)
|
||||
core.update_drawing_name(tool.Ifc, tool.Drawing, drawing=drawing, name=self.name)
|
||||
|
||||
|
||||
def getTitleblocks(self, context):
|
||||
global titleblocks_enum
|
||||
if len(titleblocks_enum) < 1:
|
||||
titleblocks_enum.clear()
|
||||
for filename in Path(os.path.join(context.scene.BIMProperties.data_dir, "templates", "titleblocks")).glob(
|
||||
"*.svg"
|
||||
):
|
||||
f = str(filename.stem)
|
||||
titleblocks_enum.append((f, f, ""))
|
||||
files = Path(os.path.join(context.scene.BIMProperties.data_dir, "templates", "titleblocks")).glob("*.svg")
|
||||
files = sorted([str(f.stem) for f in files])
|
||||
titleblocks_enum.extend([(f, f, "") for f in files])
|
||||
return titleblocks_enum
|
||||
|
||||
|
||||
@@ -198,8 +197,9 @@ class Variable(PropertyGroup):
|
||||
|
||||
|
||||
class Drawing(PropertyGroup):
|
||||
name: StringProperty(name="Name", update=updateDrawingName)
|
||||
camera: PointerProperty(name="Camera", type=bpy.types.Object)
|
||||
ifc_definition_id: IntProperty(name="IFC Definition ID")
|
||||
name: StringProperty(name="Name", update=update_drawing_name)
|
||||
target_view: StringProperty(name="Target View")
|
||||
|
||||
|
||||
class Schedule(PropertyGroup):
|
||||
@@ -218,6 +218,8 @@ class Sheet(PropertyGroup):
|
||||
def get_name(self):
|
||||
return self.get("name")
|
||||
|
||||
ifc_definition_id: IntProperty(name="IFC Definition ID")
|
||||
identification: StringProperty(name="Identification")
|
||||
name: StringProperty(name="Name", get=get_name, set=set_name)
|
||||
drawings: CollectionProperty(name="Drawings", type=Drawing)
|
||||
active_drawing_index: IntProperty(name="Active Drawing Index")
|
||||
@@ -276,12 +278,27 @@ class DocProperties(PropertyGroup):
|
||||
should_use_linework_cache: BoolProperty(name="Use Linework Cache", default=False)
|
||||
should_use_annotation_cache: BoolProperty(name="Use Annotation Cache", default=False)
|
||||
should_extract: BoolProperty(name="Should Extract", default=True)
|
||||
is_editing_drawings: BoolProperty(name="Is Editing Drawings", default=False)
|
||||
target_view: EnumProperty(
|
||||
items=[
|
||||
("PLAN_VIEW", "Plan", ""),
|
||||
("ELEVATION_VIEW", "Elevation", ""),
|
||||
("SECTION_VIEW", "Section", ""),
|
||||
("REFLECTED_PLAN_VIEW", "RCP", ""),
|
||||
("MODEL_VIEW", "Model", ""),
|
||||
],
|
||||
name="Target View",
|
||||
default="PLAN_VIEW",
|
||||
update=update_target_view,
|
||||
)
|
||||
location_hint: EnumProperty(items=get_location_hint, name="Location Hint")
|
||||
drawings: CollectionProperty(name="Drawings", type=Drawing)
|
||||
active_drawing_index: IntProperty(name="Active Drawing Index", update=refreshActiveDrawingIndex)
|
||||
active_drawing_index: IntProperty(name="Active Drawing Index")
|
||||
current_drawing_index: IntProperty(name="Current Drawing Index")
|
||||
schedules: CollectionProperty(name="Schedules", type=Schedule)
|
||||
active_schedule_index: IntProperty(name="Active Schedule Index")
|
||||
titleblock: EnumProperty(items=getTitleblocks, name="Titleblock", update=refreshTitleblocks)
|
||||
is_editing_sheets: BoolProperty(name="Is Editing Sheets", default=False)
|
||||
sheets: CollectionProperty(name="Sheets", type=Sheet)
|
||||
active_sheet_index: IntProperty(name="Active Sheet Index")
|
||||
ifc_files: CollectionProperty(name="IFCs", type=StrProperty)
|
||||
@@ -307,17 +324,6 @@ class DocProperties(PropertyGroup):
|
||||
class BIMCameraProperties(PropertyGroup):
|
||||
representation: StringProperty(name="Representation")
|
||||
view_name: StringProperty(name="View Name")
|
||||
target_view: EnumProperty(
|
||||
items=[
|
||||
("PLAN_VIEW", "PLAN_VIEW", ""),
|
||||
("ELEVATION_VIEW", "ELEVATION_VIEW", ""),
|
||||
("SECTION_VIEW", "SECTION_VIEW", ""),
|
||||
("REFLECTED_PLAN_VIEW", "REFLECTED_PLAN_VIEW", ""),
|
||||
("MODEL_VIEW", "MODEL_VIEW", ""),
|
||||
],
|
||||
name="Target View",
|
||||
default="PLAN_VIEW",
|
||||
)
|
||||
diagram_scale: EnumProperty(items=get_diagram_scales, name="Drawing Scale", update=update_diagram_scale)
|
||||
custom_diagram_scale: StringProperty(name="Custom Scale", update=update_diagram_scale)
|
||||
raster_x: IntProperty(name="Raster X", default=1000)
|
||||
|
||||
@@ -31,7 +31,7 @@ class SheetBuilder:
|
||||
self.scale = "NTS"
|
||||
|
||||
def create(self, name, titleblock_name):
|
||||
sheet_path = os.path.join(self.data_dir, f"{name}.svg")
|
||||
sheet_path = os.path.join(self.data_dir, "sheets", f"{name}.svg")
|
||||
root = ET.Element("svg")
|
||||
root.attrib["xmlns"] = "http://www.w3.org/2000/svg"
|
||||
root.attrib["xmlns:xlink"] = "http://www.w3.org/1999/xlink"
|
||||
|
||||
@@ -20,7 +20,7 @@ import bpy
|
||||
import blenderbim.bim.helper
|
||||
import blenderbim.tool as tool
|
||||
from bpy.types import Panel
|
||||
from blenderbim.bim.module.drawing.data import TextData
|
||||
from blenderbim.bim.module.drawing.data import TextData, SheetsData, DrawingsData
|
||||
|
||||
|
||||
class BIM_PT_camera(Panel):
|
||||
@@ -67,9 +67,6 @@ class BIM_PT_camera(Panel):
|
||||
row = layout.row()
|
||||
row.operator("bim.resize_text")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(props, "target_view")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(props, "cut_objects")
|
||||
if props.cut_objects == "CUSTOM":
|
||||
@@ -149,44 +146,61 @@ class BIM_PT_drawing_underlay(Panel):
|
||||
|
||||
|
||||
class BIM_PT_drawings(Panel):
|
||||
bl_label = "SVG Drawings"
|
||||
bl_label = "Drawings"
|
||||
bl_idname = "BIM_PT_drawings"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "output"
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "UI"
|
||||
bl_category = "BIM Documentation"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.use_property_split = True
|
||||
props = context.scene.DocProperties
|
||||
if not DrawingsData.is_loaded:
|
||||
DrawingsData.load()
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.add_drawing")
|
||||
row.operator("bim.refresh_drawing_list", icon="FILE_REFRESH", text="")
|
||||
self.props = context.scene.DocProperties
|
||||
|
||||
if props.drawings:
|
||||
if props.active_drawing_index < len(props.drawings):
|
||||
if not self.props.is_editing_drawings:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=f"{DrawingsData.data['total_drawings']} Drawings Found", icon="IMAGE_DATA")
|
||||
row.operator("bim.load_drawings", text="", icon="IMPORT")
|
||||
return
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.props, "target_view", text="")
|
||||
row.prop(self.props, "location_hint", text="")
|
||||
row.operator("bim.add_drawing", text="", icon="ADD")
|
||||
row.operator("bim.disable_editing_drawings", text="", icon="CANCEL")
|
||||
|
||||
if self.props.drawings:
|
||||
if self.props.active_drawing_index < len(self.props.drawings):
|
||||
row = self.layout.row(align=True)
|
||||
row.alignment = "RIGHT"
|
||||
op = row.operator("bim.open_view", icon="URL", text="")
|
||||
op.view = props.active_drawing.name
|
||||
row.operator("bim.remove_drawing", icon="X", text="").index = props.active_drawing_index
|
||||
layout.template_list("BIM_UL_drawinglist", "", props, "drawings", props, "active_drawing_index")
|
||||
op.view = self.props.active_drawing.name
|
||||
op = row.operator("bim.activate_view", icon="OUTLINER_OB_CAMERA", text="")
|
||||
op.drawing = self.props.active_drawing.ifc_definition_id
|
||||
row.operator("bim.create_drawing", text="", icon="OUTPUT")
|
||||
row.operator("bim.remove_drawing", icon="X", text="").index = self.props.active_drawing_index
|
||||
self.layout.template_list(
|
||||
"BIM_UL_drawinglist", "", self.props, "drawings", self.props, "active_drawing_index"
|
||||
)
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.add_ifc_file")
|
||||
# Commented out until federated drawing generation is rebuilt
|
||||
# row = self.layout.row()
|
||||
# row.operator("bim.add_ifc_file")
|
||||
|
||||
for index, ifc_file in enumerate(props.ifc_files):
|
||||
row = layout.row(align=True)
|
||||
row.prop(ifc_file, "name", text="IFC #{}".format(index + 1))
|
||||
row.operator("bim.select_doc_ifc_file", icon="FILE_FOLDER", text="").index = index
|
||||
row.operator("bim.remove_ifc_file", icon="X", text="").index = index
|
||||
# for index, ifc_file in enumerate(self.props.ifc_files):
|
||||
# row = self.layout.row(align=True)
|
||||
# row.prop(ifc_file, "name", text="IFC #{}".format(index + 1))
|
||||
# row.operator("bim.select_doc_ifc_file", icon="FILE_FOLDER", text="").index = index
|
||||
# row.operator("bim.remove_ifc_file", icon="X", text="").index = index
|
||||
|
||||
|
||||
class BIM_PT_schedules(Panel):
|
||||
bl_label = "ODS Schedules"
|
||||
bl_label = "Schedules"
|
||||
bl_idname = "BIM_PT_schedules"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "output"
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "UI"
|
||||
bl_category = "BIM Documentation"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
@@ -208,31 +222,39 @@ class BIM_PT_schedules(Panel):
|
||||
|
||||
|
||||
class BIM_PT_sheets(Panel):
|
||||
bl_label = "SVG Sheets"
|
||||
bl_label = "Sheets"
|
||||
bl_idname = "BIM_PT_sheets"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "output"
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "UI"
|
||||
bl_category = "BIM Documentation"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
props = context.scene.DocProperties
|
||||
if not SheetsData.is_loaded:
|
||||
SheetsData.load()
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "titleblock", text="")
|
||||
row.operator("bim.add_sheet")
|
||||
self.props = context.scene.DocProperties
|
||||
|
||||
if props.sheets:
|
||||
if not self.props.is_editing_sheets:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=f"{SheetsData.data['total_sheets']} Sheets Found", icon="FILE")
|
||||
row.operator("bim.load_sheets", text="", icon="IMPORT")
|
||||
return
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.props, "titleblock", text="")
|
||||
row.operator("bim.add_sheet", text="", icon="ADD")
|
||||
row.operator("bim.disable_editing_sheets", text="", icon="CANCEL")
|
||||
|
||||
if self.props.sheets:
|
||||
row = self.layout.row(align=True)
|
||||
row.alignment = "RIGHT"
|
||||
row.operator("bim.open_sheet", icon="URL", text="")
|
||||
row.operator("bim.remove_sheet", icon="X", text="").index = props.active_sheet_index
|
||||
row.operator("bim.add_drawing_to_sheet", icon="IMAGE_PLANE", text="")
|
||||
row.operator("bim.add_schedule_to_sheet", icon="PRESET_NEW", text="")
|
||||
row.operator("bim.create_sheets", icon="FILE_REFRESH", text="")
|
||||
row.operator("bim.remove_sheet", icon="X", text="").index = self.props.active_sheet_index
|
||||
|
||||
layout.template_list("BIM_UL_generic", "", props, "sheets", props, "active_sheet_index")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.add_drawing_to_sheet")
|
||||
row.operator("bim.add_schedule_to_sheet")
|
||||
row = layout.row()
|
||||
row.operator("bim.create_sheets")
|
||||
self.layout.template_list("BIM_UL_sheets", "", self.props, "sheets", self.props, "active_sheet_index")
|
||||
|
||||
|
||||
class BIM_PT_text(Panel):
|
||||
@@ -289,21 +311,15 @@ class BIM_PT_text(Panel):
|
||||
|
||||
class BIM_PT_annotation_utilities(Panel):
|
||||
bl_idname = "BIM_PT_annotation_utilities"
|
||||
bl_label = "Drawing"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_label = "Annotation"
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "UI"
|
||||
bl_category = "BlenderBIM"
|
||||
bl_category = "BIM Documentation"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.clean_wireframes")
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.add_grid")
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.add_sections_annotations")
|
||||
self.props = context.scene.DocProperties
|
||||
|
||||
row = layout.row(align=True)
|
||||
op = row.operator("bim.add_annotation", text="Dim", icon="ARROW_LEFTRIGHT")
|
||||
@@ -345,29 +361,35 @@ class BIM_PT_annotation_utilities(Panel):
|
||||
op.object_type = "MISC"
|
||||
op.data_type = "mesh"
|
||||
|
||||
props = context.scene.DocProperties
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.add_drawing")
|
||||
row.operator("bim.refresh_drawing_list", icon="FILE_REFRESH", text="")
|
||||
|
||||
if props.drawings:
|
||||
if props.active_drawing_index < len(props.drawings):
|
||||
op = row.operator("bim.open_view", icon="URL", text="")
|
||||
op.view = props.active_drawing.name
|
||||
row.operator("bim.remove_drawing", icon="X", text="").index = props.active_drawing_index
|
||||
layout.template_list("BIM_UL_drawinglist", "", props, "drawings", props, "active_drawing_index")
|
||||
|
||||
layout.prop(props, "should_draw_decorations")
|
||||
layout.prop(props, "decorations_colour")
|
||||
row.prop(self.props, "should_draw_decorations", text="Viewport Annotations")
|
||||
|
||||
|
||||
class BIM_UL_drawinglist(bpy.types.UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if item:
|
||||
row = layout.row(align=True)
|
||||
row.prop(item, "name", text="", emboss=False)
|
||||
op = row.operator("bim.open_view_camera", icon="OUTLINER_OB_CAMERA", text="")
|
||||
op.view_name = item.name
|
||||
icon = "UV_FACESEL"
|
||||
if item.target_view == "ELEVATION_VIEW":
|
||||
icon = "UV_VERTEXSEL"
|
||||
elif item.target_view == "SECTION_VIEW":
|
||||
icon = "UV_EDGESEL"
|
||||
elif item.target_view == "REFLECTED_PLAN_VIEW":
|
||||
icon = "XRAY"
|
||||
elif item.target_view == "MODEL_VIEW":
|
||||
icon = "SNAP_VOLUME"
|
||||
row.prop(item, "name", text="", icon=icon, emboss=False)
|
||||
else:
|
||||
layout.label(text="", translate=False)
|
||||
|
||||
|
||||
class BIM_UL_sheets(bpy.types.UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if item:
|
||||
row = layout.row(align=True)
|
||||
split1 = row.split(factor=0.2)
|
||||
split1.label(text=item.identification or "X")
|
||||
split2 = split1.split(factor=0.8)
|
||||
split2.label(text=item.name or "Unnamed")
|
||||
else:
|
||||
layout.label(text="", translate=False)
|
||||
|
||||
@@ -145,6 +145,11 @@ class UpdateRepresentation(bpy.types.Operator):
|
||||
self.file = IfcStore.get_file()
|
||||
|
||||
for obj in objs:
|
||||
# TODO: write unit tests to see how this bulk operation handles
|
||||
# contradictory ifc_representation_class values and when
|
||||
# ifc_representation_class is IfcTextLiteral
|
||||
if not obj.data:
|
||||
continue
|
||||
self.update_obj_mesh_representation(context, obj)
|
||||
IfcStore.edited_objs.discard(obj)
|
||||
return {"FINISHED"}
|
||||
@@ -155,6 +160,11 @@ class UpdateRepresentation(bpy.types.Operator):
|
||||
if product.is_a("IfcGridAxis"):
|
||||
ifcopenshell.api.run("grid.create_axis_curve", self.file, **{"axis_curve": obj, "grid_axis": product})
|
||||
return
|
||||
if product.is_a("IfcRelSpaceBoundary"):
|
||||
# TODO refactor
|
||||
settings = tool.Boundary.get_assign_connection_geometry_settings(obj)
|
||||
ifcopenshell.api.run("boundary.assign_connection_geometry", tool.Ifc.get(), **settings)
|
||||
return
|
||||
|
||||
core.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
|
||||
|
||||
@@ -178,8 +188,9 @@ class UpdateRepresentation(bpy.types.Operator):
|
||||
"geometry": obj.data,
|
||||
"coordinate_offset": coordinate_offset,
|
||||
"total_items": max(1, len(obj.material_slots)),
|
||||
"should_force_faceted_brep": context.scene.BIMGeometryProperties.should_force_faceted_brep,
|
||||
"should_force_triangulation": context.scene.BIMGeometryProperties.should_force_triangulation,
|
||||
"should_force_faceted_brep": tool.Geometry.should_force_faceted_brep(),
|
||||
"should_force_triangulation": tool.Geometry.should_force_triangulation(),
|
||||
"should_generate_uvs": tool.Geometry.should_generate_uvs(obj),
|
||||
"ifc_representation_class": self.ifc_representation_class,
|
||||
}
|
||||
|
||||
@@ -320,7 +331,7 @@ class OverrideDeleteTrait:
|
||||
def delete_ifc_object(self, obj):
|
||||
if obj.BIMObjectProperties.ifc_definition_id:
|
||||
element = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
IfcStore.deleted_ids.add(element.id())
|
||||
IfcStore.delete_element(element)
|
||||
if getattr(element, "FillsVoids", None):
|
||||
self.remove_filling(element)
|
||||
if element.is_a("IfcOpeningElement"):
|
||||
@@ -344,6 +355,7 @@ class OverrideDeleteTrait:
|
||||
class OverrideDelete(bpy.types.Operator, OverrideDeleteTrait):
|
||||
bl_idname = "object.delete"
|
||||
bl_label = "Delete"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
use_global: bpy.props.BoolProperty(default=False)
|
||||
confirm: bpy.props.BoolProperty(default=True)
|
||||
|
||||
@@ -357,21 +369,29 @@ class OverrideDelete(bpy.types.Operator, OverrideDeleteTrait):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
for obj in context.selected_objects:
|
||||
bpy.data.objects.remove(obj)
|
||||
# Required otherwise gizmos are still visible
|
||||
context.view_layer.objects.active = None
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_confirm(self, event)
|
||||
if self.confirm:
|
||||
return context.window_manager.invoke_confirm(self, event)
|
||||
self.confirm = True
|
||||
return self.execute(context)
|
||||
|
||||
def _execute(self, context):
|
||||
for obj in context.selected_objects:
|
||||
self.delete_ifc_object(obj)
|
||||
bpy.data.objects.remove(obj)
|
||||
# Required otherwise gizmos are still visible
|
||||
context.view_layer.objects.active = None
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class OverrideOutlinerDelete(bpy.types.Operator, OverrideDeleteTrait):
|
||||
bl_idname = "outliner.delete"
|
||||
bl_label = "Delete"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
hierarchy: bpy.props.BoolProperty(default=False)
|
||||
|
||||
@classmethod
|
||||
@@ -394,7 +414,6 @@ class OverrideOutlinerDelete(bpy.types.Operator, OverrideDeleteTrait):
|
||||
if item.bl_rna.identifier == "Collection":
|
||||
collection = bpy.data.collections.get(item.name)
|
||||
collection_data = self.get_collection_objects_and_children(collection)
|
||||
print(collection_data)
|
||||
objects_to_delete |= collection_data["objects"]
|
||||
collections_to_delete |= collection_data["children"]
|
||||
collections_to_delete.add(collection)
|
||||
@@ -433,7 +452,6 @@ class OverrideOutlinerDelete(bpy.types.Operator, OverrideDeleteTrait):
|
||||
while queue:
|
||||
collection = queue.pop()
|
||||
for obj in collection.objects:
|
||||
print('obj is', obj)
|
||||
objects.add(obj)
|
||||
queue.extend(collection.children)
|
||||
children = children.union(collection.children)
|
||||
@@ -443,6 +461,7 @@ class OverrideOutlinerDelete(bpy.types.Operator, OverrideDeleteTrait):
|
||||
class OverrideDuplicateMove(bpy.types.Operator):
|
||||
bl_idname = "object.duplicate_move"
|
||||
bl_label = "Duplicate Objects"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
|
||||
@@ -50,7 +50,7 @@ class BIM_PT_representations(Panel):
|
||||
layout.label(text="No representations found")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(context.scene.BIMProperties, "contexts", text="")
|
||||
row.prop(context.scene.BIMRootProperties, "contexts", text="")
|
||||
row.operator("bim.add_representation", icon="ADD", text="")
|
||||
|
||||
for representation in RepresentationsData.data["representations"]:
|
||||
|
||||
@@ -28,7 +28,7 @@ class BIM_PT_groups(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_utilities"
|
||||
bl_parent_id = "BIM_PT_project_setup"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
|
||||
@@ -27,6 +27,7 @@ class BIM_PT_lca(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_integrations"
|
||||
|
||||
def draw(self, context):
|
||||
props = context.scene.BIMLCAProperties
|
||||
|
||||
@@ -21,37 +21,47 @@ from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.AddConstituent,
|
||||
operator.AddDefaultMaterial,
|
||||
operator.AddLayer,
|
||||
operator.AddListItem,
|
||||
operator.AddMaterial,
|
||||
operator.AddMaterialSet,
|
||||
operator.AddProfile,
|
||||
operator.AssignMaterial,
|
||||
operator.AssignParameterizedProfile,
|
||||
operator.CopyMaterial,
|
||||
operator.DisableEditingAssignedMaterial,
|
||||
operator.DisableEditingMaterialSetItem,
|
||||
operator.DisableEditingMaterials,
|
||||
operator.EditAssignedMaterial,
|
||||
operator.EditMaterialSetItem,
|
||||
operator.EnableEditingAssignedMaterial,
|
||||
operator.EnableEditingMaterialSetItem,
|
||||
operator.LoadMaterials,
|
||||
operator.RemoveConstituent,
|
||||
operator.RemoveLayer,
|
||||
operator.RemoveListItem,
|
||||
operator.RemoveMaterial,
|
||||
operator.RemoveMaterialSet,
|
||||
operator.RemoveProfile,
|
||||
operator.ReorderMaterialSetItem,
|
||||
operator.SelectByMaterial,
|
||||
operator.UnassignMaterial,
|
||||
operator.UnlinkMaterial,
|
||||
prop.Material,
|
||||
prop.BIMMaterialProperties,
|
||||
prop.BIMObjectMaterialProperties,
|
||||
ui.BIM_PT_materials,
|
||||
ui.BIM_PT_material,
|
||||
ui.BIM_PT_object_material,
|
||||
ui.BIM_UL_materials,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
bpy.types.Scene.BIMMaterialProperties = bpy.props.PointerProperty(type=prop.BIMMaterialProperties)
|
||||
bpy.types.Object.BIMObjectMaterialProperties = bpy.props.PointerProperty(type=prop.BIMObjectMaterialProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
del bpy.types.Scene.BIMMaterialProperties
|
||||
del bpy.types.Object.BIMObjectMaterialProperties
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.schema
|
||||
import blenderbim.tool as tool
|
||||
|
||||
|
||||
def refresh():
|
||||
MaterialsData.is_loaded = False
|
||||
ObjectMaterialData.is_loaded = False
|
||||
|
||||
|
||||
class MaterialsData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {
|
||||
"total_materials": cls.total_materials(),
|
||||
"material_types": cls.material_types(),
|
||||
}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def total_materials(cls):
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
return (
|
||||
len(tool.Ifc.get().by_type("IfcMaterial"))
|
||||
+ len(tool.Ifc.get().by_type("IfcMaterialLayerSet"))
|
||||
+ len(tool.Ifc.get().by_type("IfcMaterialList"))
|
||||
)
|
||||
return (
|
||||
len(tool.Ifc.get().by_type("IfcMaterial"))
|
||||
+ len(tool.Ifc.get().by_type("IfcMaterialConstituentSet"))
|
||||
+ len(tool.Ifc.get().by_type("IfcMaterialLayerSet"))
|
||||
+ len(tool.Ifc.get().by_type("IfcMaterialProfileSet"))
|
||||
+ len(tool.Ifc.get().by_type("IfcMaterialList"))
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def material_types(cls):
|
||||
material_types = [
|
||||
"IfcMaterial",
|
||||
"IfcMaterialConstituentSet",
|
||||
"IfcMaterialLayerSet",
|
||||
"IfcMaterialProfileSet",
|
||||
"IfcMaterialList",
|
||||
]
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
material_types = ["IfcMaterial", "IfcMaterialLayerSet", "IfcMaterialList"]
|
||||
return [(m, m, "") for m in material_types]
|
||||
|
||||
|
||||
class ObjectMaterialData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {
|
||||
"materials": cls.materials(),
|
||||
}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def materials(cls):
|
||||
return sorted([(str(m.id()), m.Name or "Unnamed", "") for m in tool.Ifc.get().by_type("IfcMaterial")], key=lambda x: x[1])
|
||||
@@ -31,11 +31,32 @@ from ifcopenshell.api.material.data import Data
|
||||
from ifcopenshell.api.profile.data import Data as ProfileData
|
||||
|
||||
|
||||
class Operator:
|
||||
def execute(self, context):
|
||||
IfcStore.execute_ifc_operator(self, context)
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
return {"FINISHED"}
|
||||
class LoadMaterials(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.load_materials"
|
||||
bl_label = "Load Materials"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.load_materials(tool.Material, context.scene.BIMMaterialProperties.material_type)
|
||||
|
||||
|
||||
class DisableEditingMaterials(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.disable_editing_materials"
|
||||
bl_label = "Disable Editing Materials"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.disable_editing_materials(tool.Material)
|
||||
|
||||
|
||||
class SelectByMaterial(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.select_by_material"
|
||||
bl_label = "Select By Material"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
material: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.select_by_material(tool.Material, material=tool.Ifc.get().by_id(self.material))
|
||||
|
||||
|
||||
class AssignParameterizedProfile(bpy.types.Operator):
|
||||
@@ -68,70 +89,54 @@ class AssignParameterizedProfile(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddDefaultMaterial(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.add_default_material"
|
||||
bl_label = "Add Default Material"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.add_default_material(tool.Ifc, tool.Material)
|
||||
Data.load(IfcStore.get_file())
|
||||
|
||||
|
||||
class AddMaterial(bpy.types.Operator):
|
||||
class AddMaterial(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.add_material"
|
||||
bl_label = "Add Material"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
obj = bpy.data.materials.get(self.obj) if self.obj else context.active_object.active_material
|
||||
self.file = IfcStore.get_file()
|
||||
result = ifcopenshell.api.run("material.add_material", self.file, **{"name": obj.name})
|
||||
IfcStore.link_element(result, obj)
|
||||
if obj.BIMMaterialProperties.ifc_style_id:
|
||||
context = ifcopenshell.util.representation.get_context(self.file, "Model", "Body", "MODEL_VIEW")
|
||||
if context:
|
||||
ifcopenshell.api.run(
|
||||
"style.assign_material_style",
|
||||
self.file,
|
||||
**{
|
||||
"material": result,
|
||||
"style": self.file.by_id(obj.BIMMaterialProperties.ifc_style_id),
|
||||
"context": context,
|
||||
},
|
||||
)
|
||||
obj = bpy.data.materials.get(self.obj) if self.obj else None
|
||||
core.add_material(tool.Ifc, tool.Material, tool.Style, obj=obj)
|
||||
Data.load(IfcStore.get_file())
|
||||
material_prop_purge()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RemoveMaterial(bpy.types.Operator):
|
||||
class AddMaterialSet(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.add_material_set"
|
||||
bl_label = "Add Material Set"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
set_type: bpy.props.StringProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.add_material_set(tool.Ifc, tool.Material, set_type=self.set_type)
|
||||
Data.load(IfcStore.get_file())
|
||||
material_prop_purge()
|
||||
|
||||
|
||||
class RemoveMaterial(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.remove_material"
|
||||
bl_label = "Remove Material"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
material: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
obj = bpy.data.materials.get(self.obj) if self.obj else context.active_object.active_material
|
||||
self.file = IfcStore.get_file()
|
||||
result = ifcopenshell.api.run(
|
||||
"material.remove_material",
|
||||
self.file,
|
||||
**{"material": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)},
|
||||
)
|
||||
obj.BIMObjectProperties.ifc_definition_id = 0
|
||||
core.remove_material(tool.Ifc, tool.Material, tool.Style, material=tool.Ifc.get().by_id(self.material))
|
||||
Data.load(IfcStore.get_file())
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class UnlinkMaterial(bpy.types.Operator, Operator):
|
||||
class RemoveMaterialSet(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.remove_material_set"
|
||||
bl_label = "Remove Material Set"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
material: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.remove_material_set(tool.Ifc, tool.Material, material=tool.Ifc.get().by_id(self.material))
|
||||
Data.load(IfcStore.get_file())
|
||||
|
||||
|
||||
class UnlinkMaterial(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.unlink_material"
|
||||
bl_label = "Unlink Material"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
import bpy
|
||||
from ifcopenshell.api.material.data import Data
|
||||
from blenderbim.bim.module.material.data import MaterialsData, ObjectMaterialData
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.prop import StrProperty, Attribute
|
||||
from bpy.types import PropertyGroup
|
||||
@@ -32,18 +33,15 @@ from bpy.props import (
|
||||
CollectionProperty,
|
||||
)
|
||||
|
||||
materials_enum = []
|
||||
materialtypes_enum = []
|
||||
profileclasses_enum = []
|
||||
parameterizedprofileclasses_enum = []
|
||||
|
||||
|
||||
def purge():
|
||||
global materials_enum
|
||||
global materialtypes_enum
|
||||
global profileclasses_enum
|
||||
global parameterizedprofileclasses_enum
|
||||
materials_enum = []
|
||||
materialtypes_enum = []
|
||||
profileclasses_enum = []
|
||||
parameterizedprofileclasses_enum = []
|
||||
@@ -77,12 +75,10 @@ def getParameterizedProfileClasses(self, context):
|
||||
return parameterizedprofileclasses_enum
|
||||
|
||||
|
||||
def getMaterials(self, context):
|
||||
global materials_enum
|
||||
if len(materials_enum) == 0 and IfcStore.get_file():
|
||||
materials_enum.clear()
|
||||
materials_enum = [(str(m_id), m["Name"], "") for m_id, m in Data.materials.items()]
|
||||
return materials_enum
|
||||
def get_materials(self, context):
|
||||
if not ObjectMaterialData.is_loaded:
|
||||
ObjectMaterialData.load()
|
||||
return ObjectMaterialData.data["materials"]
|
||||
|
||||
|
||||
def getMaterialTypes(self, context):
|
||||
@@ -104,9 +100,28 @@ def getMaterialTypes(self, context):
|
||||
return materialtypes_enum
|
||||
|
||||
|
||||
def get_material_types(self, context):
|
||||
if not MaterialsData.is_loaded:
|
||||
MaterialsData.load()
|
||||
return MaterialsData.data["material_types"]
|
||||
|
||||
|
||||
class Material(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
ifc_definition_id: IntProperty(name="IFC Definition ID")
|
||||
total_elements: IntProperty(name="Total Elements")
|
||||
|
||||
|
||||
class BIMMaterialProperties(PropertyGroup):
|
||||
is_editing: BoolProperty(name="Is Editing", default=False)
|
||||
material_type: EnumProperty(items=get_material_types, name="Material Type")
|
||||
materials: CollectionProperty(name="Materials", type=Material)
|
||||
active_material_index: IntProperty(name="Active Material Index")
|
||||
|
||||
|
||||
class BIMObjectMaterialProperties(PropertyGroup):
|
||||
material_type: EnumProperty(items=getMaterialTypes, name="Material Type")
|
||||
material: EnumProperty(items=getMaterials, name="Material")
|
||||
material: EnumProperty(items=get_materials, name="Material")
|
||||
is_editing: BoolProperty(name="Is Editing", default=False)
|
||||
material_set_usage_attributes: CollectionProperty(name="Material Set Usage Attributes", type=Attribute)
|
||||
material_set_attributes: CollectionProperty(name="Material Set Attributes", type=Attribute)
|
||||
@@ -115,7 +130,7 @@ class BIMObjectMaterialProperties(PropertyGroup):
|
||||
material_set_item_profile_attributes: CollectionProperty(
|
||||
name="Material Set Item Profile Attributes", type=Attribute
|
||||
)
|
||||
material_set_item_material: EnumProperty(items=getMaterials, name="Material")
|
||||
material_set_item_material: EnumProperty(items=get_materials, name="Material")
|
||||
profile_classes: EnumProperty(items=getProfileClasses, name="Profile Classes")
|
||||
parameterized_profile_classes: EnumProperty(
|
||||
items=getParameterizedProfileClasses, name="Parameterized Profile Classes"
|
||||
|
||||
@@ -17,11 +17,62 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import blenderbim.bim.helper
|
||||
from bpy.types import Panel
|
||||
from bpy.types import Panel, UIList
|
||||
from ifcopenshell.api.material.data import Data
|
||||
from ifcopenshell.api.profile.data import Data as ProfileData
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.helper import draw_attributes
|
||||
from blenderbim.bim.module.material.data import MaterialsData, ObjectMaterialData
|
||||
|
||||
|
||||
class BIM_PT_materials(Panel):
|
||||
bl_label = "IFC Materials"
|
||||
bl_idname = "BIM_PT_materials"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_geometry"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return IfcStore.get_file()
|
||||
|
||||
def draw(self, context):
|
||||
if not MaterialsData.is_loaded:
|
||||
MaterialsData.load()
|
||||
|
||||
self.props = context.scene.BIMMaterialProperties
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="{} Materials Found".format(MaterialsData.data["total_materials"]), icon="MATERIAL")
|
||||
if self.props.is_editing:
|
||||
row.operator("bim.disable_editing_materials", text="", icon="CANCEL")
|
||||
else:
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.props, "material_type", text="")
|
||||
row.operator("bim.load_materials", text="", icon="IMPORT")
|
||||
return
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.alignment = "RIGHT"
|
||||
|
||||
if self.props.material_type == "IfcMaterial":
|
||||
row.operator("bim.add_material", text="", icon="ADD")
|
||||
if self.props.materials and self.props.active_material_index < len(self.props.materials):
|
||||
material = self.props.materials[self.props.active_material_index]
|
||||
op = row.operator("bim.select_by_material", text="", icon="RESTRICT_SELECT_OFF")
|
||||
op.material = material.ifc_definition_id
|
||||
row.operator("bim.remove_material", text="", icon="X").material = material.ifc_definition_id
|
||||
else:
|
||||
row.operator("bim.add_material_set", text="", icon="ADD").set_type = self.props.material_type
|
||||
if self.props.materials and self.props.active_material_index < len(self.props.materials):
|
||||
material = self.props.materials[self.props.active_material_index]
|
||||
op = row.operator("bim.select_by_material", text="", icon="RESTRICT_SELECT_OFF")
|
||||
op.material = material.ifc_definition_id
|
||||
row.operator("bim.remove_material_set", text="", icon="X").material = material.ifc_definition_id
|
||||
|
||||
self.layout.template_list("BIM_UL_materials", "", self.props, "materials", self.props, "active_material_index")
|
||||
|
||||
|
||||
class BIM_PT_material(Panel):
|
||||
@@ -37,11 +88,13 @@ class BIM_PT_material(Panel):
|
||||
|
||||
def draw(self, context):
|
||||
row = self.layout.row(align=True)
|
||||
if bool(context.active_object.active_material.BIMObjectProperties.ifc_definition_id):
|
||||
row.operator("bim.remove_material", icon="X", text="Remove IFC Material")
|
||||
material_id = context.active_object.active_material.BIMObjectProperties.ifc_definition_id
|
||||
if bool(material_id):
|
||||
row.operator("bim.remove_material", icon="X", text="Remove IFC Material").material = material_id
|
||||
row.operator("bim.unlink_material", icon="UNLINKED", text="")
|
||||
else:
|
||||
row.operator("bim.add_material", icon="ADD", text="Create IFC Material")
|
||||
op = row.operator("bim.add_material", icon="ADD", text="Create IFC Material")
|
||||
op.obj = context.active_object.active_material.name
|
||||
|
||||
|
||||
class BIM_PT_object_material(Panel):
|
||||
@@ -66,6 +119,9 @@ class BIM_PT_object_material(Panel):
|
||||
return True
|
||||
|
||||
def draw(self, context):
|
||||
if not ObjectMaterialData.is_loaded:
|
||||
ObjectMaterialData.load()
|
||||
|
||||
self.file = IfcStore.get_file()
|
||||
self.oprops = context.active_object.BIMObjectProperties
|
||||
self.props = context.active_object.BIMObjectMaterialProperties
|
||||
@@ -77,10 +133,10 @@ class BIM_PT_object_material(Panel):
|
||||
ProfileData.load(self.file)
|
||||
self.product_data = Data.products[self.oprops.ifc_definition_id]
|
||||
|
||||
if not Data.materials:
|
||||
if not ObjectMaterialData.data["materials"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="No Materials Available")
|
||||
row.operator("bim.add_default_material", icon="ADD", text="")
|
||||
row.operator("bim.add_material", icon="ADD", text="").obj = ""
|
||||
return
|
||||
|
||||
if self.product_data:
|
||||
@@ -333,3 +389,13 @@ class BIM_PT_object_material(Panel):
|
||||
if total_thickness:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=f"Total Thickness: {total_thickness:.3f}")
|
||||
|
||||
|
||||
class BIM_UL_materials(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if item:
|
||||
row = layout.row(align=True)
|
||||
row.label(text=item.name)
|
||||
row2 = row.row()
|
||||
row2.alignment = "RIGHT"
|
||||
row2.label(text=str(item.total_elements))
|
||||
|
||||
@@ -47,3 +47,5 @@ class BIM_PT_misc_utilities(bpy.types.Panel):
|
||||
row.operator("bim.get_connected_system_elements")
|
||||
row = layout.row()
|
||||
row.operator("bim.draw_system_arrows")
|
||||
row = layout.row()
|
||||
row.operator("bim.clean_wireframes")
|
||||
|
||||
@@ -39,7 +39,12 @@ class AuthoringData:
|
||||
@classmethod
|
||||
def ifc_classes(cls):
|
||||
results = []
|
||||
classes = {e.is_a() for e in tool.Ifc.get().by_type("IfcElementType")}
|
||||
classes = {
|
||||
e.is_a()
|
||||
for e in tool.Ifc.get().by_type("IfcElementType")
|
||||
+ tool.Ifc.get().by_type("IfcDoorStyle")
|
||||
+ tool.Ifc.get().by_type("IfcWindowStyle")
|
||||
}
|
||||
results.extend([(c, c, "") for c in sorted(classes)])
|
||||
return results
|
||||
|
||||
|
||||
@@ -112,7 +112,7 @@ def add_object(self, context):
|
||||
|
||||
class BIM_OT_add_object(Operator):
|
||||
bl_idname = "mesh.add_grid"
|
||||
bl_label = "Grid"
|
||||
bl_label = "IFC Grid"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
u_spacing: FloatProperty(name="U Spacing", default=10)
|
||||
@@ -120,6 +120,10 @@ class BIM_OT_add_object(Operator):
|
||||
v_spacing: FloatProperty(name="V Spacing", default=10)
|
||||
total_v: IntProperty(name="Number of V Grids", default=3)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return IfcStore.get_file()
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import math
|
||||
import bmesh
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.unit
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.representation
|
||||
import mathutils.geometry
|
||||
import blenderbim.bim.handler
|
||||
import blenderbim.core.type
|
||||
import blenderbim.core.root
|
||||
import blenderbim.core.geometry
|
||||
import blenderbim.tool as tool
|
||||
from math import pi, degrees
|
||||
from mathutils import Vector, Matrix
|
||||
|
||||
|
||||
class MepGenerator:
|
||||
def __init__(self, relating_type):
|
||||
self.relating_type = relating_type
|
||||
|
||||
def generate(self):
|
||||
self.file = tool.Ifc.get()
|
||||
self.collection = bpy.context.view_layer.active_layer_collection.collection
|
||||
|
||||
if self.relating_type.is_a("IfcCableCarrierSegmentType"):
|
||||
pass
|
||||
elif self.relating_type.is_a("IfcCableSegmentType"):
|
||||
pass
|
||||
elif self.relating_type.is_a("IfcDuctSegmentType"):
|
||||
dimensions = ifcopenshell.util.element.get_psets(self.relating_type).get("Pset_DuctSegmentTypeCommon") or {}
|
||||
shape = dimensions.get("Shape") or dimensions.get("CrossSectionShape")
|
||||
|
||||
if shape == "RECTANGULAR":
|
||||
self.width = dimensions.get("NominalDiameterOrWidth")
|
||||
self.height = dimensions.get("NominalHeight")
|
||||
self.length = 1
|
||||
self.rotation = 0
|
||||
|
||||
return self.derive_from_cursor()
|
||||
elif self.relating_type.is_a("IfcPipeSegmentType"):
|
||||
pass
|
||||
|
||||
def derive_from_cursor(self):
|
||||
self.location = bpy.context.scene.cursor.location
|
||||
return self.create_rectangle_segment()
|
||||
|
||||
def create_rectangle_segment(self):
|
||||
verts = [
|
||||
Vector((0, self.width, 0)),
|
||||
Vector((0, 0, 0)),
|
||||
Vector((0, self.width, self.height)),
|
||||
Vector((0, 0, self.height)),
|
||||
Vector((self.length, self.width, 0)),
|
||||
Vector((self.length, 0, 0)),
|
||||
Vector((self.length, self.width, self.height)),
|
||||
Vector((self.length, 0, self.height)),
|
||||
]
|
||||
faces = [
|
||||
[1, 3, 2, 0],
|
||||
[4, 6, 7, 5],
|
||||
[1, 0, 4, 5],
|
||||
[3, 7, 6, 2],
|
||||
[0, 2, 6, 4],
|
||||
[1, 5, 7, 3],
|
||||
]
|
||||
mesh = bpy.data.meshes.new(name="Segment")
|
||||
mesh.from_pydata(verts, [], faces)
|
||||
|
||||
ifc_classes = ifcopenshell.util.type.get_applicable_entities(self.relating_type.is_a(), self.file.schema)
|
||||
ifc_class = ifc_classes[0]
|
||||
|
||||
obj = bpy.data.objects.new(tool.Model.generate_occurrence_name(self.relating_type, ifc_class), mesh)
|
||||
obj.location = self.location
|
||||
obj.rotation_euler[2] = self.rotation
|
||||
self.collection.objects.link(obj)
|
||||
|
||||
bpy.ops.bim.assign_class(
|
||||
obj=obj.name,
|
||||
ifc_class=ifc_class,
|
||||
ifc_representation_class="IfcExtrudedAreaSolid/IfcRectangleProfileDef",
|
||||
)
|
||||
|
||||
blenderbim.core.type.assign_type(tool.Ifc, tool.Type, element=tool.Ifc.get_entity(obj), type=self.relating_type)
|
||||
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="EPset_Parametric")
|
||||
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Engine": "BlenderBIM.Mep"})
|
||||
obj.select_set(True)
|
||||
return obj
|
||||
@@ -25,7 +25,7 @@ import ifcopenshell.util.representation
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.type
|
||||
import blenderbim.core.geometry
|
||||
from . import wall, slab, profile
|
||||
from . import wall, slab, profile, mep
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.api.pset.data import Data as PsetData
|
||||
from mathutils import Vector, Matrix
|
||||
@@ -81,6 +81,9 @@ class AddTypeInstance(bpy.types.Operator):
|
||||
elif material and material.is_a("IfcMaterialLayerSet"):
|
||||
if self.generate_layered_element(ifc_class, relating_type):
|
||||
return {"FINISHED"}
|
||||
if relating_type.is_a("IfcFlowSegmentType"):
|
||||
if mep.MepGenerator(relating_type).generate():
|
||||
return {"FINISHED"}
|
||||
|
||||
building_obj = None
|
||||
if len(context.selected_objects) == 1 and context.active_object:
|
||||
@@ -108,7 +111,7 @@ class AddTypeInstance(bpy.types.Operator):
|
||||
]
|
||||
mesh = bpy.data.meshes.new(name="Instance")
|
||||
mesh.from_pydata(verts, edges, faces)
|
||||
obj = bpy.data.objects.new("Instance", mesh)
|
||||
obj = bpy.data.objects.new(tool.Model.generate_occurrence_name(relating_type, instance_class), mesh)
|
||||
obj.location = context.scene.cursor.location
|
||||
collection = context.view_layer.active_layer_collection.collection
|
||||
collection.objects.link(obj)
|
||||
|
||||
@@ -138,18 +138,18 @@ class DumbProfileGenerator:
|
||||
[0, 2, 6, 4],
|
||||
]
|
||||
|
||||
ifc_classes = ifcopenshell.util.type.get_applicable_entities(self.relating_type.is_a(), self.file.schema)
|
||||
# Standard cases are deprecated, so let's cull them
|
||||
ifc_class = [c for c in ifc_classes if "StandardCase" not in c][0]
|
||||
|
||||
mesh = bpy.data.meshes.new(name="Dumb Profile")
|
||||
mesh.from_pydata(verts, edges, faces)
|
||||
obj = bpy.data.objects.new("Profile", mesh)
|
||||
obj = bpy.data.objects.new(tool.Model.generate_occurrence_name(self.relating_type, ifc_class), mesh)
|
||||
obj.location = self.location
|
||||
if self.collection_obj and self.collection_obj.BIMObjectProperties.ifc_definition_id:
|
||||
obj.location[2] = self.collection_obj.location[2]
|
||||
self.collection.objects.link(obj)
|
||||
|
||||
ifc_classes = ifcopenshell.util.type.get_applicable_entities(self.relating_type.is_a(), self.file.schema)
|
||||
# Standard cases are deprecated, so let's cull them
|
||||
ifc_class = [c for c in ifc_classes if "StandardCase" not in c][0]
|
||||
obj.name = ifc_class[3:]
|
||||
bpy.ops.bim.assign_class(obj=obj.name, ifc_class=ifc_class, should_add_representation=False)
|
||||
|
||||
if self.relating_type.is_a() in ["IfcBeamType", "IfcMemberType"]:
|
||||
|
||||
@@ -53,3 +53,8 @@ def update_ifc_class(self, context):
|
||||
class BIMModelProperties(PropertyGroup):
|
||||
ifc_class: bpy.props.EnumProperty(items=get_ifc_class, name="IFC Class", update=update_ifc_class)
|
||||
relating_type: bpy.props.EnumProperty(items=get_relating_type, name="Relating Type")
|
||||
occurrence_name_style: bpy.props.EnumProperty(
|
||||
items=[("CLASS", "By Class", ""), ("TYPE", "By Type", ""), ("CUSTOM", "Custom", "")],
|
||||
name="Occurrence Name Style",
|
||||
)
|
||||
occurrence_name_function: bpy.props.StringProperty(name="Occurrence Name Function")
|
||||
|
||||
@@ -278,19 +278,18 @@ class DumbSlabGenerator:
|
||||
edges = []
|
||||
faces = [[0, 3, 2, 1]]
|
||||
|
||||
ifc_classes = ifcopenshell.util.type.get_applicable_entities(self.relating_type.is_a(), self.file.schema)
|
||||
# Standard cases are deprecated, so let's cull them
|
||||
ifc_class = [c for c in ifc_classes if "StandardCase" not in c][0]
|
||||
|
||||
mesh = bpy.data.meshes.new(name="Dumb Slab")
|
||||
mesh.from_pydata(verts, edges, faces)
|
||||
obj = bpy.data.objects.new("Slab", mesh)
|
||||
obj = bpy.data.objects.new(tool.Model.generate_occurrence_name(self.relating_type, ifc_class), mesh)
|
||||
modifier = obj.modifiers.new("Slab Depth", "SOLIDIFY")
|
||||
modifier.use_even_offset = True
|
||||
modifier.offset = 1
|
||||
modifier.thickness = self.depth
|
||||
|
||||
ifc_classes = ifcopenshell.util.type.get_applicable_entities(self.relating_type.is_a(), self.file.schema)
|
||||
# Standard cases are deprecated, so let's cull them
|
||||
ifc_class = [c for c in ifc_classes if "StandardCase" not in c][0]
|
||||
|
||||
obj.name = ifc_class[3:]
|
||||
obj.location = self.location
|
||||
if self.collection_obj and self.collection_obj.BIMObjectProperties.ifc_definition_id:
|
||||
obj.location[2] = self.collection_obj.location[2] - self.depth
|
||||
|
||||
@@ -795,7 +795,7 @@ class DumbWallGenerator:
|
||||
# Standard cases are deprecated, so let's cull them
|
||||
ifc_class = [c for c in ifc_classes if "StandardCase" not in c][0]
|
||||
|
||||
obj = bpy.data.objects.new(ifc_class[3:], mesh)
|
||||
obj = bpy.data.objects.new(tool.Model.generate_occurrence_name(self.relating_type, ifc_class), mesh)
|
||||
obj.location = self.location
|
||||
obj.rotation_euler[2] = self.rotation
|
||||
if self.collection_obj and self.collection_obj.BIMObjectProperties.ifc_definition_id:
|
||||
@@ -866,25 +866,24 @@ def calculate_quantities(usecase_path, ifc_file, settings):
|
||||
width = obj.dimensions[1] / unit_scale
|
||||
height = obj.dimensions[2] / unit_scale
|
||||
|
||||
if product.HasOpenings:
|
||||
# TODO: calculate gross / net
|
||||
gross_footprint_area = 0
|
||||
net_footprint_area = 0
|
||||
gross_side_area = 0
|
||||
net_side_area = 0
|
||||
gross_volume = 0
|
||||
net_volume = 0
|
||||
else:
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(obj.data)
|
||||
bm.faces.ensure_lookup_table()
|
||||
gross_footprint_area = sum([f.calc_area() for f in bm.faces if f.normal.z < -0.9])
|
||||
net_footprint_area = gross_footprint_area
|
||||
gross_side_area = sum([f.calc_area() for f in bm.faces if f.normal.y > 0.9])
|
||||
net_side_area = gross_side_area
|
||||
gross_volume = bm.calc_volume()
|
||||
net_volume = gross_volume
|
||||
bm.free()
|
||||
bm_gross = bmesh.new()
|
||||
bm_gross.from_mesh(obj.data)
|
||||
bm_gross.faces.ensure_lookup_table()
|
||||
|
||||
bm_net = bmesh.new()
|
||||
depsgraph = bpy.context.evaluated_depsgraph_get()
|
||||
evaluated_mesh = obj.evaluated_get(depsgraph).data
|
||||
bm_net.from_mesh(evaluated_mesh)
|
||||
bm_net.faces.ensure_lookup_table()
|
||||
|
||||
gross_footprint_area = sum([f.calc_area() for f in bm_gross.faces if f.normal.z < -0.9])
|
||||
net_footprint_area = sum([f.calc_area() for f in bm_net.faces if f.normal.z < -0.9])
|
||||
gross_side_area = sum([f.calc_area() for f in bm_gross.faces if f.normal.y > 0.9])
|
||||
net_side_area = sum([f.calc_area() for f in bm_net.faces if f.normal.y > 0.9])
|
||||
gross_volume = bm_gross.calc_volume()
|
||||
net_volume = bm_net.calc_volume()
|
||||
bm_gross.free()
|
||||
bm_net.free()
|
||||
|
||||
ifcopenshell.api.run(
|
||||
"pset.edit_qto",
|
||||
|
||||
@@ -52,7 +52,7 @@ class BimTool(WorkSpaceTool):
|
||||
)
|
||||
|
||||
def draw_settings(context, layout, tool):
|
||||
if not AuthoringData.is_loaded:
|
||||
if not AuthoringData.is_loaded and IfcStore.get_file():
|
||||
AuthoringData.load()
|
||||
|
||||
row = layout.row(align=True)
|
||||
|
||||
@@ -20,6 +20,7 @@ import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.AddActor,
|
||||
operator.AddAddress,
|
||||
operator.AddAddressAttribute,
|
||||
operator.AddOrganisation,
|
||||
@@ -28,18 +29,22 @@ classes = (
|
||||
operator.AddPersonAttribute,
|
||||
operator.AddRole,
|
||||
operator.ClearUser,
|
||||
operator.DisableEditingActor,
|
||||
operator.DisableEditingAddress,
|
||||
operator.DisableEditingOrganisation,
|
||||
operator.DisableEditingPerson,
|
||||
operator.DisableEditingRole,
|
||||
operator.EditActor,
|
||||
operator.EditAddress,
|
||||
operator.EditOrganisation,
|
||||
operator.EditPerson,
|
||||
operator.EditRole,
|
||||
operator.EnableEditingActor,
|
||||
operator.EnableEditingAddress,
|
||||
operator.EnableEditingOrganisation,
|
||||
operator.EnableEditingPerson,
|
||||
operator.EnableEditingRole,
|
||||
operator.RemoveActor,
|
||||
operator.RemoveAddress,
|
||||
operator.RemoveAddressAttribute,
|
||||
operator.RemoveOrganisation,
|
||||
@@ -52,6 +57,7 @@ classes = (
|
||||
ui.BIM_PT_people,
|
||||
ui.BIM_PT_organisations,
|
||||
ui.BIM_PT_owner,
|
||||
ui.BIM_PT_actor,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ def refresh():
|
||||
PeopleData.is_loaded = False
|
||||
OrganisationsData.is_loaded = False
|
||||
OwnerData.is_loaded = False
|
||||
ActorData.is_loaded = False
|
||||
|
||||
|
||||
class RolesAddressesData:
|
||||
@@ -202,3 +203,30 @@ class OwnerData:
|
||||
}
|
||||
)
|
||||
return results
|
||||
|
||||
|
||||
class ActorData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {
|
||||
"actor": cls.actor(),
|
||||
"actors": cls.actors(),
|
||||
}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def actor(cls):
|
||||
ifc_class = bpy.context.scene.BIMOwnerProperties.actor_type
|
||||
return [(str(p.id()), p[0] or "Unnamed", "") for p in tool.Ifc.get().by_type(ifc_class)]
|
||||
|
||||
@classmethod
|
||||
def actors(cls):
|
||||
actors = []
|
||||
props = bpy.context.scene.BIMOwnerProperties
|
||||
for actor in tool.Ifc.get().by_type(props.actor_class, include_subtypes=False):
|
||||
is_editing = props.active_actor_id == actor.id()
|
||||
actors.append({"id": actor.id(), "name": actor.Name or "Unnamed", "is_editing": is_editing})
|
||||
return actors
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import ifcopenshell.api
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.owner as core
|
||||
import blenderbim.bim.handler
|
||||
@@ -307,3 +306,52 @@ class ClearUser(bpy.types.Operator, Operator):
|
||||
|
||||
def _execute(self, context):
|
||||
core.clear_user(tool.Owner)
|
||||
|
||||
|
||||
class AddActor(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.add_actor"
|
||||
bl_label = "Add Actor"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
props = bpy.context.scene.BIMOwnerProperties
|
||||
if props.actor:
|
||||
core.add_actor(tool.Ifc, ifc_class=props.actor_class, actor=tool.Ifc.get().by_id(int(props.actor)))
|
||||
|
||||
|
||||
class EnableEditingActor(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.enable_editing_actor"
|
||||
bl_label = "Enable Editing Actor"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
actor: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.enable_editing_actor(tool.Owner, actor=tool.Ifc.get().by_id(self.actor))
|
||||
|
||||
|
||||
class DisableEditingActor(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.disable_editing_actor"
|
||||
bl_label = "Disable Editing Actor"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.disable_editing_actor(tool.Owner)
|
||||
|
||||
|
||||
class EditActor(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.edit_actor"
|
||||
bl_label = "Edit Actor"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.edit_actor(tool.Ifc, tool.Owner)
|
||||
|
||||
|
||||
class RemoveActor(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.remove_actor"
|
||||
bl_label = "Remove Actor"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
actor: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.remove_actor(tool.Ifc, actor=tool.Ifc.get().by_id(self.actor))
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
import bpy
|
||||
from blenderbim.bim.prop import StrProperty, Attribute
|
||||
from blenderbim.bim.module.owner.data import OwnerData
|
||||
from blenderbim.bim.module.owner.data import OwnerData, ActorData
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
@@ -44,6 +44,20 @@ def get_user_organisation(self, context):
|
||||
return OwnerData.data["user_organisation"]
|
||||
|
||||
|
||||
def get_actor(self, context):
|
||||
if not ActorData.is_loaded:
|
||||
ActorData.load()
|
||||
return ActorData.data["actor"]
|
||||
|
||||
|
||||
def update_actor_type(self, context):
|
||||
ActorData.data["actor"] = ActorData.actor()
|
||||
|
||||
|
||||
def update_actor_class(self, context):
|
||||
ActorData.data["actors"] = ActorData.actors()
|
||||
|
||||
|
||||
class BIMOwnerProperties(PropertyGroup):
|
||||
active_person_id: IntProperty(name="Active Person Id")
|
||||
person_attributes: CollectionProperty(name="Person Attributes", type=Attribute)
|
||||
@@ -64,3 +78,20 @@ class BIMOwnerProperties(PropertyGroup):
|
||||
user_person: EnumProperty(items=get_user_person, name="Person")
|
||||
user_organisation: EnumProperty(items=get_user_organisation, name="Organisation")
|
||||
active_user_id: IntProperty(name="Active User Id")
|
||||
active_actor_id: IntProperty(name="Active Actor Id")
|
||||
actor_attributes: CollectionProperty(name="Actor Attributes", type=Attribute)
|
||||
actor_class: EnumProperty(
|
||||
items=(("IfcActor", "Actor", ""), ("IfcOccupant", "Occupant", "")),
|
||||
name="Actor Type",
|
||||
update=update_actor_class,
|
||||
)
|
||||
actor_type: EnumProperty(
|
||||
items=(
|
||||
("IfcPerson", "Person", ""),
|
||||
("IfcOrganization", "Organisation", ""),
|
||||
("IfcPersonAndOrganization", "User", ""),
|
||||
),
|
||||
name="Actor Type",
|
||||
update=update_actor_type,
|
||||
)
|
||||
actor: EnumProperty(items=get_actor, name="Actor")
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
import bpy
|
||||
import blenderbim.bim.helper
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim.module.owner.data import PeopleData, OrganisationsData, OwnerData
|
||||
from blenderbim.bim.module.owner.data import PeopleData, OrganisationsData, OwnerData, ActorData
|
||||
|
||||
|
||||
def draw_roles(box, parent):
|
||||
@@ -227,3 +227,52 @@ class BIM_PT_owner(bpy.types.Panel):
|
||||
row.operator("bim.set_user", icon="KEYFRAME_HLT", text="").user = user["id"]
|
||||
op = row.operator("bim.remove_person_and_organisation", icon="X", text="")
|
||||
op.person_and_organisation = user["id"]
|
||||
|
||||
|
||||
class BIM_PT_actor(bpy.types.Panel):
|
||||
bl_label = "IFC Actor"
|
||||
bl_idname = "BIM_PT_actor"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_project_setup"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return tool.Ifc.get()
|
||||
|
||||
def draw(self, context):
|
||||
if not ActorData.is_loaded:
|
||||
ActorData.load()
|
||||
|
||||
self.props = context.scene.BIMOwnerProperties
|
||||
|
||||
self.layout.use_property_split = True
|
||||
self.layout.use_property_decorate = False
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.props, "actor_class", text="")
|
||||
row.prop(self.props, "actor_type", text="")
|
||||
if ActorData.data["actor"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.props, "actor", text="")
|
||||
row.operator("bim.add_actor", icon="ADD", text="")
|
||||
else:
|
||||
self.layout.label(text="No users found.")
|
||||
|
||||
for actor in ActorData.data["actors"]:
|
||||
self.draw_actor(actor)
|
||||
|
||||
def draw_actor(self, actor):
|
||||
if actor["is_editing"]:
|
||||
box = self.layout.box()
|
||||
row = box.row(align=True)
|
||||
row.operator("bim.edit_actor", icon="CHECKMARK")
|
||||
row.operator("bim.disable_editing_actor", icon="CANCEL", text="")
|
||||
blenderbim.bim.helper.draw_attributes(self.props.actor_attributes, box)
|
||||
else:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=actor["name"])
|
||||
row.operator("bim.enable_editing_actor", icon="GREASEPENCIL", text="").actor = actor["id"]
|
||||
row.operator("bim.remove_actor", icon="X", text="").actor = actor["id"]
|
||||
|
||||
@@ -20,9 +20,10 @@ import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.ExecuteIfcPatch,
|
||||
operator.RunMigratePatch,
|
||||
operator.SelectIfcPatchInput,
|
||||
operator.SelectIfcPatchOutput,
|
||||
operator.ExecuteIfcPatch,
|
||||
operator.UpdateIfcPatchArguments,
|
||||
prop.BIMPatchProperties,
|
||||
ui.BIM_PT_patch,
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
import os
|
||||
import bpy
|
||||
import json
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.patch as core
|
||||
import blenderbim.bim.handler
|
||||
|
||||
try:
|
||||
import ifcpatch
|
||||
@@ -114,3 +117,20 @@ class UpdateIfcPatchArguments(bpy.types.Operator):
|
||||
new_attr.name = arg_name
|
||||
new_attr.set_value(arg_info.get("default", new_attr.get_value_default()))
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RunMigratePatch(bpy.types.Operator):
|
||||
bl_idname = "bim.run_migrate_patch"
|
||||
bl_label = "Run Migrate Patch"
|
||||
infile: bpy.props.StringProperty()
|
||||
outfile: bpy.props.StringProperty()
|
||||
schema: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
core.run_migrate_patch(tool.Patch, infile=self.infile, outfile=self.outfile, schema=self.schema)
|
||||
try:
|
||||
bpy.ops.file.refresh()
|
||||
except:
|
||||
pass # Probably running in headless mode
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import bpy
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
@@ -31,9 +32,32 @@ class ProjectData:
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {"export_schema": cls.get_export_schema()}
|
||||
cls.data = {
|
||||
"export_schema": cls.get_export_schema(),
|
||||
"template_file": cls.template_file(),
|
||||
"last_saved": cls.last_saved(),
|
||||
}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def get_export_schema(cls):
|
||||
return [(s, s, "") for s in IfcStore.schema_identifiers]
|
||||
|
||||
@classmethod
|
||||
def template_file(cls):
|
||||
files = os.listdir(os.path.join(bpy.context.scene.BIMProperties.data_dir, "libraries"))
|
||||
results = [("0", "Blank Project", "")]
|
||||
results.extend([(f, f.replace(".ifc", ""), "") for f in files if ".ifc" in f])
|
||||
return results
|
||||
|
||||
@classmethod
|
||||
def last_saved(cls):
|
||||
ifc = tool.Ifc.get()
|
||||
if not ifc:
|
||||
return ""
|
||||
try:
|
||||
save_datetime = ifc.wrapped_data.header.file_name.time_stamp
|
||||
save_date, save_time = save_datetime.split("T")
|
||||
return f"{save_date} {':'.join(save_time.split(':')[0:2])}"
|
||||
except:
|
||||
return ""
|
||||
|
||||
@@ -27,6 +27,7 @@ import ifcopenshell.util.selector
|
||||
import ifcopenshell.util.representation
|
||||
import blenderbim.bim.handler
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.project as core
|
||||
import blenderbim.core.context
|
||||
import blenderbim.core.owner
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
@@ -45,80 +46,16 @@ class CreateProject(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
IfcStore.begin_transaction(self)
|
||||
IfcStore.add_transaction_operation(self, rollback=self.rollback, commit=lambda data: True)
|
||||
result = self._execute(context)
|
||||
self.transaction_data = {"file": self.file}
|
||||
self._execute(context)
|
||||
self.transaction_data = {"file": tool.Ifc.get()}
|
||||
IfcStore.add_transaction_operation(self, rollback=lambda data: True, commit=self.commit)
|
||||
IfcStore.end_transaction(self)
|
||||
return result
|
||||
|
||||
def _execute(self, context):
|
||||
active_object = context.view_layer.objects.active
|
||||
self.file = IfcStore.get_file()
|
||||
if self.file:
|
||||
return {"FINISHED"}
|
||||
|
||||
IfcStore.file = ifcopenshell.api.run(
|
||||
"project.create_file", **{"version": context.scene.BIMProjectProperties.export_schema}
|
||||
)
|
||||
self.file = IfcStore.get_file()
|
||||
|
||||
if self.file.schema == "IFC2X3":
|
||||
person = blenderbim.core.owner.add_person(tool.Ifc)
|
||||
organisation = blenderbim.core.owner.add_organisation(tool.Ifc)
|
||||
user = blenderbim.core.owner.add_person_and_organisation(tool.Ifc, person=person, organisation=organisation)
|
||||
blenderbim.core.owner.set_user(tool.Owner, user=user)
|
||||
|
||||
project = bpy.data.objects.new(self.get_name("IfcProject", "My Project"), None)
|
||||
site = bpy.data.objects.new(self.get_name("IfcSite", "My Site"), None)
|
||||
building = bpy.data.objects.new(self.get_name("IfcBuilding", "My Building"), None)
|
||||
building_storey = bpy.data.objects.new(self.get_name("IfcBuildingStorey", "My Storey"), None)
|
||||
|
||||
bpy.ops.bim.assign_class(obj=project.name, ifc_class="IfcProject")
|
||||
blenderbim.core.unit.assign_scene_units(tool.Ifc, tool.Unit)
|
||||
|
||||
model = blenderbim.core.context.add_context(
|
||||
tool.Ifc, context_type="Model", context_identifier="", target_view="", parent=0
|
||||
)
|
||||
body_context = blenderbim.core.context.add_context(
|
||||
tool.Ifc, context_type="Model", context_identifier="Body", target_view="MODEL_VIEW", parent=model
|
||||
)
|
||||
blenderbim.core.context.add_context(
|
||||
tool.Ifc, context_type="Model", context_identifier="Box", target_view="MODEL_VIEW", parent=model
|
||||
)
|
||||
plan = blenderbim.core.context.add_context(
|
||||
tool.Ifc, context_type="Plan", context_identifier="", target_view="", parent=0
|
||||
)
|
||||
blenderbim.core.context.add_context(
|
||||
tool.Ifc, context_type="Plan", context_identifier="Annotation", target_view="PLAN_VIEW", parent=plan
|
||||
)
|
||||
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
context.scene.BIMRootProperties.contexts = str(body_context.id())
|
||||
|
||||
bpy.ops.bim.assign_class(obj=site.name, ifc_class="IfcSite")
|
||||
bpy.ops.bim.assign_class(obj=building.name, ifc_class="IfcBuilding")
|
||||
bpy.ops.bim.assign_class(obj=building_storey.name, ifc_class="IfcBuildingStorey")
|
||||
|
||||
blenderbim.core.aggregate.assign_object(
|
||||
tool.Ifc, tool.Aggregate, tool.Collector, relating_obj=project, related_obj=site
|
||||
)
|
||||
blenderbim.core.aggregate.assign_object(
|
||||
tool.Ifc, tool.Aggregate, tool.Collector, relating_obj=site, related_obj=building
|
||||
)
|
||||
blenderbim.core.aggregate.assign_object(
|
||||
tool.Ifc, tool.Aggregate, tool.Collector, relating_obj=building, related_obj=building_storey
|
||||
)
|
||||
|
||||
context.view_layer.objects.active = active_object
|
||||
return {"FINISHED"}
|
||||
|
||||
def get_name(self, ifc_class, name):
|
||||
if not bpy.data.objects.get(f"{ifc_class}/{name}"):
|
||||
return name
|
||||
i = 2
|
||||
while bpy.data.objects.get(f"{ifc_class}/{name} {i}"):
|
||||
i += 1
|
||||
return f"{name} {i}"
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMProjectProperties
|
||||
template = None if props.template_file == "0" else props.template_file
|
||||
core.create_project(tool.Ifc, tool.Project, schema=props.export_schema, template=template)
|
||||
|
||||
def rollback(self, data):
|
||||
IfcStore.file = None
|
||||
@@ -373,7 +310,11 @@ class AppendLibraryElement(bpy.types.Operator):
|
||||
self.import_type_from_ifc(element, context)
|
||||
elif element.is_a("IfcMaterial"):
|
||||
self.import_material_from_ifc(element, context)
|
||||
context.scene.BIMProjectProperties.library_elements[self.prop_index].is_appended = True
|
||||
try:
|
||||
context.scene.BIMProjectProperties.library_elements[self.prop_index].is_appended = True
|
||||
except:
|
||||
# TODO Remove this terrible code when I refactor this into the core
|
||||
pass
|
||||
blenderbim.bim.handler.purge_module_data()
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -670,10 +611,14 @@ class LinkIfc(bpy.types.Operator):
|
||||
bl_description = "Link a Blender file"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
filter_glob: bpy.props.StringProperty(default="*.blend;*.blend1", options={"HIDDEN"})
|
||||
use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=False)
|
||||
|
||||
def execute(self, context):
|
||||
new = context.scene.BIMProjectProperties.links.add()
|
||||
new.name = self.filepath
|
||||
filepath = self.filepath
|
||||
if self.use_relative_path:
|
||||
filepath = os.path.relpath(filepath, bpy.path.abspath("//"))
|
||||
new.name = filepath
|
||||
bpy.ops.bim.load_link(filepath=self.filepath)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -705,13 +650,16 @@ class UnloadLink(bpy.types.Operator):
|
||||
filepath: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
filepath = self.filepath
|
||||
if not os.path.isabs(filepath):
|
||||
filepath = os.path.abspath(os.path.join(bpy.path.abspath("//"), filepath))
|
||||
for collection in context.scene.collection.children:
|
||||
if collection.library and collection.library.filepath == self.filepath:
|
||||
if collection.library and collection.library.filepath == filepath:
|
||||
context.scene.collection.children.unlink(collection)
|
||||
for scene in bpy.data.scenes:
|
||||
if scene.library and scene.library.filepath == self.filepath:
|
||||
if scene.library and scene.library.filepath == filepath:
|
||||
bpy.data.scenes.remove(scene)
|
||||
link = context.scene.BIMProjectProperties.links.get(self.filepath)
|
||||
link = context.scene.BIMProjectProperties.links.get(filepath)
|
||||
link.is_loaded = False
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -724,16 +672,19 @@ class LoadLink(bpy.types.Operator):
|
||||
filepath: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
filepath = self.filepath
|
||||
if not os.path.isabs(filepath):
|
||||
filepath = os.path.abspath(os.path.join(bpy.path.abspath("//"), filepath))
|
||||
with bpy.data.libraries.load(self.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 != self.filepath:
|
||||
if not scene.library or scene.library.filepath != 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 = context.scene.BIMProjectProperties.links.get(filepath)
|
||||
link.is_loaded = True
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -38,6 +38,12 @@ def get_export_schema(self, context):
|
||||
return ProjectData.data["export_schema"]
|
||||
|
||||
|
||||
def get_template_file(self, context):
|
||||
if not ProjectData.is_loaded:
|
||||
ProjectData.load()
|
||||
return ProjectData.data["template_file"]
|
||||
|
||||
|
||||
def update_filter_mode(self, context):
|
||||
self.filter_categories.clear()
|
||||
if self.filter_mode == "NONE":
|
||||
@@ -132,6 +138,7 @@ class BIMProjectProperties(PropertyGroup):
|
||||
links: CollectionProperty(name="Links", type=Link)
|
||||
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")
|
||||
|
||||
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))
|
||||
|
||||
@@ -28,7 +28,7 @@ class BIM_PT_project(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_project_setup"
|
||||
bl_parent_id = "BIM_PT_project_info"
|
||||
|
||||
def draw(self, context):
|
||||
if not ProjectData.is_loaded:
|
||||
@@ -94,8 +94,7 @@ class BIM_PT_project(Panel):
|
||||
props = context.scene.BIMProperties
|
||||
pprops = context.scene.BIMProjectProperties
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="IFC Filename", icon="FILE")
|
||||
row.label(text=os.path.basename(props.ifc_file) or "No File Found")
|
||||
row.label(text=os.path.basename(props.ifc_file) or "No File Found", icon="FILE")
|
||||
|
||||
if IfcStore.get_file():
|
||||
row.prop(pprops, "is_authoring", icon="MODIFIER", text="")
|
||||
@@ -136,20 +135,21 @@ class BIM_PT_project(Panel):
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="File Not Loaded", icon="ERROR")
|
||||
|
||||
if props.ifc_file:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Saved", icon="EXPORT")
|
||||
row.label(text=ProjectData.data["last_saved"])
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "ifc_file", text="")
|
||||
row.operator("bim.reload_ifc_file", icon="FILE_REFRESH", text="")
|
||||
row.operator("bim.select_ifc_file", icon="FILE_FOLDER", text="")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "ifc_file", text="")
|
||||
row.operator("bim.reload_ifc_file", icon="FILE_REFRESH", text="")
|
||||
op = row.operator("export_ifc.bim", icon="FILE_TICK", text="")
|
||||
op = row.operator("export_ifc.bim", icon="EXPORT", text="Save Project")
|
||||
op.should_save_as = False
|
||||
op = row.operator("export_ifc.bim", icon="FILE_TICK", text="Save As")
|
||||
op.should_save_as = True
|
||||
row.operator("bim.select_ifc_file", icon="FILE_FOLDER", text="")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "schema_dir")
|
||||
row.operator("bim.select_schema_dir", icon="FILE_FOLDER", text="")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "data_dir")
|
||||
row.operator("bim.select_data_dir", icon="FILE_FOLDER", text="")
|
||||
|
||||
def draw_create_project_ui(self, context):
|
||||
props = context.scene.BIMProperties
|
||||
@@ -164,6 +164,9 @@ class BIM_PT_project(Panel):
|
||||
row.prop(props, "area_unit", text="Area Unit")
|
||||
row = self.layout.row()
|
||||
row.prop(props, "volume_unit", text="Volume Unit")
|
||||
row = self.layout.row()
|
||||
row.prop(pprops, "template_file", text="Template")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.create_project")
|
||||
row.operator("bim.load_project")
|
||||
@@ -281,11 +284,12 @@ class BIM_UL_links(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if item:
|
||||
row = layout.row(align=True)
|
||||
row.label(text=item.name)
|
||||
if item.is_loaded:
|
||||
row.label(text=item.name)
|
||||
op = row.operator("bim.unload_link", text="", icon="UNLINKED")
|
||||
op.filepath = item.name
|
||||
else:
|
||||
row.prop(item, "name", text="")
|
||||
op = row.operator("bim.load_link", text="", icon="LINKED")
|
||||
op.filepath = item.name
|
||||
op = row.operator("bim.unlink_ifc", text="", icon="X")
|
||||
|
||||
@@ -192,7 +192,6 @@ class BIM_PT_material_psets(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "material"
|
||||
bl_parent_id = "BIM_PT_object_metadata"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
|
||||
@@ -20,13 +20,12 @@ import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.EnableReassignClass,
|
||||
operator.DisableReassignClass,
|
||||
operator.ReassignClass,
|
||||
operator.AssignClass,
|
||||
operator.UnassignClass,
|
||||
operator.UnlinkObject,
|
||||
operator.CopyClass,
|
||||
operator.DisableReassignClass,
|
||||
operator.EnableReassignClass,
|
||||
operator.ReassignClass,
|
||||
operator.UnlinkObject,
|
||||
prop.BIMRootProperties,
|
||||
ui.BIM_PT_class,
|
||||
)
|
||||
|
||||
@@ -32,12 +32,47 @@ class IfcClassData:
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.is_loaded = True
|
||||
cls.data = {
|
||||
"contexts": cls.contexts(),
|
||||
"has_entity": cls.has_entity(),
|
||||
"name": cls.name(),
|
||||
"ifc_class": cls.ifc_class(),
|
||||
}
|
||||
cls.data = {}
|
||||
cls.data["ifc_products"] = cls.ifc_products()
|
||||
cls.data["ifc_classes"] = cls.ifc_classes()
|
||||
cls.data["contexts"] = cls.contexts()
|
||||
cls.data["has_entity"] = cls.has_entity()
|
||||
cls.data["name"] = cls.name()
|
||||
cls.data["ifc_class"] = cls.ifc_class()
|
||||
|
||||
@classmethod
|
||||
def ifc_products(cls):
|
||||
products = [
|
||||
"IfcElement",
|
||||
"IfcElementType",
|
||||
"IfcSpatialElement",
|
||||
"IfcGroup",
|
||||
"IfcStructuralItem",
|
||||
"IfcContext",
|
||||
"IfcAnnotation",
|
||||
"IfcRelSpaceBoundary",
|
||||
]
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
products = [
|
||||
"IfcElement",
|
||||
"IfcElementType",
|
||||
"IfcSpatialStructureElement",
|
||||
"IfcGroup",
|
||||
"IfcStructuralItem",
|
||||
"IfcAnnotation",
|
||||
"IfcRelSpaceBoundary",
|
||||
]
|
||||
return [(e, e, "") for e in products]
|
||||
|
||||
@classmethod
|
||||
def ifc_classes(cls):
|
||||
ifc_product = bpy.context.scene.BIMRootProperties.ifc_product
|
||||
declaration = tool.Ifc.schema().declaration_by_name(ifc_product)
|
||||
declarations = ifcopenshell.util.schema.get_subtypes(declaration)
|
||||
names = [d.name() for d in declarations]
|
||||
if ifc_product == "IfcElementType":
|
||||
names.extend(("IfcDoorStyle", "IfcWindowStyle"))
|
||||
return [(c, c, "") for c in sorted(names)]
|
||||
|
||||
@classmethod
|
||||
def contexts(cls):
|
||||
|
||||
@@ -113,7 +113,7 @@ class ReassignClass(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AssignClass(bpy.types.Operator):
|
||||
class AssignClass(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.assign_class"
|
||||
bl_label = "Assign IFC Class"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
@@ -126,178 +126,29 @@ class AssignClass(bpy.types.Operator):
|
||||
should_add_representation: bpy.props.BoolProperty(default=True)
|
||||
ifc_representation_class: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
objects = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects
|
||||
self.file = IfcStore.get_file()
|
||||
if not self.ifc_class:
|
||||
self.ifc_class = context.scene.BIMRootProperties.ifc_class
|
||||
self.declaration = IfcStore.get_schema().declaration_by_name(self.ifc_class)
|
||||
if self.predefined_type == "USERDEFINED":
|
||||
self.predefined_type = self.userdefined_type
|
||||
ifc_class = self.ifc_class or context.scene.BIMRootProperties.ifc_class
|
||||
predefined_type = self.userdefined_type if self.predefined_type == "USERDEFINED" else self.predefined_type
|
||||
ifc_context = self.context_id
|
||||
if not ifc_context and get_contexts(self, context):
|
||||
ifc_context = int(context.scene.BIMRootProperties.contexts or "0") or None
|
||||
if ifc_context:
|
||||
ifc_context = tool.Ifc.get().by_id(ifc_context)
|
||||
active_object = context.active_object
|
||||
for obj in objects:
|
||||
self.assign_class(context, obj)
|
||||
return {"FINISHED"}
|
||||
|
||||
def assign_class(self, context, obj):
|
||||
if obj.BIMObjectProperties.ifc_definition_id:
|
||||
return
|
||||
product = ifcopenshell.api.run(
|
||||
"root.create_entity",
|
||||
self.file,
|
||||
**{
|
||||
"ifc_class": self.ifc_class,
|
||||
"predefined_type": self.predefined_type or None,
|
||||
"name": obj.name,
|
||||
},
|
||||
)
|
||||
obj.name = "{}/{}".format(product.is_a(), obj.name)
|
||||
IfcStore.link_element(product, obj)
|
||||
|
||||
if self.should_add_representation:
|
||||
ifc_context = self.context_id
|
||||
if not ifc_context and get_contexts(self, context):
|
||||
ifc_context = int(context.scene.BIMRootProperties.contexts or "0") or None
|
||||
if ifc_context:
|
||||
ifc_context = tool.Ifc.get().by_id(ifc_context)
|
||||
blenderbim.core.geometry.add_representation(
|
||||
core.assign_class(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
tool.Style,
|
||||
tool.Surveyor,
|
||||
tool.Collector,
|
||||
tool.Root,
|
||||
obj=obj,
|
||||
ifc_class=ifc_class,
|
||||
predefined_type=predefined_type,
|
||||
should_add_representation=self.should_add_representation,
|
||||
context=ifc_context,
|
||||
ifc_representation_class=self.ifc_representation_class,
|
||||
profile_set_usage=None,
|
||||
)
|
||||
|
||||
if product.is_a("IfcElementType"):
|
||||
tool.Collector.assign(obj)
|
||||
elif product.is_a("IfcOpeningElement"):
|
||||
obj.display_type = "WIRE"
|
||||
tool.Collector.assign(obj)
|
||||
elif (
|
||||
product.is_a("IfcSpatialElement")
|
||||
or product.is_a("IfcSpatialStructureElement")
|
||||
or product.is_a("IfcProject")
|
||||
or product.is_a("IfcContext")
|
||||
):
|
||||
self.place_in_spatial_collection(obj, context)
|
||||
elif product.is_a("IfcStructuralItem"):
|
||||
if product.is_a("IfcStructuralMember"):
|
||||
self.place_in_structural_items_collection(obj, context, structural_collection="Members")
|
||||
elif product.is_a("IfcStructuralConnection"):
|
||||
self.place_in_structural_items_collection(obj, context, structural_collection="Connections")
|
||||
else:
|
||||
self.assign_potential_spatial_container(obj)
|
||||
context.view_layer.objects.active = obj
|
||||
|
||||
def place_in_spatial_collection(self, obj, context):
|
||||
for collection in obj.users_collection:
|
||||
if collection.name == obj.name:
|
||||
return
|
||||
parent_collection = None
|
||||
for collection in obj.users_collection:
|
||||
collection.objects.unlink(obj)
|
||||
if "Ifc" in collection.name:
|
||||
parent_collection = collection
|
||||
collection = bpy.data.collections.new(obj.name)
|
||||
collection.objects.link(obj)
|
||||
if parent_collection:
|
||||
parent_collection.children.link(collection)
|
||||
blenderbim.core.aggregate.assign_object(
|
||||
tool.Ifc,
|
||||
tool.Aggregate,
|
||||
tool.Collector,
|
||||
relating_obj=bpy.data.objects.get(parent_collection.name),
|
||||
related_obj=obj,
|
||||
)
|
||||
else:
|
||||
context.scene.collection.children.link(collection)
|
||||
|
||||
def place_in_structural_items_collection(self, obj, context, structural_collection):
|
||||
for project in [c for c in context.view_layer.layer_collection.children if "IfcProject" in c.name]:
|
||||
if not [c for c in project.children if "StructuralItems" in c.name]:
|
||||
members = bpy.data.collections.new("Members")
|
||||
connections = bpy.data.collections.new("Connections")
|
||||
items = bpy.data.collections.new("StructuralItems")
|
||||
items.children.link(members)
|
||||
items.children.link(connections)
|
||||
project.collection.children.link(items)
|
||||
|
||||
for coll in [c for c in project.children if "StructuralItems" in c.name]:
|
||||
for collection in [c for c in coll.children if structural_collection in c.name]:
|
||||
for user_collection in obj.users_collection:
|
||||
user_collection.objects.unlink(obj)
|
||||
collection.collection.objects.link(obj)
|
||||
break
|
||||
break
|
||||
break
|
||||
|
||||
def assign_potential_spatial_container(self, obj):
|
||||
for collection in obj.users_collection:
|
||||
if "Ifc" not in collection.name or collection.name == obj.name:
|
||||
continue
|
||||
spatial_obj = bpy.data.objects.get(collection.name)
|
||||
if spatial_obj and spatial_obj.BIMObjectProperties.ifc_definition_id:
|
||||
element = self.file.by_id(spatial_obj.BIMObjectProperties.ifc_definition_id)
|
||||
if self.file.schema != "IFC2X3" and not element.is_a("IfcSpatialElement"):
|
||||
continue
|
||||
elif self.file.schema == "IFC2X3" and not element.is_a("IfcSpatialStructureElement"):
|
||||
continue
|
||||
blenderbim.core.spatial.assign_container(
|
||||
tool.Ifc,
|
||||
tool.Collector,
|
||||
tool.Spatial,
|
||||
structure_obj=spatial_obj,
|
||||
element_obj=obj,
|
||||
)
|
||||
break
|
||||
|
||||
|
||||
class UnassignClass(bpy.types.Operator):
|
||||
bl_idname = "bim.unassign_class"
|
||||
bl_label = "Unassign IFC Class"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
if self.obj:
|
||||
objects = [bpy.data.objects.get(self.obj)]
|
||||
else:
|
||||
objects = context.selected_objects
|
||||
for obj in objects:
|
||||
if not obj.BIMObjectProperties.ifc_definition_id:
|
||||
continue
|
||||
product = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
self.remove_voids(product, obj)
|
||||
IfcStore.unlink_element(product, obj)
|
||||
if product.is_a("IfcGridAxis"):
|
||||
ifcopenshell.api.run("grid.remove_grid_axis", self.file, **{"axis": product})
|
||||
elif product.is_a("IfcGrid"):
|
||||
grid_collection = bpy.data.collections.get(obj.name)
|
||||
for axis_collection in grid_collection.children:
|
||||
for axis_obj in axis_collection.objects:
|
||||
bpy.ops.bim.unassign_class(obj=axis_obj.name)
|
||||
else:
|
||||
ifcopenshell.api.run("root.remove_product", self.file, **{"product": product})
|
||||
if "/" in obj.name and obj.name[0:3] == "Ifc":
|
||||
obj.name = "/".join(obj.name.split("/")[1:])
|
||||
if obj.data and obj.data.name == "Void":
|
||||
bpy.data.objects.remove(obj)
|
||||
return {"FINISHED"}
|
||||
|
||||
def remove_voids(self, product, obj):
|
||||
if product.id() not in VoidData.products:
|
||||
VoidData.load(self.file, product.id())
|
||||
for opening_id in VoidData.products[product.id()]:
|
||||
bpy.ops.bim.remove_opening(opening_id=opening_id, obj=obj.name)
|
||||
context.view_layer.objects.active = active_object
|
||||
|
||||
|
||||
class UnlinkObject(bpy.types.Operator):
|
||||
|
||||
@@ -33,17 +33,11 @@ from bpy.props import (
|
||||
CollectionProperty,
|
||||
)
|
||||
|
||||
products_enum = []
|
||||
classes_enum = []
|
||||
types_enum = []
|
||||
|
||||
|
||||
def purge():
|
||||
global products_enum
|
||||
global classes_enum
|
||||
global types_enum
|
||||
products_enum = []
|
||||
classes_enum = []
|
||||
types_enum = []
|
||||
|
||||
|
||||
@@ -61,10 +55,9 @@ def getIfcPredefinedTypes(self, context):
|
||||
return types_enum
|
||||
|
||||
|
||||
def refreshClasses(self, context):
|
||||
global classes_enum
|
||||
classes_enum.clear()
|
||||
enum = getIfcClasses(self, context)
|
||||
def refresh_classes(self, context):
|
||||
IfcClassData.load()
|
||||
enum = get_ifc_classes(self, context)
|
||||
context.scene.BIMRootProperties.ifc_class = enum[0][0]
|
||||
|
||||
|
||||
@@ -76,38 +69,16 @@ def refreshPredefinedTypes(self, context):
|
||||
context.scene.BIMRootProperties.ifc_predefined_type = enum[0][0]
|
||||
|
||||
|
||||
def getIfcProducts(self, context):
|
||||
global products_enum
|
||||
file = IfcStore.get_file()
|
||||
if len(products_enum) < 1:
|
||||
products_enum.extend(
|
||||
[
|
||||
(e, e, "")
|
||||
for e in [
|
||||
"IfcElement",
|
||||
"IfcElementType",
|
||||
"IfcSpatialElement",
|
||||
"IfcGroup",
|
||||
"IfcStructuralItem",
|
||||
"IfcContext",
|
||||
"IfcAnnotation",
|
||||
"IfcRelSpaceBoundary",
|
||||
]
|
||||
]
|
||||
)
|
||||
if file.schema == "IFC2X3":
|
||||
products_enum[2] = ("IfcSpatialStructureElement", "IfcSpatialStructureElement", "")
|
||||
return products_enum
|
||||
def get_ifc_products(self, context):
|
||||
if not IfcClassData.is_loaded:
|
||||
IfcClassData.load()
|
||||
return IfcClassData.data["ifc_products"]
|
||||
|
||||
|
||||
def getIfcClasses(self, context):
|
||||
global classes_enum
|
||||
file = IfcStore.get_file()
|
||||
if len(classes_enum) < 1 and file:
|
||||
declaration = IfcStore.get_schema().declaration_by_name(context.scene.BIMRootProperties.ifc_product)
|
||||
declarations = ifcopenshell.util.schema.get_subtypes(declaration)
|
||||
classes_enum.extend([(c, c, "") for c in sorted([d.name() for d in declarations])])
|
||||
return classes_enum
|
||||
def get_ifc_classes(self, context):
|
||||
if not IfcClassData.is_loaded:
|
||||
IfcClassData.load()
|
||||
return IfcClassData.data["ifc_classes"]
|
||||
|
||||
|
||||
def get_contexts(self, context):
|
||||
@@ -118,7 +89,7 @@ def get_contexts(self, context):
|
||||
|
||||
class BIMRootProperties(PropertyGroup):
|
||||
contexts: EnumProperty(items=get_contexts, name="Contexts")
|
||||
ifc_product: EnumProperty(items=getIfcProducts, name="Products", update=refreshClasses)
|
||||
ifc_class: EnumProperty(items=getIfcClasses, name="Class", update=refreshPredefinedTypes)
|
||||
ifc_product: EnumProperty(items=get_ifc_products, name="Products", update=refresh_classes)
|
||||
ifc_class: EnumProperty(items=get_ifc_classes, name="Class", update=refreshPredefinedTypes)
|
||||
ifc_predefined_type: EnumProperty(items=getIfcPredefinedTypes, name="Predefined Type", default=None)
|
||||
ifc_userdefined_type: StringProperty(name="Userdefined Type")
|
||||
|
||||
@@ -65,10 +65,6 @@ class BIM_PT_class(Panel):
|
||||
row.operator("bim.unlink_object", icon="UNLINKED", text="")
|
||||
if IfcStore.get_file().by_id(props.ifc_definition_id).is_a("IfcRoot"):
|
||||
row.operator("bim.enable_reassign_class", icon="GREASEPENCIL", text="")
|
||||
if context.selected_objects:
|
||||
row.operator("bim.unassign_class", icon="X", text="")
|
||||
else:
|
||||
row.operator("bim.unassign_class", icon="X", text="").obj = context.active_object.name
|
||||
else:
|
||||
ifc_predefined_types = root_prop.getIfcPredefinedTypes(context.scene.BIMRootProperties, context)
|
||||
self.draw_class_dropdowns(context, ifc_predefined_types)
|
||||
|
||||
@@ -28,7 +28,7 @@ class BIM_PT_search(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_utilities"
|
||||
bl_parent_id = "BIM_PT_collaboration"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
|
||||
@@ -20,86 +20,88 @@ import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.AddWorkPlan,
|
||||
operator.EditWorkPlan,
|
||||
operator.RemoveWorkPlan,
|
||||
operator.EnableEditingWorkPlan,
|
||||
operator.DisableEditingWorkPlan,
|
||||
operator.EnableEditingWorkPlanSchedules,
|
||||
operator.AssignWorkSchedule,
|
||||
operator.UnassignWorkSchedule,
|
||||
operator.AddWorkSchedule,
|
||||
operator.EditWorkSchedule,
|
||||
operator.RemoveWorkSchedule,
|
||||
operator.EnableEditingWorkSchedule,
|
||||
operator.EnableEditingTasks,
|
||||
operator.DisableEditingWorkSchedule,
|
||||
operator.DisableEditingSequence,
|
||||
operator.EditSequenceAttributes,
|
||||
operator.EditSequenceTimeLag,
|
||||
operator.EnableEditingSequenceAttributes,
|
||||
operator.EnableEditingSequenceTimeLag,
|
||||
operator.AddSummaryTask,
|
||||
operator.AddTask,
|
||||
operator.AddTaskColumn,
|
||||
operator.AddTimePeriod,
|
||||
operator.AddWorkCalendar,
|
||||
operator.EditWorkCalendar,
|
||||
operator.EditWorkTime,
|
||||
operator.RemoveWorkCalendar,
|
||||
operator.RemoveWorkTime,
|
||||
operator.UnassignRecurrencePattern,
|
||||
operator.UnassignLagTime,
|
||||
operator.RemoveTimePeriod,
|
||||
operator.EnableEditingWorkCalendar,
|
||||
operator.EnableEditingWorkTime,
|
||||
operator.EnableEditingWorkCalendarTimes,
|
||||
operator.DisableEditingWorkCalendar,
|
||||
operator.DisableEditingWorkTime,
|
||||
operator.AddWorkPlan,
|
||||
operator.AddWorkSchedule,
|
||||
operator.AddWorkTime,
|
||||
operator.AssignLagTime,
|
||||
operator.AssignRecurrencePattern,
|
||||
operator.AddTimePeriod,
|
||||
operator.AddTask,
|
||||
operator.AddSummaryTask,
|
||||
operator.ExpandTask,
|
||||
operator.ContractTask,
|
||||
operator.RemoveTask,
|
||||
operator.EnableEditingTask,
|
||||
operator.DisableEditingTask,
|
||||
operator.DisableEditingTaskTime,
|
||||
operator.EditTask,
|
||||
operator.CopyTaskAttribute,
|
||||
operator.AssignPredecessor,
|
||||
operator.AssignSuccessor,
|
||||
operator.UnassignPredecessor,
|
||||
operator.UnassignSuccessor,
|
||||
operator.EnableEditingTaskTime,
|
||||
operator.EnableEditingTaskCalendar,
|
||||
operator.EnableEditingTaskSequence,
|
||||
operator.EditTaskTime,
|
||||
operator.EditTaskCalendar,
|
||||
operator.RemoveTaskCalendar,
|
||||
operator.AssignProduct,
|
||||
operator.UnassignProduct,
|
||||
operator.AssignProcess,
|
||||
operator.UnassignProcess,
|
||||
operator.GenerateGanttChart,
|
||||
operator.ImportP6,
|
||||
operator.ImportP6XER,
|
||||
operator.ImportPP,
|
||||
operator.ImportMSP,
|
||||
operator.LoadTaskProperties,
|
||||
operator.SelectTaskRelatedProducts,
|
||||
operator.VisualiseWorkScheduleDate,
|
||||
operator.VisualiseWorkScheduleDateRange,
|
||||
operator.RecalculateSchedule,
|
||||
operator.AssignProduct,
|
||||
operator.AssignRecurrencePattern,
|
||||
operator.AssignSuccessor,
|
||||
operator.AssignWorkSchedule,
|
||||
operator.BlenderBIM_DatePicker,
|
||||
operator.BlenderBIM_DatePickerSetDate,
|
||||
operator.BlenderBIM_RedrawDatePicker,
|
||||
operator.AddTaskColumn,
|
||||
operator.RemoveTaskColumn,
|
||||
operator.SetTaskSortColumn,
|
||||
operator.LoadTaskInputs,
|
||||
operator.LoadTaskResources,
|
||||
operator.LoadTaskOutputs,
|
||||
operator.CalculateTaskDuration,
|
||||
operator.ContractTask,
|
||||
operator.CopyTaskAttribute,
|
||||
operator.DisableEditingSequence,
|
||||
operator.DisableEditingTask,
|
||||
operator.DisableEditingTaskTime,
|
||||
operator.DisableEditingWorkCalendar,
|
||||
operator.DisableEditingWorkPlan,
|
||||
operator.DisableEditingWorkSchedule,
|
||||
operator.DisableEditingWorkTime,
|
||||
operator.EditSequenceAttributes,
|
||||
operator.EditSequenceTimeLag,
|
||||
operator.EditTask,
|
||||
operator.EditTaskCalendar,
|
||||
operator.EditTaskTime,
|
||||
operator.EditWorkCalendar,
|
||||
operator.EditWorkPlan,
|
||||
operator.EditWorkSchedule,
|
||||
operator.EditWorkTime,
|
||||
operator.EnableEditingSequenceAttributes,
|
||||
operator.EnableEditingSequenceTimeLag,
|
||||
operator.EnableEditingTask,
|
||||
operator.EnableEditingTaskCalendar,
|
||||
operator.EnableEditingTaskSequence,
|
||||
operator.EnableEditingTaskTime,
|
||||
operator.EnableEditingTasks,
|
||||
operator.EnableEditingWorkCalendar,
|
||||
operator.EnableEditingWorkCalendarTimes,
|
||||
operator.EnableEditingWorkPlan,
|
||||
operator.EnableEditingWorkPlanSchedules,
|
||||
operator.EnableEditingWorkSchedule,
|
||||
operator.EnableEditingWorkTime,
|
||||
operator.ExpandTask,
|
||||
operator.ExportMSP,
|
||||
operator.ExportP6,
|
||||
operator.GenerateGanttChart,
|
||||
operator.ImportMSP,
|
||||
operator.ImportP6,
|
||||
operator.ImportP6XER,
|
||||
operator.ImportPP,
|
||||
operator.LoadTaskInputs,
|
||||
operator.LoadTaskOutputs,
|
||||
operator.LoadTaskProperties,
|
||||
operator.LoadTaskResources,
|
||||
operator.RecalculateSchedule,
|
||||
operator.RemoveTask,
|
||||
operator.RemoveTaskCalendar,
|
||||
operator.RemoveTaskColumn,
|
||||
operator.RemoveTimePeriod,
|
||||
operator.RemoveWorkCalendar,
|
||||
operator.RemoveWorkPlan,
|
||||
operator.RemoveWorkSchedule,
|
||||
operator.RemoveWorkTime,
|
||||
operator.SelectTaskRelatedProducts,
|
||||
operator.SetTaskSortColumn,
|
||||
operator.UnassignLagTime,
|
||||
operator.UnassignPredecessor,
|
||||
operator.UnassignProcess,
|
||||
operator.UnassignProduct,
|
||||
operator.UnassignRecurrencePattern,
|
||||
operator.UnassignSuccessor,
|
||||
operator.UnassignWorkSchedule,
|
||||
operator.VisualiseWorkScheduleDate,
|
||||
operator.VisualiseWorkScheduleDateRange,
|
||||
prop.WorkPlan,
|
||||
prop.BIMWorkPlanProperties,
|
||||
prop.Task,
|
||||
@@ -124,6 +126,11 @@ classes = (
|
||||
)
|
||||
|
||||
|
||||
def menu_func_export(self, context):
|
||||
self.layout.operator(operator.ExportP6.bl_idname, text="P6 (.xml)")
|
||||
self.layout.operator(operator.ExportMSP.bl_idname, text="Microsoft Project (.xml)")
|
||||
|
||||
|
||||
def menu_func_import(self, context):
|
||||
self.layout.operator(operator.ImportP6.bl_idname, text="P6 (.xml)")
|
||||
self.layout.operator(operator.ImportP6XER.bl_idname, text="P6 (.xer)")
|
||||
@@ -138,6 +145,7 @@ def register():
|
||||
bpy.types.Scene.BIMWorkCalendarProperties = bpy.props.PointerProperty(type=prop.BIMWorkCalendarProperties)
|
||||
bpy.types.Scene.DatePickerProperties = bpy.props.PointerProperty(type=prop.DatePickerProperties)
|
||||
bpy.types.TextCurve.BIMDateTextProperties = bpy.props.PointerProperty(type=prop.BIMDateTextProperties)
|
||||
bpy.types.TOPBAR_MT_file_export.append(menu_func_export)
|
||||
bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
|
||||
|
||||
|
||||
@@ -148,4 +156,5 @@ def unregister():
|
||||
del bpy.types.Scene.BIMWorkCalendarProperties
|
||||
del bpy.types.Scene.DatePickerProperties
|
||||
del bpy.types.TextCurve.BIMDateTextProperties
|
||||
bpy.types.TOPBAR_MT_file_export.remove(menu_func_export)
|
||||
bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)
|
||||
|
||||
@@ -39,6 +39,19 @@ from ifcopenshell.api.sequence.data import Data
|
||||
from ifcopenshell.api.resource.data import Data as ResourceData
|
||||
|
||||
|
||||
def animate_text(scene, context):
|
||||
data = bpy.data.curves.get("Timeline")
|
||||
if not data or not bpy.data.objects.get("Timeline"):
|
||||
self.remove_text_animation_handler()
|
||||
scene.frame_current
|
||||
props = data.BIMDateTextProperties
|
||||
start = parser.parse(props.start, dayfirst=True, fuzzy=True)
|
||||
finish = parser.parse(props.finish, dayfirst=True, fuzzy=True)
|
||||
duration = finish - start
|
||||
frame_date = (((scene.frame_current - props.start_frame) / props.total_frames) * duration) + start
|
||||
data.body = frame_date.date().isoformat()
|
||||
|
||||
|
||||
class AddWorkPlan(bpy.types.Operator):
|
||||
bl_idname = "bim.add_work_plan"
|
||||
bl_label = "Add Work Plan"
|
||||
@@ -976,6 +989,7 @@ class GenerateGanttChart(bpy.types.Operator):
|
||||
data = {
|
||||
"pID": task.id(),
|
||||
"pName": task.Name,
|
||||
"pCaption": task.Name,
|
||||
"pStart": task.TaskTime.ScheduleStart if task.TaskTime else "",
|
||||
"pEnd": task.TaskTime.ScheduleFinish if task.TaskTime else "",
|
||||
"pPlanStart": task.TaskTime.ScheduleStart if task.TaskTime else "",
|
||||
@@ -986,6 +1000,7 @@ class GenerateGanttChart(bpy.types.Operator):
|
||||
"pParent": task.Nests[0].RelatingObject.id() if task.Nests else 0,
|
||||
"pOpen": 1,
|
||||
"pCost": 1,
|
||||
"ifcduration": task.TaskTime.ScheduleDuration if task.TaskTime else "",
|
||||
}
|
||||
if task.TaskTime and task.TaskTime.IsCritical:
|
||||
data["pClass"] = "gtaskred"
|
||||
@@ -1196,6 +1211,65 @@ class ImportMSP(bpy.types.Operator, ImportHelper):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ExportMSP(bpy.types.Operator, ImportHelper):
|
||||
bl_idname = "export_msp.bim"
|
||||
bl_label = "Export MSP"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filename_ext = ".xml"
|
||||
filter_glob: bpy.props.StringProperty(default="*.xml", options={"HIDDEN"})
|
||||
holiday_start_date: bpy.props.StringProperty(default="2022-01-01", name="Holiday Start Date")
|
||||
holiday_finish_date: bpy.props.StringProperty(default="2023-01-01", name="Holiday Finish Date")
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
ifc_file = IfcStore.get_file()
|
||||
return ifc_file is not None
|
||||
|
||||
def execute(self, context):
|
||||
from ifc4d.ifc2msp import Ifc2Msp
|
||||
|
||||
self.file = IfcStore.get_file()
|
||||
start = time.time()
|
||||
ifc2msp = Ifc2Msp()
|
||||
ifc2msp.work_schedule = self.file.by_type("IfcWorkSchedule")[0]
|
||||
ifc2msp.xml = bpy.path.ensure_ext(self.filepath, ".xml")
|
||||
ifc2msp.file = self.file
|
||||
ifc2msp.holiday_start_date = parser.parse(self.holiday_start_date).date()
|
||||
ifc2msp.holiday_finish_date = parser.parse(self.holiday_finish_date).date()
|
||||
ifc2msp.execute()
|
||||
print("Export finished in {:.2f} seconds".format(time.time() - start))
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ExportP6(bpy.types.Operator, ImportHelper):
|
||||
bl_idname = "export_p6.bim"
|
||||
bl_label = "Export P6"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filename_ext = ".xml"
|
||||
filter_glob: bpy.props.StringProperty(default="*.xml", options={"HIDDEN"})
|
||||
holiday_start_date: bpy.props.StringProperty(default="2022-01-01", name="Holiday Start Date")
|
||||
holiday_finish_date: bpy.props.StringProperty(default="2023-01-01", name="Holiday Finish Date")
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
ifc_file = IfcStore.get_file()
|
||||
return ifc_file is not None
|
||||
|
||||
def execute(self, context):
|
||||
from ifc4d.ifc2p6 import Ifc2P6
|
||||
|
||||
self.file = IfcStore.get_file()
|
||||
start = time.time()
|
||||
ifc2p6 = Ifc2P6()
|
||||
ifc2p6.xml = bpy.path.ensure_ext(self.filepath, ".xml")
|
||||
ifc2p6.file = self.file
|
||||
ifc2p6.holiday_start_date = parser.parse(self.holiday_start_date).date()
|
||||
ifc2p6.holiday_finish_date = parser.parse(self.holiday_finish_date).date()
|
||||
ifc2p6.execute()
|
||||
print("Export finished in {:.2f} seconds".format(time.time() - start))
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EnableEditingWorkCalendarTimes(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_work_calendar_times"
|
||||
bl_label = "Enable Editing Work Calendar Times"
|
||||
@@ -1607,6 +1681,10 @@ class EnableEditingSequenceTimeLag(bpy.types.Operator):
|
||||
|
||||
def import_attributes(self, name, prop, data):
|
||||
if name == "LagValue":
|
||||
prop = self.props.time_lag_attributes.add()
|
||||
prop.name = name
|
||||
prop.is_null = data[name] is None
|
||||
prop.is_optional = False
|
||||
if isinstance(data[name], isodate.Duration):
|
||||
prop.data_type = "string"
|
||||
prop.string_value = (
|
||||
@@ -1841,22 +1919,10 @@ class VisualiseWorkScheduleDateRange(bpy.types.Operator):
|
||||
obj.data.BIMDateTextProperties.total_frames = int(self.total_frames)
|
||||
obj.data.BIMDateTextProperties.start = self.props.visualisation_start
|
||||
obj.data.BIMDateTextProperties.finish = self.props.visualisation_finish
|
||||
bpy.app.handlers.frame_change_post.append(self.animate_text)
|
||||
bpy.app.handlers.frame_change_post.append(animate_text)
|
||||
|
||||
def remove_text_animation_handler(self):
|
||||
bpy.app.handlers.frame_change_post.remove(self.animate_text)
|
||||
|
||||
def animate_text(self, scene, context):
|
||||
data = bpy.data.curves.get("Timeline")
|
||||
if not data or not bpy.data.objects.get("Timeline"):
|
||||
self.remove_text_animation_handler()
|
||||
scene.frame_current
|
||||
props = data.BIMDateTextProperties
|
||||
start = parser.parse(props.start, dayfirst=True, fuzzy=True)
|
||||
finish = parser.parse(props.finish, dayfirst=True, fuzzy=True)
|
||||
duration = finish - start
|
||||
frame_date = (((scene.frame_current - props.start_frame) / props.total_frames) * duration) + start
|
||||
data.body = frame_date.date().isoformat()
|
||||
bpy.app.handlers.frame_change_post.remove(animate_text)
|
||||
|
||||
def animate_input(self, obj, product_frame):
|
||||
if product_frame["type"] in ["LOGISTIC", "MOVE", "DISPOSAL"]:
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import isodate
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.attribute
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
@@ -184,6 +185,16 @@ def updateTaskDuration(self, context):
|
||||
props = context.scene.BIMWorkScheduleProperties
|
||||
if not props.is_task_update_enabled:
|
||||
return
|
||||
|
||||
if self.duration == "-":
|
||||
return
|
||||
|
||||
try:
|
||||
isodate.parse_duration(self.duration),
|
||||
except:
|
||||
self.duration = "-"
|
||||
return
|
||||
|
||||
self.file = IfcStore.get_file()
|
||||
task = self.file.by_id(self.ifc_definition_id)
|
||||
if task.TaskTime:
|
||||
|
||||
@@ -27,7 +27,7 @@ class BIM_PT_spatial(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
bl_parent_id = "BIM_PT_geometry_object"
|
||||
bl_parent_id = "BIM_PT_object_metadata"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
|
||||
@@ -21,21 +21,30 @@ from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.AddStyle,
|
||||
operator.EnableEditingStyle,
|
||||
operator.DisableEditingStyle,
|
||||
operator.DisableEditingStyles,
|
||||
operator.EditStyle,
|
||||
operator.EnableEditingStyle,
|
||||
operator.LoadStyles,
|
||||
operator.RemoveStyle,
|
||||
operator.UpdateStyleColours,
|
||||
operator.UnlinkStyle,
|
||||
operator.UpdateStyleColours,
|
||||
operator.UpdateStyleTextures,
|
||||
prop.Style,
|
||||
prop.BIMStylesProperties,
|
||||
prop.BIMStyleProperties,
|
||||
ui.BIM_PT_styles,
|
||||
ui.BIM_PT_style,
|
||||
ui.BIM_PT_style_attributes,
|
||||
ui.BIM_UL_styles,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
bpy.types.Scene.BIMStylesProperties = bpy.props.PointerProperty(type=prop.BIMStylesProperties)
|
||||
bpy.types.Material.BIMStyleProperties = bpy.props.PointerProperty(type=prop.BIMStyleProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
del bpy.types.Scene.BIMStylesProperties
|
||||
del bpy.types.Material.BIMStyleProperties
|
||||
|
||||
@@ -18,12 +18,33 @@
|
||||
|
||||
import bpy
|
||||
import blenderbim.tool as tool
|
||||
import ifcopenshell
|
||||
|
||||
|
||||
def refresh():
|
||||
StylesData.is_loaded = False
|
||||
StyleAttributesData.is_loaded = False
|
||||
|
||||
|
||||
class StylesData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {"style_types": cls.style_types(), "total_styles": cls.total_styles()}
|
||||
|
||||
@classmethod
|
||||
def style_types(cls):
|
||||
declaration = tool.Ifc.schema().declaration_by_name("IfcPresentationStyle")
|
||||
declarations = ifcopenshell.util.schema.get_subtypes(declaration)
|
||||
return [(c, c, "") for c in sorted([d.name() for d in declarations])]
|
||||
|
||||
@classmethod
|
||||
def total_styles(cls):
|
||||
return len(tool.Ifc.get().by_type("IfcPresentationStyle"))
|
||||
|
||||
|
||||
class StyleAttributesData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@@ -20,6 +20,7 @@ import bpy
|
||||
import blenderbim.bim.handler
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.style as core
|
||||
import ifcopenshell.util.representation
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
|
||||
@@ -39,6 +40,21 @@ class UpdateStyleColours(bpy.types.Operator, Operator):
|
||||
core.update_style_colours(tool.Ifc, tool.Style, obj=context.active_object.active_material)
|
||||
|
||||
|
||||
class UpdateStyleTextures(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.update_style_textures"
|
||||
bl_label = "Update Style Textures"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
representation = ifcopenshell.util.representation.get_representation(
|
||||
tool.Ifc.get_entity(context.active_object), "Model", "Body", "MODEL_VIEW"
|
||||
)
|
||||
if representation:
|
||||
core.update_style_textures(
|
||||
tool.Ifc, tool.Style, obj=context.active_object.active_material, representation=representation
|
||||
)
|
||||
|
||||
|
||||
class RemoveStyle(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.remove_style"
|
||||
bl_label = "Remove Style"
|
||||
@@ -91,3 +107,22 @@ class EditStyle(bpy.types.Operator, Operator):
|
||||
|
||||
def _execute(self, context):
|
||||
core.edit_style(tool.Ifc, tool.Style, obj=context.active_object.active_material)
|
||||
|
||||
|
||||
class DisableEditingStyles(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.disable_editing_styles"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_label = "Disable Editing Styles"
|
||||
|
||||
def _execute(self, context):
|
||||
core.disable_editing_styles(tool.Style)
|
||||
|
||||
|
||||
class LoadStyles(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.load_styles"
|
||||
bl_label = "Load Styles"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
style_type: bpy.props.StringProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.load_styles(tool.Style, style_type=self.style_type)
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
import bpy
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.prop import StrProperty, Attribute
|
||||
from blenderbim.bim.module.style.data import StylesData
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
@@ -32,6 +33,25 @@ from bpy.props import (
|
||||
)
|
||||
|
||||
|
||||
def get_style_types(self, context):
|
||||
if not StylesData.is_loaded:
|
||||
StylesData.load()
|
||||
return StylesData.data["style_types"]
|
||||
|
||||
|
||||
class Style(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
ifc_definition_id: IntProperty(name="IFC Definition ID")
|
||||
total_elements: IntProperty(name="Total Elements")
|
||||
|
||||
|
||||
class BIMStylesProperties(PropertyGroup):
|
||||
is_editing: BoolProperty(name="Is Editing")
|
||||
style_type: EnumProperty(items=get_style_types, name="Style Type")
|
||||
styles: CollectionProperty(name="Styles", type=Style)
|
||||
active_style_index: IntProperty(name="Active Style Index")
|
||||
|
||||
|
||||
class BIMStyleProperties(PropertyGroup):
|
||||
attributes: CollectionProperty(name="Attributes", type=Attribute)
|
||||
is_editing: BoolProperty(name="Is Editing")
|
||||
|
||||
@@ -17,9 +17,51 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import blenderbim.bim.helper
|
||||
from bpy.types import Panel
|
||||
from bpy.types import Panel, UIList
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.style.data import StyleAttributesData
|
||||
from blenderbim.bim.module.style.data import StylesData, StyleAttributesData
|
||||
|
||||
|
||||
class BIM_PT_styles(Panel):
|
||||
bl_label = "IFC Styles"
|
||||
bl_idname = "BIM_PT_styles"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_geometry"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return IfcStore.get_file()
|
||||
|
||||
def draw(self, context):
|
||||
if not StylesData.is_loaded:
|
||||
StylesData.load()
|
||||
|
||||
self.props = context.scene.BIMStylesProperties
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="{} Styles Found".format(StylesData.data["total_styles"]), icon="MATERIAL")
|
||||
row = self.layout.row(align=True)
|
||||
if self.props.is_editing:
|
||||
row.operator("bim.disable_editing_styles", text="", icon="CANCEL")
|
||||
else:
|
||||
row.prop(self.props, "style_type", text="")
|
||||
row.operator("bim.load_styles", text="", icon="IMPORT").style_type = self.props.style_type
|
||||
return
|
||||
|
||||
#row = self.layout.row(align=True)
|
||||
#row.alignment = "RIGHT"
|
||||
|
||||
#row.operator("bim.add_presentation_style", text="", icon="ADD")
|
||||
#if self.props.styles and self.props.active_style_index < len(self.props.styles):
|
||||
# style = self.props.styles[self.props.active_style_index]
|
||||
# op = row.operator("bim.select_by_style", text="", icon="RESTRICT_SELECT_OFF")
|
||||
# op.style = style.ifc_definition_id
|
||||
# row.operator("bim.remove_style", text="", icon="X").style = style.ifc_definition_id
|
||||
|
||||
self.layout.template_list("BIM_UL_styles", "", self.props, "styles", self.props, "active_style_index")
|
||||
|
||||
|
||||
class BIM_PT_style(Panel):
|
||||
@@ -42,6 +84,7 @@ class BIM_PT_style(Panel):
|
||||
row = self.layout.row(align=True)
|
||||
if props.ifc_style_id:
|
||||
row.operator("bim.update_style_colours", icon="GREASEPENCIL")
|
||||
row.operator("bim.update_style_textures", icon="TEXTURE", text="")
|
||||
row.operator("bim.unlink_style", icon="UNLINKED", text="")
|
||||
row.operator("bim.remove_style", icon="X", text="")
|
||||
else:
|
||||
@@ -88,3 +131,13 @@ class BIM_PT_style_attributes(Panel):
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=attribute["name"])
|
||||
row.label(text=attribute["value"])
|
||||
|
||||
|
||||
class BIM_UL_styles(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if item:
|
||||
row = layout.row(align=True)
|
||||
row.label(text=item.name)
|
||||
#row2 = row.row()
|
||||
#row2.alignment = "RIGHT"
|
||||
#row2.label(text=str(item.total_elements))
|
||||
|
||||
@@ -20,21 +20,28 @@ import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.LoadSystems,
|
||||
operator.DisableSystemEditingUI,
|
||||
operator.AddPort,
|
||||
operator.AddSystem,
|
||||
operator.EditSystem,
|
||||
operator.RemoveSystem,
|
||||
operator.ToggleAssigningSystem,
|
||||
operator.AssignSystem,
|
||||
operator.UnassignSystem,
|
||||
operator.EnableEditingSystem,
|
||||
operator.ConnectPort,
|
||||
operator.DisableEditingSystem,
|
||||
operator.DisableSystemEditingUI,
|
||||
operator.DisconnectPort,
|
||||
operator.EditSystem,
|
||||
operator.EnableEditingSystem,
|
||||
operator.HidePorts,
|
||||
operator.LoadSystems,
|
||||
operator.RemoveSystem,
|
||||
operator.SelectSystemProducts,
|
||||
operator.SetFlowDirection,
|
||||
operator.ShowPorts,
|
||||
operator.UnassignSystem,
|
||||
prop.System,
|
||||
prop.BIMSystemProperties,
|
||||
ui.BIM_PT_systems,
|
||||
ui.BIM_PT_object_systems,
|
||||
ui.BIM_PT_ports,
|
||||
ui.BIM_PT_port,
|
||||
ui.BIM_UL_systems,
|
||||
ui.BIM_UL_object_systems,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.schema
|
||||
import blenderbim.tool as tool
|
||||
|
||||
|
||||
def refresh():
|
||||
SystemData.is_loaded = False
|
||||
ObjectSystemData.is_loaded = False
|
||||
PortData.is_loaded = False
|
||||
|
||||
|
||||
class SystemData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {
|
||||
"system_class": cls.system_class(),
|
||||
"total_systems": cls.total_systems(),
|
||||
}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def system_class(cls):
|
||||
declaration = tool.Ifc.schema().declaration_by_name("IfcSystem")
|
||||
declarations = ifcopenshell.util.schema.get_subtypes(declaration)
|
||||
# We're only interested in systems for services. Not sure why IFC groups these together.
|
||||
return [
|
||||
(c, c, "")
|
||||
for c in sorted([d.name() for d in declarations])
|
||||
if c not in ("IfcZone", "IfcStructuralAnalysisModel")
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def total_systems(cls):
|
||||
return len(tool.Ifc.get().by_type("IfcSystem"))
|
||||
|
||||
|
||||
class ObjectSystemData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {
|
||||
"systems": cls.systems(),
|
||||
"total_systems": cls.total_systems(),
|
||||
}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def systems(cls):
|
||||
results = []
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
if not element:
|
||||
return results
|
||||
for system in ifcopenshell.util.system.get_element_systems(element):
|
||||
results.append({"id": system.id(), "name": system.Name or "Unnamed", "ifc_class": system.is_a()})
|
||||
return results
|
||||
|
||||
@classmethod
|
||||
def total_systems(cls):
|
||||
return len(tool.Ifc.get().by_type("IfcSystem"))
|
||||
|
||||
|
||||
class PortData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {
|
||||
"total_ports": cls.total_ports(),
|
||||
}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def total_ports(cls):
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
return len(ifcopenshell.util.system.get_ports(element))
|
||||
@@ -17,208 +17,181 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import ifcopenshell.util.attribute
|
||||
import ifcopenshell.api
|
||||
import blenderbim.bim.helper
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.system as core
|
||||
import blenderbim.bim.handler
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.api.system.data import Data
|
||||
|
||||
|
||||
class LoadSystems(bpy.types.Operator):
|
||||
class Operator:
|
||||
def execute(self, context):
|
||||
IfcStore.execute_ifc_operator(self, context)
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class LoadSystems(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.load_systems"
|
||||
bl_label = "Load Systems"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMSystemProperties
|
||||
props.systems.clear()
|
||||
for ifc_definition_id, system in Data.systems.items():
|
||||
new = props.systems.add()
|
||||
new.ifc_definition_id = ifc_definition_id
|
||||
new.name = system["Name"]
|
||||
props.is_editing = True
|
||||
bpy.ops.bim.disable_editing_system()
|
||||
return {"FINISHED"}
|
||||
def _execute(self, context):
|
||||
core.load_systems(tool.System)
|
||||
|
||||
|
||||
class DisableSystemEditingUI(bpy.types.Operator):
|
||||
class DisableSystemEditingUI(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.disable_system_editing_ui"
|
||||
bl_label = "Disable System Editing UI"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
context.scene.BIMSystemProperties.is_editing = False
|
||||
context.scene.BIMSystemProperties.active_system_id = 0
|
||||
return {"FINISHED"}
|
||||
def _execute(self, context):
|
||||
core.disable_system_editing_ui(tool.System)
|
||||
|
||||
|
||||
class AddSystem(bpy.types.Operator):
|
||||
class AddSystem(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.add_system"
|
||||
bl_label = "Add System"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
result = ifcopenshell.api.run("system.add_system", IfcStore.get_file())
|
||||
Data.load(IfcStore.get_file())
|
||||
bpy.ops.bim.load_systems()
|
||||
bpy.ops.bim.enable_editing_system(system=result.id())
|
||||
return {"FINISHED"}
|
||||
core.add_system(tool.Ifc, tool.System, ifc_class=context.scene.BIMSystemProperties.system_class)
|
||||
|
||||
|
||||
class EditSystem(bpy.types.Operator):
|
||||
class EditSystem(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.edit_system"
|
||||
bl_label = "Edit System"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMSystemProperties
|
||||
attributes = {}
|
||||
for attribute in props.system_attributes:
|
||||
if attribute.is_null:
|
||||
attributes[attribute.name] = None
|
||||
else:
|
||||
attributes[attribute.name] = attribute.string_value
|
||||
self.file = IfcStore.get_file()
|
||||
ifcopenshell.api.run(
|
||||
"system.edit_system",
|
||||
self.file,
|
||||
**{"system": self.file.by_id(props.active_system_id), "attributes": attributes}
|
||||
core.edit_system(
|
||||
tool.Ifc, tool.System, system=tool.Ifc.get().by_id(context.scene.BIMSystemProperties.active_system_id)
|
||||
)
|
||||
Data.load(IfcStore.get_file())
|
||||
bpy.ops.bim.load_systems()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RemoveSystem(bpy.types.Operator):
|
||||
class RemoveSystem(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.remove_system"
|
||||
bl_label = "Remove System"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
system: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMSystemProperties
|
||||
self.file = IfcStore.get_file()
|
||||
ifcopenshell.api.run("system.remove_system", self.file, **{"system": self.file.by_id(self.system)})
|
||||
Data.load(IfcStore.get_file())
|
||||
bpy.ops.bim.load_systems()
|
||||
return {"FINISHED"}
|
||||
core.remove_system(tool.Ifc, tool.System, system=tool.Ifc.get().by_id(self.system))
|
||||
|
||||
|
||||
class EnableEditingSystem(bpy.types.Operator):
|
||||
class EnableEditingSystem(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.enable_editing_system"
|
||||
bl_label = "Enable Editing System"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
system: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMSystemProperties
|
||||
props.system_attributes.clear()
|
||||
|
||||
blenderbim.bim.helper.import_attributes("IfcSystem", props.system_attributes, Data.systems[self.system])
|
||||
props.active_system_id = self.system
|
||||
return {"FINISHED"}
|
||||
def _execute(self, context):
|
||||
core.enable_editing_system(tool.System, system=tool.Ifc.get().by_id(self.system))
|
||||
|
||||
|
||||
class DisableEditingSystem(bpy.types.Operator):
|
||||
class DisableEditingSystem(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.disable_editing_system"
|
||||
bl_label = "Disable Editing System"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
context.scene.BIMSystemProperties.active_system_id = 0
|
||||
return {"FINISHED"}
|
||||
def _execute(self, context):
|
||||
core.disable_editing_system(tool.System)
|
||||
|
||||
|
||||
class ToggleAssigningSystem(bpy.types.Operator):
|
||||
bl_idname = "bim.toggle_assigning_system"
|
||||
bl_label = "Toggle Assigning System"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
context.scene.BIMSystemProperties.is_adding = not context.scene.BIMSystemProperties.is_adding
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AssignSystem(bpy.types.Operator):
|
||||
class AssignSystem(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.assign_system"
|
||||
bl_label = "Assign System"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
product: bpy.props.StringProperty()
|
||||
system: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
products = [bpy.data.objects.get(self.product)] if self.product else context.selected_objects
|
||||
for product in products:
|
||||
if not product.BIMObjectProperties.ifc_definition_id:
|
||||
continue
|
||||
ifcopenshell.api.run(
|
||||
"system.assign_system",
|
||||
self.file,
|
||||
**{
|
||||
"product": self.file.by_id(product.BIMObjectProperties.ifc_definition_id),
|
||||
"system": self.file.by_id(self.system),
|
||||
}
|
||||
)
|
||||
Data.load(self.file)
|
||||
return {"FINISHED"}
|
||||
for obj in context.selected_objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element:
|
||||
core.assign_system(tool.Ifc, system=tool.Ifc.get().by_id(self.system), product=element)
|
||||
|
||||
|
||||
class UnassignSystem(bpy.types.Operator):
|
||||
class UnassignSystem(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.unassign_system"
|
||||
bl_label = "Unassign System"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
product: bpy.props.StringProperty()
|
||||
system: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
products = [bpy.data.objects.get(self.product)] if self.product else context.selected_objects
|
||||
for product in products:
|
||||
props = product.BIMObjectProperties
|
||||
if not props.ifc_definition_id:
|
||||
continue
|
||||
if not (props.ifc_definition_id in Data.products and self.system in Data.products[props.ifc_definition_id]):
|
||||
continue
|
||||
ifcopenshell.api.run(
|
||||
"system.unassign_system",
|
||||
self.file,
|
||||
**{
|
||||
"product": self.file.by_id(product.BIMObjectProperties.ifc_definition_id),
|
||||
"system": self.file.by_id(self.system),
|
||||
}
|
||||
)
|
||||
Data.load(self.file)
|
||||
return {"FINISHED"}
|
||||
for obj in context.selected_objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element:
|
||||
core.unassign_system(tool.Ifc, system=tool.Ifc.get().by_id(self.system), product=element)
|
||||
|
||||
|
||||
class SelectSystemProducts(bpy.types.Operator):
|
||||
class SelectSystemProducts(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.select_system_products"
|
||||
bl_label = "Select System Products"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
system: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
for obj in context.visible_objects:
|
||||
obj.select_set(False)
|
||||
if not obj.BIMObjectProperties.ifc_definition_id:
|
||||
continue
|
||||
product_systems = Data.products.get(obj.BIMObjectProperties.ifc_definition_id, [])
|
||||
if self.system in product_systems:
|
||||
obj.select_set(True)
|
||||
return {"FINISHED"}
|
||||
def _execute(self, context):
|
||||
core.select_system_products(tool.System, system=tool.Ifc.get().by_id(self.system))
|
||||
|
||||
|
||||
class ShowPorts(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.show_ports"
|
||||
bl_label = "Show Ports"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.show_ports(tool.System, element=tool.Ifc.get_entity(context.active_object))
|
||||
|
||||
|
||||
class HidePorts(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.hide_ports"
|
||||
bl_label = "Hide Ports"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.hide_ports(tool.System, element=tool.Ifc.get_entity(context.active_object))
|
||||
|
||||
|
||||
class AddPort(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.add_port"
|
||||
bl_label = "Add Ports"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.add_port(tool.Ifc, tool.System, element=tool.Ifc.get_entity(context.active_object))
|
||||
|
||||
|
||||
class ConnectPort(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.connect_port"
|
||||
bl_label = "Connect Ports"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return len(context.selected_objects) == 2
|
||||
|
||||
def _execute(self, context):
|
||||
obj1 = context.active_object
|
||||
obj2 = context.selected_objects[0] if context.selected_objects[1] == obj1 else context.selected_objects[1]
|
||||
core.connect_port(tool.Ifc, port1=tool.Ifc.get_entity(obj1), port2=tool.Ifc.get_entity(obj2))
|
||||
|
||||
|
||||
class DisconnectPort(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.disconnect_port"
|
||||
bl_label = "Disconnect Ports"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.disconnect_port(tool.Ifc, port=tool.Ifc.get_entity(context.active_object))
|
||||
|
||||
|
||||
class SetFlowDirection(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.set_flow_direction"
|
||||
bl_label = "Set Flow Direction"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
direction: bpy.props.StringProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.set_flow_direction(
|
||||
tool.Ifc, tool.System, port=tool.Ifc.get_entity(context.active_object), direction=self.direction
|
||||
)
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
from blenderbim.bim.module.system.data import SystemData
|
||||
from blenderbim.bim.prop import StrProperty, Attribute
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
@@ -31,8 +32,15 @@ from bpy.props import (
|
||||
)
|
||||
|
||||
|
||||
def get_system_class(self, context):
|
||||
if not SystemData.is_loaded:
|
||||
SystemData.load()
|
||||
return SystemData.data["system_class"]
|
||||
|
||||
|
||||
class System(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
ifc_class: StringProperty(name="IFC Class")
|
||||
ifc_definition_id: IntProperty(name="IFC Definition ID")
|
||||
|
||||
|
||||
@@ -43,3 +51,4 @@ class BIMSystemProperties(PropertyGroup):
|
||||
systems: CollectionProperty(name="Systems", type=System)
|
||||
active_system_index: IntProperty(name="Active System Index")
|
||||
active_system_id: IntProperty(name="Active System Id")
|
||||
system_class: EnumProperty(items=get_system_class, name="Class")
|
||||
|
||||
@@ -16,9 +16,10 @@
|
||||
# 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 blenderbim.tool as tool
|
||||
from bpy.types import Panel, UIList
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.api.system.data import Data
|
||||
from blenderbim.bim.module.system.data import SystemData, ObjectSystemData, PortData
|
||||
|
||||
|
||||
class BIM_PT_systems(Panel):
|
||||
@@ -35,15 +36,18 @@ class BIM_PT_systems(Panel):
|
||||
return IfcStore.get_file()
|
||||
|
||||
def draw(self, context):
|
||||
if not Data.is_loaded:
|
||||
Data.load(IfcStore.get_file())
|
||||
if not SystemData.is_loaded:
|
||||
SystemData.load()
|
||||
self.props = context.scene.BIMSystemProperties
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="{} Systems Found".format(len(Data.systems)), icon="OUTLINER")
|
||||
row.label(text="{} Systems Found".format(SystemData.data["total_systems"]), icon="OUTLINER")
|
||||
if self.props.is_editing:
|
||||
row.operator("bim.add_system", text="", icon="ADD")
|
||||
row.operator("bim.disable_system_editing_ui", text="", icon="CANCEL")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.props, "system_class", text="")
|
||||
row.operator("bim.add_system", text="", icon="ADD")
|
||||
else:
|
||||
row.operator("bim.load_systems", text="", icon="GREASEPENCIL")
|
||||
|
||||
@@ -84,13 +88,13 @@ class BIM_PT_object_systems(Panel):
|
||||
return IfcStore.get_file() and context.active_object.BIMObjectProperties.ifc_definition_id
|
||||
|
||||
def draw(self, context):
|
||||
if not Data.is_loaded:
|
||||
Data.load(IfcStore.get_file())
|
||||
if not ObjectSystemData.is_loaded:
|
||||
ObjectSystemData.load()
|
||||
self.props = context.scene.BIMSystemProperties
|
||||
row = self.layout.row(align=True)
|
||||
if self.props.is_adding:
|
||||
row.label(text="Adding Systems", icon="OUTLINER")
|
||||
row.operator("bim.toggle_assigning_system", text="", icon="CANCEL")
|
||||
if self.props.is_editing:
|
||||
row = self.layout.row()
|
||||
row.alignment = "RIGHT"
|
||||
row.operator("bim.disable_system_editing_ui", text="", icon="CANCEL")
|
||||
self.layout.template_list(
|
||||
"BIM_UL_object_systems",
|
||||
"",
|
||||
@@ -100,27 +104,98 @@ class BIM_PT_object_systems(Panel):
|
||||
"active_system_index",
|
||||
)
|
||||
else:
|
||||
row.label(text=f"{len(Data.systems)} Systems in IFC Project", icon="OUTLINER")
|
||||
row.operator("bim.toggle_assigning_system", text="", icon="ADD")
|
||||
|
||||
systems_object = Data.products.get(context.active_object.BIMObjectProperties.ifc_definition_id, [])
|
||||
for system_id in systems_object:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=Data.systems[system_id].get("Name", "Unnamed"))
|
||||
op = row.operator("bim.select_system_products", text="", icon="RESTRICT_SELECT_OFF")
|
||||
op.system = system_id
|
||||
op = row.operator("bim.unassign_system", text="", icon="X")
|
||||
op.system = system_id
|
||||
row.label(text=f"{ObjectSystemData.data['total_systems']} Systems in IFC Project", icon="OUTLINER")
|
||||
row.operator("bim.load_systems", text="", icon="GREASEPENCIL")
|
||||
|
||||
if not systems_object:
|
||||
system_icons = {
|
||||
"IfcSystem": "EXTERNAL_DRIVE",
|
||||
"IfcDistributionSystem": "NETWORK_DRIVE",
|
||||
"IfcDistributionCircuit": "DRIVER",
|
||||
"IfcBuildingSystem": "MOD_BUILD",
|
||||
}
|
||||
for system in ObjectSystemData.data["systems"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=system["name"], icon=system_icons[system["ifc_class"]])
|
||||
op = row.operator("bim.select_system_products", text="", icon="RESTRICT_SELECT_OFF")
|
||||
op.system = system["id"]
|
||||
op = row.operator("bim.unassign_system", text="", icon="X")
|
||||
op.system = system["id"]
|
||||
|
||||
if not ObjectSystemData.data["systems"]:
|
||||
self.layout.label(text="No System associated with Active Object")
|
||||
|
||||
|
||||
class BIM_PT_ports(Panel):
|
||||
bl_label = "IFC Ports"
|
||||
bl_idname = "BIM_PT_ports"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
bl_parent_id = "BIM_PT_services_object"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if not context.active_object:
|
||||
return False
|
||||
element = tool.Ifc.get_entity(context.active_object)
|
||||
if not element or not element.is_a("IfcDistributionElement"):
|
||||
return False
|
||||
return True
|
||||
|
||||
def draw(self, context):
|
||||
if not PortData.is_loaded:
|
||||
PortData.load()
|
||||
self.props = context.scene.BIMSystemProperties
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=f"{PortData.data['total_ports']} Ports Found", icon="PLUGIN")
|
||||
row.operator("bim.show_ports", icon="HIDE_OFF", text="")
|
||||
row.operator("bim.hide_ports", icon="HIDE_ON", text="")
|
||||
row.operator("bim.add_port", icon="ADD", text="")
|
||||
|
||||
|
||||
class BIM_PT_port(Panel):
|
||||
bl_label = "IFC Port"
|
||||
bl_idname = "BIM_PT_port"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
bl_parent_id = "BIM_PT_services_object"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if not context.active_object:
|
||||
return False
|
||||
element = tool.Ifc.get_entity(context.active_object)
|
||||
if not element or not element.is_a("IfcPort"):
|
||||
return False
|
||||
return True
|
||||
|
||||
def draw(self, context):
|
||||
self.props = context.scene.BIMSystemProperties
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.connect_port", icon="PLUGIN", text="")
|
||||
row.operator("bim.disconnect_port", icon="UNLINKED", text="")
|
||||
row.operator("bim.set_flow_direction", icon="FORWARD", text="").direction = "SOURCE"
|
||||
row.operator("bim.set_flow_direction", icon="BACK", text="").direction = "SINK"
|
||||
row.operator("bim.set_flow_direction", icon="ARROW_LEFTRIGHT", text="").direction = "SOURCEANDSINK"
|
||||
row.operator("bim.set_flow_direction", icon="RESTRICT_INSTANCED_ON", text="").direction = "NOTDEFINED"
|
||||
|
||||
|
||||
class BIM_UL_systems(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
system_icons = {
|
||||
"IfcSystem": "EXTERNAL_DRIVE",
|
||||
"IfcDistributionSystem": "NETWORK_DRIVE",
|
||||
"IfcDistributionCircuit": "DRIVER",
|
||||
"IfcBuildingSystem": "MOD_BUILD",
|
||||
}
|
||||
if item:
|
||||
row = layout.row(align=True)
|
||||
row.label(text=item.name)
|
||||
row.label(text=item.name, icon=system_icons[item.ifc_class])
|
||||
system_id = item.ifc_definition_id
|
||||
if context.scene.BIMSystemProperties.active_system_id == system_id:
|
||||
op = row.operator("bim.select_system_products", text="", icon="RESTRICT_SELECT_OFF")
|
||||
@@ -143,8 +218,13 @@ class BIM_UL_systems(UIList):
|
||||
|
||||
class BIM_UL_object_systems(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
system_icons = {
|
||||
"IfcSystem": "EXTERNAL_DRIVE",
|
||||
"IfcDistributionSystem": "NETWORK_DRIVE",
|
||||
"IfcDistributionCircuit": "DRIVER",
|
||||
"IfcBuildingSystem": "MOD_BUILD",
|
||||
}
|
||||
if item:
|
||||
row = layout.row(align=True)
|
||||
row.label(text=item.name)
|
||||
op = row.operator("bim.assign_system", text="", icon="ADD")
|
||||
op.system = item.ifc_definition_id
|
||||
row.label(text=item.name, icon=system_icons[item.ifc_class])
|
||||
row.operator("bim.assign_system", text="", icon="ADD").system = item.ifc_definition_id
|
||||
|
||||
@@ -22,6 +22,7 @@ import json
|
||||
import webbrowser
|
||||
import ifcopenshell
|
||||
import blenderbim.bim.handler
|
||||
import blenderbim.tool as tool
|
||||
from . import schema
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.ui import IFCFileSelector
|
||||
@@ -39,6 +40,43 @@ class OpenUri(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SelectURIAttribute(bpy.types.Operator):
|
||||
bl_idname = "bim.select_uri_attribute"
|
||||
bl_label = "Select URI Attribute"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Select a local file"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
data_path: bpy.props.StringProperty(name="Data Path")
|
||||
use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=False)
|
||||
|
||||
def execute(self, context):
|
||||
# data_path contains the latter half of the path to the string_value property
|
||||
# I have no idea how to find out the former half, so let's just use brute force.
|
||||
data_path = self.data_path.replace(".string_value", "")
|
||||
attribute = None
|
||||
try:
|
||||
attribute = eval(f"bpy.context.scene.{data_path}")
|
||||
except:
|
||||
try:
|
||||
attribute = eval(f"bpy.context.active_object.{data_path}")
|
||||
except:
|
||||
try:
|
||||
attribute = eval(f"bpy.context.active_object.active_material.{data_path}")
|
||||
except:
|
||||
# Do you know a better way?
|
||||
pass
|
||||
if attribute:
|
||||
filepath = self.filepath
|
||||
if self.use_relative_path:
|
||||
filepath = os.path.relpath(filepath, os.path.dirname(tool.Ifc.get_path()))
|
||||
attribute.string_value = filepath
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
class SelectIfcFile(bpy.types.Operator, IFCFileSelector):
|
||||
bl_idname = "bim.select_ifc_file"
|
||||
bl_label = "Select IFC File"
|
||||
@@ -106,7 +144,9 @@ class OpenUpstream(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddSectionPlane(bpy.types.Operator):
|
||||
class BIM_OT_add_section_plane(bpy.types.Operator):
|
||||
"""Add a temporary empty object as a section cutaway. Cull all geometry rendering below the empty's local Z axis"""
|
||||
|
||||
bl_idname = "bim.add_section_plane"
|
||||
bl_label = "Add Temporary Section Cutaway"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
@@ -151,79 +191,100 @@ class AddSectionPlane(bpy.types.Operator):
|
||||
def create_section_compare_node(self):
|
||||
group = bpy.data.node_groups.new("Section Compare", type="ShaderNodeTree")
|
||||
group_input = group.nodes.new(type="NodeGroupInput")
|
||||
group_input.location = 0, 50
|
||||
|
||||
separate_xyz = group.nodes.new(type="ShaderNodeSeparateXYZ")
|
||||
separate_xyz.location = 200, 0
|
||||
|
||||
greater = group.nodes.new(type="ShaderNodeMath")
|
||||
greater.operation = "GREATER_THAN"
|
||||
greater.inputs[1].default_value = 0
|
||||
greater.location = 400, 0
|
||||
|
||||
multiply = group.nodes.new(type="ShaderNodeMath")
|
||||
multiply.operation = "MULTIPLY"
|
||||
multiply.inputs[0].default_value = 1
|
||||
multiply.location = 600, 150
|
||||
|
||||
group_output = group.nodes.new(type="NodeGroupOutput")
|
||||
separate_xyz_a = group.nodes.new(type="ShaderNodeSeparateXYZ")
|
||||
separate_xyz_b = group.nodes.new(type="ShaderNodeSeparateXYZ")
|
||||
gt_a = group.nodes.new(type="ShaderNodeMath")
|
||||
gt_a.operation = "GREATER_THAN"
|
||||
gt_a.inputs[1].default_value = 0
|
||||
gt_b = group.nodes.new(type="ShaderNodeMath")
|
||||
gt_b.operation = "GREATER_THAN"
|
||||
gt_b.inputs[1].default_value = 0
|
||||
add = group.nodes.new(type="ShaderNodeMath")
|
||||
compare = group.nodes.new(type="ShaderNodeMath")
|
||||
compare.operation = "COMPARE"
|
||||
compare.inputs[1].default_value = 2
|
||||
group.links.new(group_input.outputs[""], separate_xyz_a.inputs[0])
|
||||
group.links.new(group_input.outputs[""], separate_xyz_b.inputs[0])
|
||||
group.links.new(separate_xyz_a.outputs[2], gt_a.inputs[0])
|
||||
group.links.new(separate_xyz_b.outputs[2], gt_b.inputs[0])
|
||||
group.links.new(gt_a.outputs[0], add.inputs[0])
|
||||
group.links.new(gt_b.outputs[0], add.inputs[1])
|
||||
group.links.new(add.outputs[0], compare.inputs[0])
|
||||
group.links.new(compare.outputs[0], group_output.inputs[""])
|
||||
group_output.location = 800, 0
|
||||
|
||||
group.links.new(group_input.outputs[""], multiply.inputs[0])
|
||||
group.links.new(group_input.outputs[""], separate_xyz.inputs[0])
|
||||
group.links.new(separate_xyz.outputs[2], greater.inputs[0])
|
||||
group.links.new(greater.outputs[0], multiply.inputs[1])
|
||||
group.links.new(multiply.outputs[0], group_output.inputs[""])
|
||||
|
||||
def create_section_override_node(self, obj, context):
|
||||
group = bpy.data.node_groups.new("Section Override", type="ShaderNodeTree")
|
||||
links = group.links
|
||||
nodes = group.nodes
|
||||
|
||||
group_input = group.nodes.new(type="NodeGroupInput")
|
||||
group_output = group.nodes.new(type="NodeGroupOutput")
|
||||
group_input = nodes.new(type="NodeGroupInput")
|
||||
group_output = nodes.new(type="NodeGroupOutput")
|
||||
group_output.location = 600, 250
|
||||
|
||||
backfacing = group.nodes.new(type="ShaderNodeNewGeometry")
|
||||
backfacing_mix = group.nodes.new(type="ShaderNodeMixShader")
|
||||
emission = group.nodes.new(type="ShaderNodeEmission")
|
||||
backfacing_mix = nodes.new(type="ShaderNodeMixShader")
|
||||
backfacing_mix.location = group_output.location - Vector((400, 350))
|
||||
|
||||
backfacing = nodes.new(type="ShaderNodeNewGeometry")
|
||||
backfacing.location = backfacing_mix.location + Vector((-200, 200))
|
||||
group_input.location = backfacing_mix.location - Vector((200, 50))
|
||||
|
||||
emission = nodes.new(type="ShaderNodeEmission")
|
||||
emission.inputs[0].default_value = list(context.scene.BIMProperties.section_plane_colour) + [1]
|
||||
emission.location = backfacing_mix.location - Vector((200, 150))
|
||||
|
||||
group.links.new(backfacing.outputs["Backfacing"], backfacing_mix.inputs[0])
|
||||
group.links.new(group_input.outputs[""], backfacing_mix.inputs[1])
|
||||
group.links.new(emission.outputs["Emission"], backfacing_mix.inputs[2])
|
||||
transparent = nodes.new(type="ShaderNodeBsdfTransparent")
|
||||
transparent.location = group_output.location - Vector((400, 100))
|
||||
|
||||
transparent = group.nodes.new(type="ShaderNodeBsdfTransparent")
|
||||
section_mix = group.nodes.new(type="ShaderNodeMixShader")
|
||||
section_mix.name = "Section Mix"
|
||||
section_mix.inputs[0].default_value = 1 # Directly pass input shader when there is no cutaway
|
||||
section_mix.location = group_output.location - Vector((200, 0))
|
||||
|
||||
group.links.new(transparent.outputs["BSDF"], section_mix.inputs[1])
|
||||
group.links.new(backfacing_mix.outputs["Shader"], section_mix.inputs[2])
|
||||
|
||||
group.links.new(section_mix.outputs["Shader"], group_output.inputs[""])
|
||||
|
||||
cut_obj = group.nodes.new(type="ShaderNodeTexCoord")
|
||||
cut_obj = nodes.new(type="ShaderNodeTexCoord")
|
||||
cut_obj.object = obj
|
||||
section_compare = group.nodes.new(type="ShaderNodeGroup")
|
||||
cut_obj.location = group_output.location - Vector((800, 150))
|
||||
|
||||
section_compare = nodes.new(type="ShaderNodeGroup")
|
||||
section_compare.node_tree = bpy.data.node_groups.get("Section Compare")
|
||||
section_compare.name = "Last Section Compare"
|
||||
value = group.nodes.new(type="ShaderNodeValue")
|
||||
value.name = "Mock Section"
|
||||
group.links.new(cut_obj.outputs["Object"], section_compare.inputs[0])
|
||||
group.links.new(value.outputs[0], section_compare.inputs[1])
|
||||
group.links.new(section_compare.outputs[0], section_mix.inputs[0])
|
||||
section_compare.location = group_output.location - Vector((600, 0))
|
||||
|
||||
links.new(cut_obj.outputs["Object"], section_compare.inputs[1])
|
||||
links.new(backfacing.outputs["Backfacing"], backfacing_mix.inputs[0])
|
||||
links.new(group_input.outputs[""], backfacing_mix.inputs[1])
|
||||
links.new(emission.outputs["Emission"], backfacing_mix.inputs[2])
|
||||
links.new(section_compare.outputs[0], section_mix.inputs[0])
|
||||
links.new(transparent.outputs["BSDF"], section_mix.inputs[1])
|
||||
links.new(backfacing_mix.outputs["Shader"], section_mix.inputs[2])
|
||||
links.new(section_mix.outputs["Shader"], group_output.inputs[""])
|
||||
|
||||
def append_obj_to_section_override_node(self, obj):
|
||||
group = bpy.data.node_groups.get("Section Override")
|
||||
cut_obj = group.nodes.new(type="ShaderNodeTexCoord")
|
||||
cut_obj.object = obj
|
||||
try:
|
||||
last_section_node = next(
|
||||
n
|
||||
for n in group.nodes
|
||||
if isinstance(n, bpy.types.ShaderNodeGroup)
|
||||
and n.node_tree.name == "Section Compare"
|
||||
and not n.inputs[0].links
|
||||
)
|
||||
offset = Vector((0, 0))
|
||||
except StopIteration:
|
||||
last_section_node = group.nodes.get("Section Mix")
|
||||
offset = Vector((200, 0))
|
||||
section_compare = group.nodes.new(type="ShaderNodeGroup")
|
||||
section_compare.node_tree = bpy.data.node_groups.get("Section Compare")
|
||||
section_compare.location = last_section_node.location - Vector((200, 0)) - offset
|
||||
|
||||
last_compare = group.nodes.get("Last Section Compare")
|
||||
last_compare.name = "Section Compare"
|
||||
mock_section = group.nodes.get("Mock Section")
|
||||
section_mix = group.nodes.get("Section Mix")
|
||||
cut_obj = group.nodes.new(type="ShaderNodeTexCoord")
|
||||
cut_obj.object = obj
|
||||
cut_obj.location = last_section_node.location - Vector((400, 150)) - offset
|
||||
|
||||
group.links.new(last_compare.outputs[0], section_compare.inputs[0])
|
||||
group.links.new(mock_section.outputs[0], section_compare.inputs[1])
|
||||
group.links.new(cut_obj.outputs["Object"], last_compare.inputs[1])
|
||||
group.links.new(section_compare.outputs[0], section_mix.inputs[0])
|
||||
group.links.new(section_compare.outputs[0], last_section_node.inputs[0])
|
||||
group.links.new(cut_obj.outputs["Object"], section_compare.inputs[1])
|
||||
|
||||
section_compare.name = "Last Section Compare"
|
||||
|
||||
@@ -274,7 +335,9 @@ class AddSectionPlane(bpy.types.Operator):
|
||||
return node
|
||||
|
||||
|
||||
class RemoveSectionPlane(bpy.types.Operator):
|
||||
class BIM_OT_remove_section_plane(bpy.types.Operator):
|
||||
"""Remove selected section plane. No effect if executed on a regular object"""
|
||||
|
||||
bl_idname = "bim.remove_section_plane"
|
||||
bl_label = "Remove Temporary Section Cutaway"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
@@ -286,34 +349,35 @@ class RemoveSectionPlane(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
name = context.active_object.name
|
||||
section_override = bpy.data.node_groups.get("Section Override")
|
||||
for node in section_override.nodes:
|
||||
if node.type != "TEX_COORD" or node.object.name != name:
|
||||
continue
|
||||
section_compare = node.outputs["Object"].links[0].to_node
|
||||
# If the tex coord links to section_compare.inputs[1], it is called 'Input_3'
|
||||
if node.outputs["Object"].links[0].to_socket.identifier == "Input_3":
|
||||
section_override.links.new(
|
||||
section_compare.inputs[0].links[0].from_socket, section_compare.outputs[0].links[0].to_socket
|
||||
)
|
||||
else: # If it links to section_compare.inputs[0]
|
||||
if section_compare.inputs[1].links[0].from_node.name == "Mock Section":
|
||||
# Then it is the very last section. Purge everything.
|
||||
self.purge_all_section_data(context)
|
||||
return {"FINISHED"}
|
||||
section_override.links.new(
|
||||
section_compare.inputs[1].links[0].from_socket, section_compare.outputs[0].links[0].to_socket
|
||||
)
|
||||
tex_coords = next(
|
||||
(
|
||||
n
|
||||
for n in section_override.nodes
|
||||
if isinstance(n, bpy.types.ShaderNodeTexCoord) and n.object.name == name
|
||||
),
|
||||
None,
|
||||
)
|
||||
if tex_coords is not None:
|
||||
section_compare = tex_coords.outputs["Object"].links[0].to_node
|
||||
if section_compare.inputs[0].links:
|
||||
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])
|
||||
self.offset_previous_nodes(section_compare, offset_x=200)
|
||||
section_override.nodes.remove(section_compare)
|
||||
section_override.nodes.remove(node)
|
||||
section_override.nodes.remove(tex_coords)
|
||||
bpy.data.objects.remove(context.active_object)
|
||||
|
||||
old_last_compare = section_override.nodes.get("Last Section Compare")
|
||||
old_last_compare.name = "Section Compare"
|
||||
section_mix = section_override.nodes.get("Section Mix")
|
||||
new_last_compare = section_mix.inputs[0].links[0].from_node
|
||||
new_last_compare.name = "Last Section Compare"
|
||||
bpy.ops.object.delete({"selected_objects": [context.active_object]})
|
||||
return {"FINISHED"}
|
||||
|
||||
def offset_previous_nodes(self, section_compare, offset_x=0, offset_y=0):
|
||||
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))
|
||||
self.offset_previous_nodes(previous_section_compare, offset_x, offset_y)
|
||||
|
||||
def purge_all_section_data(self, context):
|
||||
bpy.data.materials.remove(bpy.data.materials.get("Section Override"))
|
||||
for material in bpy.data.materials:
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user