mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-08 00:41:36 +00:00
Compare commits
70 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 219b7d0c24 | |||
| 97dd37b345 | |||
| e4877e8246 | |||
| e216d345c1 | |||
| 14c1b70768 | |||
| e15f222a19 | |||
| 4ab2664529 | |||
| bd0cfa5366 | |||
| 2e4b0e83a2 | |||
| cf61111386 | |||
| 3aa5ddb070 | |||
| 25dacc8fed | |||
| 7cba63af9c | |||
| 3c1d6abc7e | |||
| 8da4b9f23f | |||
| 99fa77edee | |||
| 839c5ad932 | |||
| b68ce08811 | |||
| e662308665 | |||
| a2ccff67ca | |||
| 8e045d772c | |||
| 16fd614886 | |||
| f31df307c6 | |||
| 11873a7c51 | |||
| ccccbe23f4 | |||
| 2173a562be | |||
| a11f81f2ce | |||
| d3b553c4fb | |||
| 5eb11c3516 | |||
| c195799801 | |||
| ac5a44a962 | |||
| 86880e9e62 | |||
| f8ccdf1170 | |||
| e3d8473d47 | |||
| bd5d4cecbf | |||
| 2ede8a2206 | |||
| cb77f9bd26 | |||
| 0bb21fedae | |||
| 478f90c2d4 | |||
| a016f6e5c8 | |||
| 1b2fa4f994 | |||
| 2d8383efc6 | |||
| add5132dec | |||
| fe333051df | |||
| 8d1b533f91 | |||
| fb304e141a | |||
| bd094f930e | |||
| b61000d2fe | |||
| c72cadb234 | |||
| 10642177a9 | |||
| 0b8ea28463 | |||
| 9b24f54f0d | |||
| 289645dacc | |||
| c365acb517 | |||
| 785cf56621 | |||
| 60617eecce | |||
| 26c68c00b7 | |||
| ab0a602f9f | |||
| 066326ab3b | |||
| 02713dbd39 | |||
| d58dae5d2f | |||
| ca3685900f | |||
| 5e10e61aca | |||
| 92e25ce3cb | |||
| 804a670454 | |||
| efa8382fc5 | |||
| 92f9d5f506 | |||
| 8800c14105 | |||
| ec2d0bfa4a | |||
| d684d47dc8 |
@@ -56,7 +56,7 @@ jobs:
|
||||
[ -n "$install_root" ] && find "$install_root" -type f -name 'cache-*.tar.gz' -maxdepth 1 -exec tar -xzf {} -C "$install_root" \; || true
|
||||
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
with:
|
||||
key: mac-${{ matrix.arch }}
|
||||
|
||||
|
||||
@@ -47,11 +47,11 @@ jobs:
|
||||
install_root=$(find ./build -maxdepth 4 -type d -name install 2>/dev/null | head -n 1 || true)
|
||||
[ -n "$install_root" ] && find "$install_root" -type f -name 'cache-*.tar.gz' -maxdepth 1 -exec tar -xzf {} -C "$install_root" \; || true
|
||||
|
||||
# Not supported on docker
|
||||
# - name: ccache
|
||||
# uses: hendrikmuhs/ccache-action@v1
|
||||
# with:
|
||||
# key: ubuntu-22.04-${{ runner.arch }}-rockylinux8
|
||||
- name: ccache
|
||||
# Using fork to support Rocky.
|
||||
uses: Andrej730/ccache-action@main
|
||||
with:
|
||||
key: ubuntu-22.04-${{ runner.arch }}-rockylinux8
|
||||
|
||||
- name: Run Build Script
|
||||
shell: bash
|
||||
|
||||
@@ -47,11 +47,11 @@ jobs:
|
||||
install_root=$(find ./build -maxdepth 4 -type d -name install 2>/dev/null | head -n 1 || true)
|
||||
[ -n "$install_root" ] && find "$install_root" -type f -name 'cache-*.tar.gz' -maxdepth 1 -exec tar -xzf {} -C "$install_root" \; || true
|
||||
|
||||
# Not supported on docker
|
||||
# - name: ccache
|
||||
# uses: hendrikmuhs/ccache-action@v1
|
||||
# with:
|
||||
# key: ubuntu-22.04-${{ runner.arch }}-rockylinux8
|
||||
- name: ccache
|
||||
# Using fork to support Rocky.
|
||||
uses: Andrej730/ccache-action@main
|
||||
with:
|
||||
key: ubuntu-22.04-${{ runner.arch }}-rockylinux8
|
||||
|
||||
- name: Run Build Script
|
||||
shell: bash
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
name: ci-ifctester-org
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- src/ifctester/**
|
||||
|
||||
jobs:
|
||||
publish_website:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Checkout ifctester_org_static_html
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: IfcOpenShell/ifctester_org_static_html
|
||||
token: ${{ secrets.IFCOPENBOT_TOKEN }}
|
||||
path: ifctester_org_static_html
|
||||
- name: Build webapp
|
||||
working-directory: ./src/ifctester
|
||||
run: |
|
||||
sudo apt update && sudo apt install -y nodejs
|
||||
git config --global user.name 'IfcOpenBot'
|
||||
git config --global user.email 'IfcOpenBot@users.noreply.github.com'
|
||||
make webapp-build
|
||||
- name: Commit and push
|
||||
run: |
|
||||
cp -r src/ifctester/webapp/dist/* ifctester_org_static_html/
|
||||
git -C ifctester_org_static_html add .
|
||||
git -C ifctester_org_static_html commit --allow-empty -m "$(git log --oneline -1)"
|
||||
git -C ifctester_org_static_html push
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
python -m pip install --upgrade pip
|
||||
pip install xmlschema xsdata numpy lxml pytest isodate lark networkx tabulate python-dateutil shapely
|
||||
pip install src/bcf --no-deps
|
||||
pip install https://github.com/Andrej730/aud/archive/refs/heads/master-reduced-size.zip
|
||||
pip install git+https://github.com/zdhoward/aud
|
||||
|
||||
- name: Install C++ dependencies
|
||||
run: |
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
libhdf5-dev libcgal-dev libeigen3-dev
|
||||
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
with:
|
||||
key: ubuntu-22.04-${{ runner.arch }}
|
||||
|
||||
@@ -79,6 +79,7 @@ jobs:
|
||||
git clone https://github.com/facebook/rocksdb --branch v9.11.2
|
||||
cd rocksdb
|
||||
mkdir build && cd build
|
||||
# rocksdb is using ccache automatically.
|
||||
cmake -DFAIL_ON_WARNINGS=Off \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX=/usr/local \
|
||||
@@ -99,9 +100,9 @@ jobs:
|
||||
echo ${{ env.pythonLocation }}
|
||||
|
||||
mkdir build && cd build
|
||||
# Ubuntu 22.04's libocct-foundation-dev package doesn't have Config.cmake, so we provide OCC paths directly.
|
||||
# In later versions of Ubuntu, this can be simplified and the OCC paths can be removed.
|
||||
cmake \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_CXX_STANDARD=17 \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_PREFIX_PATH=/usr \
|
||||
@@ -114,14 +115,6 @@ jobs:
|
||||
-DCOLLADA_SUPPORT=Off \
|
||||
"-DSCHEMA_VERSIONS=2x3;4;4x3_add2" \
|
||||
-DGLTF_SUPPORT=On \
|
||||
-DJSON_INCLUDE_DIR=/usr/include \
|
||||
-DCGAL_INCLUDE_DIR=/usr/include \
|
||||
-DGMP_INCLUDE_DIR=/usr/include \
|
||||
-DMPFR_INCLUDE_DIR=/usr/include \
|
||||
-DGMP_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
|
||||
-DMPFR_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
|
||||
-DHDF5_INCLUDE_DIR=/usr/include/hdf5/serial \
|
||||
-DEIGEN_DIR=/usr/include/eigen3 \
|
||||
-DWITH_ROCKSDB=On \
|
||||
../cmake
|
||||
sudo make -j $(nproc)
|
||||
|
||||
+12
-9
@@ -151,11 +151,6 @@ if(MSVC AND MSVC_PARALLEL_BUILD)
|
||||
add_definitions("/MP")
|
||||
endif()
|
||||
|
||||
if (MSVC AND BUILD_SHARED_LIBS)
|
||||
# @todo how do projects normally deal with this regarding classes derived from std::exception?
|
||||
add_compile_options(/wd4275)
|
||||
endif()
|
||||
|
||||
if(NO_WARN)
|
||||
if(MSVC)
|
||||
add_compile_options("/w")
|
||||
@@ -259,6 +254,13 @@ endmacro()
|
||||
|
||||
if(WITH_CGAL)
|
||||
if(NOT CGAL_INCLUDE_DIR)
|
||||
# CGAL is not respecting default Boost_USE_STATIC_LIBS value
|
||||
# and sometiems it's getting in the way.
|
||||
if(NOT DEFINED Boost_USE_STATIC_LIBS)
|
||||
set(CGAL_Boost_USE_STATIC_LIBS OFF)
|
||||
else()
|
||||
set(CGAL_Boost_USE_STATIC_LIBS "${Boost_USE_STATIC_LIBS}")
|
||||
endif()
|
||||
find_package(CGAL REQUIRED)
|
||||
if(NOT CGAL_DIR)
|
||||
message(
|
||||
@@ -791,7 +793,7 @@ if(HDF5_SUPPORT)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(NOT HDF5_INCLUDE_DIR)
|
||||
if(NOT HDF5_INCLUDE_DIR OR NOT HDF5_LIBRARY_DIR)
|
||||
# First try to find it as a config.
|
||||
find_package(HDF5 CONFIG)
|
||||
if(HDF5_DIR)
|
||||
@@ -801,10 +803,11 @@ if(HDF5_SUPPORT)
|
||||
# If it failed, still try to find as a module.
|
||||
# E.g. on Ubuntu `libhdf5-dev` doesn't provie hdf5-config.cmake.
|
||||
# Will automatically fill HDF5_LIBRARIES and HDF5_INCLUDE_DIR.
|
||||
find_package(HDF5)
|
||||
find_package(HDF5 COMPONENTS CXX)
|
||||
if(NOT HDF5_INCLUDE_DIR)
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"HDF5_INCLUDE_DIR is not provided (current value: '${HDF5_INCLUDE_DIR}'). "
|
||||
"HDF5_LIBRARY_DIR is not provided (current value: '${HDF5_LIBRARY_DIR}'). "
|
||||
"Also could not find HDF5 package (neither module or config)."
|
||||
)
|
||||
@@ -1043,7 +1046,7 @@ if(BUILD_IFCGEOM)
|
||||
if(WASM_BUILD)
|
||||
set(IFCOPENSHELL_LIBRARIES ${IFCOPENSHELL_LIBRARIES} IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES})
|
||||
else()
|
||||
set(IFCOPENSHELL_LIBRARIES ${IFCOPENSHELL_LIBRARIES} IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES} IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES})
|
||||
set(IFCOPENSHELL_LIBRARIES ${IFCOPENSHELL_LIBRARIES} IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES} IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES} IfcGeom)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@@ -1173,7 +1176,7 @@ if(BUILD_IFCGEOM)
|
||||
|
||||
add_library(geometry_mapping_ifc${schema} STATIC ${IFCGEOM_FILES})
|
||||
set_target_properties(geometry_mapping_ifc${schema} PROPERTIES COMPILE_FLAGS "-DIFC_GEOM_EXPORTS -DIfcSchema=Ifc${schema}")
|
||||
target_link_libraries(geometry_mapping_ifc${schema} IfcParse IfcGeom)
|
||||
target_link_libraries(geometry_mapping_ifc${schema} IfcParse)
|
||||
list(APPEND mapping_libraries geometry_mapping_ifc${schema})
|
||||
endforeach()
|
||||
|
||||
|
||||
@@ -344,6 +344,11 @@ PIC = "-fPIC" if BUILD_STATIC else ""
|
||||
if any(f.startswith("py-") for f in flags):
|
||||
PYTHON_VERSIONS = [pyv for pyv in PYTHON_VERSIONS if f"py-{''.join(pyv.split('.')[:2])}" in flags]
|
||||
|
||||
if any(f.startswith("occt-") for f in flags):
|
||||
OCCT_VERSION = next(f.split("-", 1)[1] for f in flags if f.startswith("occt-"))
|
||||
|
||||
print(OCCT_VERSION)
|
||||
|
||||
if explicit_targets:
|
||||
targets = {dep for target in explicit_targets for dep in gather_dependencies(target)}
|
||||
else:
|
||||
@@ -883,6 +888,9 @@ if USE_OCCT and "occ" in targets:
|
||||
if OCCT_VERSION == "7.8.1":
|
||||
patches.append("./patches/occt/no_ExpToCasExe_7_8_1.patch")
|
||||
|
||||
if OCCT_VERSION == "7.9.1":
|
||||
patches.append("./patches/occt/no_ExpToCasExe_7_9_1.patch")
|
||||
|
||||
if "wasm" in flags:
|
||||
patches.append("./patches/occt/no_em_js.patch")
|
||||
|
||||
@@ -1360,6 +1368,9 @@ if "IfcOpenShell-Python" in targets:
|
||||
if "wasm" in flags:
|
||||
ADDITIONAL_ARGS = f"-Wl,-undefined,suppress -sSIDE_MODULE=2 -sEXPORTED_FUNCTIONS=_PyInit__ifcopenshell_wrapper"
|
||||
|
||||
# NOTE: We don't use `CXXFLAGS` for wrappers, so wrapper is compiled with different flags
|
||||
# (e.g. ` -fdata-sections` is missing, which is set by default for executables)
|
||||
# So cache doesn't match and running build-all.py builds most of ifcopenshell libraries twice.
|
||||
os.environ["CPPFLAGS"] = f"{CXXFLAGS_MINIMAL} {ADDITIONAL_ARGS}"
|
||||
os.environ["CXXFLAGS"] = f"{CXXFLAGS_MINIMAL} {ADDITIONAL_ARGS}"
|
||||
os.environ["CFLAGS"] = f"{CFLAGS_MINIMAL} {ADDITIONAL_ARGS}"
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 34300d41ad..09b2e0d45f 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -721,6 +721,8 @@ else()
|
||||
OCCT_CHECK_AND_UNSET ("3RDPARTY_DOT_EXECUTABLE")
|
||||
endif()
|
||||
|
||||
+list (REMOVE_ITEM BUILD_TOOLKITS ExpToCasExe)
|
||||
+
|
||||
# bison
|
||||
if (BUILD_YACCLEX)
|
||||
list (APPEND OCCT_3RDPARTY_CMAKE_LIST "adm/cmake/bison")
|
||||
@@ -192,6 +192,8 @@ class TopicHandler:
|
||||
bcf_zip: The BCF zip file to save to.
|
||||
"""
|
||||
topic_dir = self.guid
|
||||
# simulating directory creation (ZipFile in python < 3.11 doesn't have mkdir)
|
||||
destination_zip.writestr(f"{topic_dir}/", "")
|
||||
self._save_xml(destination_zip, self._markup, "markup.bcf")
|
||||
self._save_viewpoints(destination_zip, topic_dir)
|
||||
self._save_bim_snippet(destination_zip)
|
||||
|
||||
@@ -179,6 +179,8 @@ class TopicHandler:
|
||||
bcf_zip: The BCF zip file to save to.
|
||||
"""
|
||||
topic_dir = self.guid
|
||||
# simulating directory creation (ZipFile in python < 3.11 doesn't have mkdir)
|
||||
destination_zip.writestr(f"{topic_dir}/", "")
|
||||
self._save_xml(destination_zip, self._markup, "markup.bcf")
|
||||
self._save_viewpoints(destination_zip, topic_dir)
|
||||
self._save_bim_snippet(destination_zip)
|
||||
|
||||
+1
-1
@@ -127,7 +127,7 @@ endif
|
||||
cd build && . env/$(VENV_ACTIVATE) && $(PIP) download GitPython --dest=./wheels
|
||||
# Provides audio playback for costing
|
||||
# This is a REALLY IMPORTANT feature
|
||||
cd build && . env/$(VENV_ACTIVATE) && $(PIP) wheel git+https://github.com/Andrej730/aud.git --wheel-dir=./wheels
|
||||
cd build && . env/$(VENV_ACTIVATE) && $(PIP) wheel git+https://github.com/zdhoward/aud --wheel-dir=./wheels
|
||||
# IfcOpenShell dependency - support for new typing features
|
||||
cd build && . env/$(VENV_ACTIVATE) && $(PIP) download typing_extensions --dest=./wheels
|
||||
# Required by IfcCSV
|
||||
|
||||
@@ -5,10 +5,10 @@ FILE_NAME('Psets_BBIM_Annotation.ifc','2020-01-01T00:00:00',$,$,'Psets_BBIM_Anno
|
||||
FILE_SCHEMA(('IFC4'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#1=IFCPROPERTYSETTEMPLATE('3VuPUwdCD2Qx3XDDRs0R1N',$,'EPset_Annotation','',.PSET_TYPEDRIVENOVERRIDE.,'IfcAnnotation,IfcTypeProduct',(#2,#3,#4,#29));
|
||||
#1=IFCPROPERTYSETTEMPLATE('3VuPUwdCD2Qx3XDDRs0R1N',$,'EPset_Annotation','',.PSET_TYPEDRIVENOVERRIDE.,'IfcAnnotation,IfcTypeProduct',(#4,#33,#29,#32,#3,#2));
|
||||
#2=IFCSIMPLEPROPERTYTEMPLATE('2P7JN79n96Q9pElZ83LKe4',$,'ZIndex','',.P_SINGLEVALUE.,'IfcInteger',$,$,$,$,$,.READWRITE.);
|
||||
#3=IFCSIMPLEPROPERTYTEMPLATE('1Wpx_r2xj1_9w5JpI0QRJy',$,'Symbol','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#4=IFCSIMPLEPROPERTYTEMPLATE('3q0oxMUKP47vZ4jnyG$dDb',$,'Classes','Classes separarated by spaces that end up in classes for this element in svg. Can be used to specify the text font size: small - 1.8mm; regular - 2.5mm; large - 3.5mm; header - 5mm; title - 7mm. By default regular size is used.',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#4=IFCSIMPLEPROPERTYTEMPLATE('3q0oxMUKP47vZ4jnyG$dDb',$,'Classes','Classes separated by spaces that end up in classes for this element in svg. Can be used to specify the text font size: small - 1.8mm; regular - 2.5mm; large - 3.5mm; header - 5mm; title - 7mm. By default regular size is used.',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#5=IFCPROPERTYSETTEMPLATE('0iKwujnQL9IevVQato8f7Z',$,'BBIM_Batting','',.PSET_TYPEDRIVENOVERRIDE.,'IfcAnnotation/BATTING,IfcTypeProduct',(#6,#7));
|
||||
#6=IFCSIMPLEPROPERTYTEMPLATE('0t2LEesGT1QRQtrIZUAR8L',$,'Thickness','Batting thickness',.P_SINGLEVALUE.,'IfcPositiveLengthMeasure',$,$,$,$,$,.READWRITE.);
|
||||
#7=IFCSIMPLEPROPERTYTEMPLATE('082PndS6v2kBOiJoSboMnh',$,'Reverse pattern direction','Reverse batting pattern (swap starting and ending points)',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
@@ -36,5 +36,7 @@ DATA;
|
||||
#29=IFCSIMPLEPROPERTYTEMPLATE('2pJmUDpB50VBdCOib1zcJJ',$,'Newline_At','',.P_SINGLEVALUE.,'IfcInteger',$,$,$,$,$,.READWRITE.);
|
||||
#30=IFCSIMPLEPROPERTYTEMPLATE('2TJn72t_v2cvBUG916Dpev',$,'CustomUnit','Dimension''s custom unit',.P_ENUMERATEDVALUE.,'IfcText',$,#31,$,$,$,.READWRITE.);
|
||||
#31=IFCPROPERTYENUMERATION('CustomUnit',(IFCTEXT('Feet and Inches - Fractional'),IFCTEXT('Feet - Decimal'),IFCTEXT('Inches - Fractional'),IFCTEXT('Inches - Decimal'),IFCTEXT('Meters'),IFCTEXT('Decimeters'),IFCTEXT('Centimeters'),IFCTEXT('Millimeters')),$);
|
||||
#32=IFCSIMPLEPROPERTYTEMPLATE('0gjJzDYBX8P85qn1xcAOOo',$,'Reverse_List','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#33=IFCSIMPLEPROPERTYTEMPLATE('22TrcxF8jFNB4buSmzjGEF',$,'List_Separator','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
ENDSEC;
|
||||
END-ISO-10303-21;
|
||||
|
||||
@@ -15,6 +15,8 @@ import pystache
|
||||
import json
|
||||
import base64
|
||||
import xml.etree.ElementTree as ET
|
||||
import platformdirs
|
||||
from pathlib import Path
|
||||
|
||||
sio_port = 8080 # default port
|
||||
|
||||
@@ -233,10 +235,17 @@ async def demo(request):
|
||||
return web.Response(text=html_content, content_type="text/html")
|
||||
|
||||
|
||||
async def on_startup(app):
|
||||
pid_file = "running_pid.json"
|
||||
def get_pid_file_path():
|
||||
"""Get the path to the PID file in the user's cache directory."""
|
||||
cache_dir = Path(platformdirs.user_cache_dir("bonsai"))
|
||||
cache_dir.mkdir(parents=True, exist_ok=True)
|
||||
return cache_dir / "running_pid.json"
|
||||
|
||||
if os.path.exists(pid_file):
|
||||
|
||||
async def on_startup(app):
|
||||
pid_file = get_pid_file_path()
|
||||
|
||||
if pid_file.exists():
|
||||
with open(pid_file, "r") as f:
|
||||
pids = json.load(f)
|
||||
else:
|
||||
|
||||
@@ -625,18 +625,13 @@ class IfcImporter:
|
||||
print("Done creating geometry")
|
||||
|
||||
def create_spatial_elements(self) -> None:
|
||||
if tool.Blender.get_addon_preferences().spatial_elements_unselectable:
|
||||
self.create_generic_elements(self.spatial_elements, unselectable=True)
|
||||
else:
|
||||
self.create_generic_elements(self.spatial_elements, unselectable=False)
|
||||
self.create_generic_elements(self.spatial_elements)
|
||||
|
||||
def create_elements(self) -> None:
|
||||
self.create_generic_elements(self.elements)
|
||||
self.create_generic_elements(self.gross_elements, is_gross=True)
|
||||
|
||||
def create_generic_elements(
|
||||
self, elements: set[ifcopenshell.entity_instance], unselectable=False, is_gross=False
|
||||
) -> None:
|
||||
def create_generic_elements(self, elements: set[ifcopenshell.entity_instance], is_gross=False) -> None:
|
||||
if isinstance(self.file, ifcopenshell.sqlite):
|
||||
return self.create_generic_sqlite_elements(elements)
|
||||
|
||||
@@ -660,10 +655,6 @@ class IfcImporter:
|
||||
print("{} / {} elements processed ...".format(i, total))
|
||||
objects.add(self.create_product(element))
|
||||
|
||||
if unselectable:
|
||||
for obj in objects:
|
||||
obj.hide_select = True
|
||||
|
||||
def create_generic_sqlite_elements(self, elements: set[ifcopenshell.entity_instance]) -> None:
|
||||
assert isinstance(self.file, ifcopenshell.sql.sqlite)
|
||||
self.geometry_cache = self.file.get_geometry([e.id() for e in elements])
|
||||
@@ -1018,6 +1009,7 @@ class IfcImporter:
|
||||
)
|
||||
obj = self.create_product(self.project["ifc"])
|
||||
obj.hide_select = True
|
||||
obj.hide_viewport = True
|
||||
self.project["blender"].objects.link(obj)
|
||||
self.project["blender"].BIMCollectionProperties.obj = obj
|
||||
props = tool.Blender.get_object_bim_props(obj)
|
||||
|
||||
@@ -347,12 +347,10 @@ class DecoratorData:
|
||||
(font_size_type for font_size_type in FONT_SIZES if font_size_type in classes_split), "regular"
|
||||
)
|
||||
font_size = FONT_SIZES[font_size_type]
|
||||
|
||||
# get symbol
|
||||
symbol = tool.Drawing.get_annotation_symbol(element)
|
||||
|
||||
# get newline_at
|
||||
newline_at = pset_data.get("Newline_At", 0)
|
||||
reverse_list = pset_data.get("Reverse_List", False)
|
||||
list_separator = pset_data.get("List_Separator") or ", "
|
||||
|
||||
# other attributes
|
||||
literals = tool.Drawing.get_text_literal(obj, return_list=True)
|
||||
@@ -364,11 +362,20 @@ class DecoratorData:
|
||||
literal_data = {
|
||||
"Literal": literal_value,
|
||||
"BoxAlignment": literal.BoxAlignment,
|
||||
"CurrentValue": tool.Drawing.replace_text_literal_variables(literal_value, product),
|
||||
"CurrentValue": tool.Drawing.replace_text_literal_variables(
|
||||
literal_value, product, reverse_list, list_separator
|
||||
),
|
||||
}
|
||||
literals_data.append(literal_data)
|
||||
|
||||
return {"Literals": literals_data, "FontSize": font_size, "Symbol": symbol, "Newline_At": newline_at}
|
||||
return {
|
||||
"Literals": literals_data,
|
||||
"FontSize": font_size,
|
||||
"Symbol": symbol,
|
||||
"Newline_At": newline_at,
|
||||
"Reverse_List": reverse_list,
|
||||
"List_Separator": list_separator,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def get_dimension_data(cls, obj: bpy.types.Object) -> dict[str, Any]:
|
||||
|
||||
@@ -106,14 +106,17 @@ class AddAnnotationType(bpy.types.Operator, tool.Ifc.Operator):
|
||||
obj = bpy.data.objects.new(object_type, None)
|
||||
|
||||
obj.name = props.type_name
|
||||
ifc_context = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Annotation", "MODEL_VIEW")
|
||||
element = tool.Drawing.run_root_assign_class(
|
||||
obj=obj,
|
||||
ifc_class="IfcTypeProduct",
|
||||
predefined_type=object_type,
|
||||
should_add_representation=has_representation,
|
||||
context=ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Annotation", "MODEL_VIEW"),
|
||||
context=ifc_context,
|
||||
ifc_representation_class=tool.Drawing.get_ifc_representation_class(object_type),
|
||||
)
|
||||
if representation := tool.Drawing.get_representation(element, ifc_context):
|
||||
tool.Drawing.reload_representation(obj=obj, representation=representation)
|
||||
element.ApplicableOccurrence = f"IfcAnnotation/{object_type}"
|
||||
|
||||
if props.create_representation_for_type and object_type == "IMAGE":
|
||||
@@ -435,10 +438,6 @@ class CreateDrawing(bpy.types.Operator):
|
||||
if os.path.isfile(svg_path) and self.props.should_use_underlay_cache:
|
||||
return svg_path
|
||||
|
||||
visible_object_names = {obj.name for obj in bpy.context.visible_objects}
|
||||
for obj in bpy.context.view_layer.objects:
|
||||
obj.hide_render = obj.name not in visible_object_names
|
||||
|
||||
assert context.scene and context.view_layer and context.screen
|
||||
context.scene.render.filepath = str(Path(svg_path).with_suffix(".png"))
|
||||
assert (drawing_style := self.cprops.get_active_drawing_style())
|
||||
@@ -449,7 +448,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
previous_visibility: dict[str, bool] = {}
|
||||
collection = tool.Blender.get_object_bim_props(self.camera).collection
|
||||
assert collection
|
||||
# Hie annotations.
|
||||
# Hide annotations.
|
||||
for obj in collection.objects:
|
||||
if context.view_layer.objects.get(obj.name):
|
||||
previous_visibility[obj.name] = obj.hide_get()
|
||||
@@ -840,6 +839,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
|
||||
if tool.Drawing.is_camera_orthographic():
|
||||
self.generate_bisect_linework(context, root)
|
||||
self.generate_wall_layers(context, root)
|
||||
self.merge_linework_and_add_metadata(root)
|
||||
self.move_elements_to_top(root)
|
||||
|
||||
@@ -2171,27 +2171,10 @@ class ActivateModel(bpy.types.Operator):
|
||||
if model_props.show_cut_decorator:
|
||||
CutDecorator.uninstall()
|
||||
|
||||
# Preserve current visibility statuses for:
|
||||
# - non-ifc objects
|
||||
# - type product
|
||||
# - annotations (so we won't unhide other drawings)
|
||||
ifc_file = tool.Ifc.get()
|
||||
visibility_status: dict[bpy.types.Object, bool] = {}
|
||||
for obj in bpy.data.objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
hide = obj.hide_get()
|
||||
elif element.is_a("IfcAnnotation"):
|
||||
hide = True
|
||||
elif element.is_a("IfcTypeProduct"):
|
||||
hide = obj.hide_get()
|
||||
else:
|
||||
continue
|
||||
visibility_status[obj] = hide
|
||||
|
||||
if not bpy.app.background:
|
||||
with context.temp_override(**tool.Blender.get_viewport_context()):
|
||||
bpy.ops.object.hide_view_clear(select=False)
|
||||
bpy.ops.bim.activate_status_filters(only_if_enabled=True)
|
||||
|
||||
elements = {e for obj in context.visible_objects if (e := tool.Ifc.get_entity(obj))}
|
||||
@@ -2236,15 +2219,9 @@ class ActivateModel(bpy.types.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=model,
|
||||
should_reload=False,
|
||||
is_global=True,
|
||||
should_sync_changes_first=True,
|
||||
)
|
||||
|
||||
# restore visibility after hide_view_clear()
|
||||
for obj, hide_status in visibility_status.items():
|
||||
obj.hide_set(hide_status)
|
||||
|
||||
tool.Blender.reset_object_visibility()
|
||||
tool.Blender.update_viewport()
|
||||
bonsai.bim.handler.refresh_ui_data()
|
||||
|
||||
|
||||
@@ -744,6 +744,11 @@ class BIMTextProperties(PropertyGroup):
|
||||
name="Font Size",
|
||||
)
|
||||
newline_at: IntProperty(name="Newline At")
|
||||
reverse_list: BoolProperty(name="Reverse List", description="Reverses the order of any list.", default=False)
|
||||
list_separator: StringProperty( # pyright: ignore[reportRedeclaration]
|
||||
name="List Separator",
|
||||
description="Text used to separate lists. Uses a comma (, ) if empty.",
|
||||
)
|
||||
symbol: EnumProperty( # pyright: ignore[reportRedeclaration]
|
||||
name="Symbol",
|
||||
description="Symbol from symbols.svg to use for this text.",
|
||||
@@ -760,6 +765,8 @@ class BIMTextProperties(PropertyGroup):
|
||||
literals: bpy.types.bpy_prop_collection_idprop[LiteralProps]
|
||||
font_size: str
|
||||
newline_at: int
|
||||
reverse_list: bool
|
||||
list_separator: str
|
||||
symbol: Union[str, Literal["NO SYMBOL", "CUSTOM SYMBOL"]]
|
||||
custom_symbol: str
|
||||
|
||||
@@ -796,6 +803,8 @@ class BIMTextProperties(PropertyGroup):
|
||||
"FontSize": float(self.font_size),
|
||||
"Newline_At": int(self.newline_at),
|
||||
"Symbol": self.get_symbol(),
|
||||
"Reverse_List": self.reverse_list,
|
||||
"List_Separator": self.list_separator or ", ",
|
||||
}
|
||||
return text_data
|
||||
|
||||
|
||||
@@ -839,6 +839,12 @@ class SvgWriter:
|
||||
|
||||
symbol = tool.Drawing.get_annotation_symbol(element)
|
||||
newline_at = tool.Drawing.get_newline_at(element)
|
||||
|
||||
# Get reverse_list and list_separator from EPset_Annotation
|
||||
pset_data = ifcopenshell.util.element.get_pset(element, "EPset_Annotation") or {}
|
||||
reverse_list = pset_data.get("Reverse_List", False)
|
||||
list_separator = pset_data.get("List_Separator") or ", "
|
||||
|
||||
template_text_fields = []
|
||||
if symbol:
|
||||
symbol_transform = self.get_symbol_transform(text_position_svg_str, angle, text_obj)
|
||||
@@ -855,7 +861,7 @@ class SvgWriter:
|
||||
# NOTE: zip makes sure that we iterate over the shortest list
|
||||
for field, text_literal in zip(template_text_fields, text_literals):
|
||||
field.text = tool.Drawing.replace_text_literal_variables(
|
||||
text_literal.Literal, product or element
|
||||
text_literal.Literal, product or element, reverse_list, list_separator
|
||||
)
|
||||
field.attrib["class"] = classes_str
|
||||
|
||||
@@ -875,7 +881,9 @@ class SvgWriter:
|
||||
line_number = 0
|
||||
|
||||
for text_literal in text_literals:
|
||||
text = tool.Drawing.replace_text_literal_variables(text_literal.Literal, product or element)
|
||||
text = tool.Drawing.replace_text_literal_variables(
|
||||
text_literal.Literal, product or element, reverse_list, list_separator
|
||||
)
|
||||
text_tags = self.create_text_tag(
|
||||
text,
|
||||
text_position_svg,
|
||||
|
||||
@@ -590,6 +590,10 @@ class BIM_PT_text(Panel):
|
||||
row.prop(props, "font_size")
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "newline_at")
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "reverse_list")
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "list_separator")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "symbol")
|
||||
@@ -648,6 +652,12 @@ class BIM_PT_text(Panel):
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Newline_At")
|
||||
row.label(text=str(text_data["Newline_At"]))
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Reverse_List")
|
||||
row.label(text=str(text_data["Reverse_List"]))
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="List_Separator")
|
||||
row.label(text=str(text_data["List_Separator"]))
|
||||
|
||||
for literal_data in text_data["Literals"]:
|
||||
box = self.layout.box()
|
||||
|
||||
@@ -180,9 +180,6 @@ def create_annotation_occurrence(context):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
if obj.data and not relating_type_rep:
|
||||
|
||||
@@ -427,9 +427,7 @@ class SwitchRepresentation(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
ifc_definition_id: bpy.props.IntProperty()
|
||||
should_reload: bpy.props.BoolProperty()
|
||||
disable_opening_subtractions: bpy.props.BoolProperty()
|
||||
should_switch_all_meshes: bpy.props.BoolProperty()
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -465,9 +463,6 @@ class SwitchRepresentation(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=self.should_reload,
|
||||
is_global=self.should_switch_all_meshes,
|
||||
should_sync_changes_first=True,
|
||||
)
|
||||
|
||||
|
||||
@@ -721,9 +716,6 @@ class UpdateParametricRepresentation(bpy.types.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=tool.Ifc.get().by_id(props.ifc_definition_id),
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
if show_representation_parameters:
|
||||
core.get_representation_ifc_parameters(tool.Geometry, obj=obj)
|
||||
@@ -1904,9 +1896,6 @@ class OverrideJoin(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=self.target,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
apply_openings=True,
|
||||
)
|
||||
|
||||
|
||||
@@ -157,8 +157,6 @@ class BIM_PT_representations(Panel):
|
||||
icon="FILE_REFRESH" if representation["is_active"] else "OUTLINER_DATA_MESH",
|
||||
text="",
|
||||
)
|
||||
op.should_switch_all_meshes = True
|
||||
op.should_reload = True
|
||||
op.ifc_definition_id = representation["id"]
|
||||
op.disable_opening_subtractions = False
|
||||
row.operator("bim.remove_representation", icon="X", text="").representation_id = representation["id"]
|
||||
|
||||
@@ -323,20 +323,23 @@ class ObjectMaterialData:
|
||||
|
||||
@classmethod
|
||||
def total_thickness(cls):
|
||||
if cls.material:
|
||||
layers = []
|
||||
if cls.material.is_a("IfcMaterialLayerSetUsage"):
|
||||
layers = cls.material.ForLayerSet.MaterialLayers
|
||||
elif cls.material.is_a("IfcMaterialLayerSet"):
|
||||
layers = cls.material.MaterialLayers
|
||||
thickness = sum([l.LayerThickness for l in layers or []])
|
||||
prefs = tool.Blender.get_addon_preferences()
|
||||
assert bpy.context.scene
|
||||
unit_system = bpy.context.scene.unit_settings.system
|
||||
precision = None
|
||||
if unit_system == "IMPERIAL":
|
||||
precision = prefs.doc.imperial_precision
|
||||
return format_distance(thickness, precision=precision, suppress_zero_inches=True, in_unit_length=True)
|
||||
if not cls.material:
|
||||
return
|
||||
layers = []
|
||||
if cls.material.is_a("IfcMaterialLayerSetUsage"):
|
||||
layers = cls.material.ForLayerSet.MaterialLayers
|
||||
elif cls.material.is_a("IfcMaterialLayerSet"):
|
||||
layers = cls.material.MaterialLayers
|
||||
if not layers:
|
||||
return
|
||||
thickness = sum([l.LayerThickness for l in layers or []])
|
||||
prefs = tool.Blender.get_addon_preferences()
|
||||
assert bpy.context.scene
|
||||
unit_system = bpy.context.scene.unit_settings.system
|
||||
precision = None
|
||||
if unit_system == "IMPERIAL":
|
||||
precision = prefs.doc.imperial_precision
|
||||
return format_distance(thickness, precision=precision, suppress_zero_inches=True, in_unit_length=True)
|
||||
|
||||
@classmethod
|
||||
def set_item_name(cls) -> Union[str, None]:
|
||||
@@ -404,7 +407,7 @@ class ObjectMaterialData:
|
||||
material = cls.material
|
||||
if not cls.material or not material.is_a("IfcMaterialConstituentSet"):
|
||||
return []
|
||||
return [m.Name for m in material.MaterialConstituents if m.Name]
|
||||
return [m.Name for m in material.MaterialConstituents or [] if m.Name]
|
||||
|
||||
@classmethod
|
||||
def is_type_material_overridden(cls) -> bool:
|
||||
|
||||
@@ -254,6 +254,10 @@ class BIM_PT_object_material(Panel):
|
||||
active_object = bpy.context.active_object
|
||||
self.layerset_bounds(box, active_object, location="Top_Exterior")
|
||||
|
||||
if not ObjectMaterialData.data["set_items"]:
|
||||
row = box.row()
|
||||
row.label(text="No Materials Found")
|
||||
|
||||
for set_item in ObjectMaterialData.data["set_items"]:
|
||||
if (
|
||||
len(self.props.material_set_item_profile_attributes)
|
||||
@@ -357,6 +361,10 @@ class BIM_PT_object_material(Panel):
|
||||
active_object = bpy.context.active_object
|
||||
self.layerset_bounds(box, active_object, location="Top_Interior")
|
||||
|
||||
if not ObjectMaterialData.data["set_items"]:
|
||||
row = box.row()
|
||||
row.label(text="No Materials Found")
|
||||
|
||||
for set_item in ObjectMaterialData.data["set_items"]:
|
||||
material_name = set_item["material"]
|
||||
material_id = set_item["material_id"]
|
||||
|
||||
@@ -193,9 +193,6 @@ class SplitAlongEdge(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
apply_openings=False,
|
||||
)
|
||||
|
||||
@@ -224,9 +221,6 @@ class SplitAlongEdge(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
apply_openings=True,
|
||||
)
|
||||
|
||||
|
||||
@@ -95,15 +95,17 @@ def update_door_modifier_representation(obj: bpy.types.Object) -> None:
|
||||
representation_data["part_of_product"] = None
|
||||
tool.Model.replace_object_ifc_representation(body, obj, model_representation)
|
||||
if fallback_material := (int(props.lining_material) or int(props.framing_material) or int(props.glazing_material)):
|
||||
materials = {
|
||||
"Lining": tool.Ifc.get().by_id(int(props.lining_material) or fallback_material),
|
||||
"Framing": tool.Ifc.get().by_id(int(props.framing_material) or fallback_material),
|
||||
}
|
||||
if props.transom_thickness:
|
||||
materials["Glazing"] = tool.Ifc.get().by_id(int(props.glazing_material) or fallback_material)
|
||||
ifcopenshell.api.material.set_shape_aspect_constituents(
|
||||
ifc_file,
|
||||
element=element,
|
||||
context=body,
|
||||
materials={
|
||||
"Lining": tool.Ifc.get().by_id(int(props.lining_material) or fallback_material),
|
||||
"Framing": tool.Ifc.get().by_id(int(props.framing_material) or fallback_material),
|
||||
"Glazing": tool.Ifc.get().by_id(int(props.glazing_material) or fallback_material),
|
||||
},
|
||||
materials=materials,
|
||||
)
|
||||
elif material := ifcopenshell.util.element.get_material(element):
|
||||
ifcopenshell.api.material.unassign_material(ifc_file, products=[element])
|
||||
@@ -143,9 +145,6 @@ def update_door_modifier_representation(obj: bpy.types.Object) -> None:
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=ifcopenshell.util.representation.get_representation(element, active_context),
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
# type attributes
|
||||
@@ -579,9 +578,6 @@ class CancelEditingDoor(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=body,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
props.is_editing = False
|
||||
|
||||
@@ -186,9 +186,6 @@ class FilledOpeningGenerator:
|
||||
tool.Geometry,
|
||||
obj=voided_obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
def regenerate_from_type(self, usecase_path: str, ifc_file: ifcopenshell.file, settings: dict[str, Any]) -> None:
|
||||
@@ -249,9 +246,6 @@ class FilledOpeningGenerator:
|
||||
tool.Geometry,
|
||||
obj=voided_obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
def generate_opening_from_filling(
|
||||
@@ -420,9 +414,6 @@ class RecalculateFill(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=building_obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -798,9 +789,6 @@ class CloneOpening(Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -580,9 +580,6 @@ def get_generic_product_preview_data(context, relating_type):
|
||||
tool.Geometry,
|
||||
obj_type,
|
||||
representation,
|
||||
should_reload=True,
|
||||
is_global=False,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
context.view_layer.update()
|
||||
break
|
||||
|
||||
@@ -406,9 +406,6 @@ class AddOccurrence(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
elif self.representation_template == "EXTRUSION":
|
||||
builder = ifcopenshell.util.shape_builder.ShapeBuilder(tool.Ifc.get())
|
||||
@@ -422,9 +419,6 @@ class AddOccurrence(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
return
|
||||
|
||||
@@ -453,9 +447,6 @@ class AddOccurrence(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
# Update required as core.type.assign_type may change obj.data
|
||||
@@ -738,7 +729,4 @@ def regenerate_profile_usage(usecase_path, ifc_file, settings):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
@@ -98,7 +98,8 @@ class DumbProfileGenerator:
|
||||
vec1 = Vector((polyline_points[i].x, polyline_points[i].y, polyline_points[i].z))
|
||||
vec2 = Vector((polyline_points[i + 1].x, polyline_points[i + 1].y, polyline_points[i + 1].z))
|
||||
coords = (vec1, vec2)
|
||||
profiles.append(self.create_profile_from_2_points(coords))
|
||||
if profile := self.create_profile_from_2_points(coords):
|
||||
profiles.append(profile)
|
||||
return profiles, is_polyline_closed
|
||||
|
||||
def derive_from_cursor(self) -> bpy.types.Object:
|
||||
@@ -166,9 +167,6 @@ class DumbProfileGenerator:
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
pset = ifcopenshell.api.pset.add_pset(self.file, product=element, name="EPset_Parametric")
|
||||
@@ -183,7 +181,7 @@ class DumbProfileGenerator:
|
||||
) -> ProfileFrom2PointsReturn:
|
||||
self.direction = coords[1] - coords[0]
|
||||
length = self.direction.length
|
||||
if round(length, 4) < 0.1:
|
||||
if round(length, 4) < 0.001:
|
||||
return
|
||||
data: dict[str, Any] = {"coords": coords}
|
||||
|
||||
@@ -567,9 +565,6 @@ class DumbProfileJoiner:
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=new_body,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
tool.Geometry.record_object_materials(obj)
|
||||
if element.is_a("IfcFlowSegment") or element.is_a("IfcFlowFitting"):
|
||||
@@ -1036,9 +1031,6 @@ def disable_editing_extrusion_axis(context):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=body,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -1100,9 +1092,6 @@ class EditExtrusionAxis(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=body,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
bpy.context.view_layer.update()
|
||||
|
||||
@@ -538,9 +538,6 @@ def cancel_editing_railing_path(context: bpy.types.Context) -> set[str]:
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=body,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -167,9 +167,6 @@ class DumbSlabGenerator:
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
obj.matrix_world = obj.matrix_world @ Matrix.Rotation(self.x_angle, 4, "X")
|
||||
|
||||
@@ -360,9 +357,6 @@ class DumbSlabPlaner:
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=new_rep,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
bonsai.core.geometry.remove_representation(
|
||||
tool.Ifc, tool.Geometry, obj=obj, representation=representation
|
||||
@@ -386,9 +380,6 @@ class DumbSlabPlaner:
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
|
||||
@@ -588,9 +579,6 @@ class EditSketchExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -632,9 +620,6 @@ def disable_editing_extrusion_profile(context):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=body,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -767,9 +752,6 @@ class EditExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=body,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
# Only certain classes should have a footprint
|
||||
|
||||
@@ -550,7 +550,7 @@ class BIM_PT_door(bpy.types.Panel):
|
||||
|
||||
row = prop_with_search(self.layout, props, "lining_material")
|
||||
tool.Model.draw_material_ui_select(row, props.lining_material)
|
||||
row = prop_with_search(self.layout, props, "framing_material", text="Panel Material")
|
||||
row = prop_with_search(self.layout, props, "framing_material", text="Panel Framing Material")
|
||||
tool.Model.draw_material_ui_select(row, props.framing_material)
|
||||
if props.transom_thickness:
|
||||
row = prop_with_search(self.layout, props, "glazing_material")
|
||||
|
||||
@@ -517,9 +517,6 @@ class ChangeExtrusionXAngle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
# Object rotation
|
||||
@@ -1024,9 +1021,6 @@ class DumbWallGenerator:
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
pset = ifcopenshell.api.pset.add_pset(self.file, product=element, name="EPset_Parametric")
|
||||
ifcopenshell.api.pset.edit_pset(self.file, pset=pset, properties={"Engine": "Bonsai.DumbLayer2"})
|
||||
|
||||
@@ -123,9 +123,6 @@ def update_window_modifier_representation(context: bpy.types.Context) -> None:
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=ifcopenshell.util.representation.get_representation(element, active_context),
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=True,
|
||||
)
|
||||
|
||||
# type attributes
|
||||
@@ -495,9 +492,6 @@ class CancelEditingWindow(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=body,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
props.is_editing = False
|
||||
|
||||
@@ -155,6 +155,7 @@ class CreateProject(bpy.types.Operator):
|
||||
tool.Ifc, tool.Georeference, tool.Project, tool.Spatial, schema=props.export_schema, template=template
|
||||
)
|
||||
tool.Blender.register_toolbar()
|
||||
tool.Loader.set_unit_scale(ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()))
|
||||
|
||||
def rollback(self, data):
|
||||
IfcStore.file = None
|
||||
|
||||
@@ -333,9 +333,6 @@ class AssignClass(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
else:
|
||||
|
||||
@@ -579,9 +576,6 @@ class AddElement(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
if not tool.Ifc.get_entity(props.representation_obj):
|
||||
bpy.data.objects.remove(props.representation_obj)
|
||||
@@ -601,9 +595,6 @@ class AddElement(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
elif representation_template == "EXTRUSION":
|
||||
builder = ifcopenshell.util.shape_builder.ShapeBuilder(tool.Ifc.get())
|
||||
@@ -650,9 +641,6 @@ class AddElement(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
elif representation_template in ("LAYERSET_AXIS2", "LAYERSET_AXIS3"):
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
@@ -763,9 +751,6 @@ class AddElement(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
elif representation_template == "EDGE":
|
||||
builder = ifcopenshell.util.shape_builder.ShapeBuilder(tool.Ifc.get())
|
||||
@@ -778,9 +763,6 @@ class AddElement(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
elif representation_template == "FACE":
|
||||
builder = ifcopenshell.util.shape_builder.ShapeBuilder(tool.Ifc.get())
|
||||
@@ -793,9 +775,6 @@ class AddElement(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
bpy.context.view_layer.update() # Ensures obj.matrix_world is correct
|
||||
|
||||
@@ -105,6 +105,7 @@ classes = (
|
||||
operator.RemoveWorkTime,
|
||||
operator.ReorderTask,
|
||||
operator.SaveAnimationColorScheme,
|
||||
operator.SelectTaskElements,
|
||||
operator.SelectTaskRelatedInputs,
|
||||
operator.SelectTaskRelatedProducts,
|
||||
operator.SelectUnassignedWorkScheduleProducts,
|
||||
|
||||
@@ -243,6 +243,9 @@ class SequenceData:
|
||||
data["NestingIndex"] = None
|
||||
for rel in task.Nests or []:
|
||||
data["NestingIndex"] = rel.RelatedObjects.index(task)
|
||||
data["TotalInputs"] = len(tool.Sequence.get_task_inputs(task))
|
||||
data["TotalOutputs"] = len(tool.Sequence.get_task_outputs(task))
|
||||
data["TotalElements"] = data["TotalInputs"] + data["TotalOutputs"]
|
||||
cls.data["tasks"][task.id()] = data
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -1281,6 +1281,17 @@ class DisableEditingSequence(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SelectTaskElements(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.select_task_elements"
|
||||
bl_label = "Select All Task Elements"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
task: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.select_task_inputs(tool.Sequence, tool.Spatial, task=tool.Ifc.get().by_id(self.task))
|
||||
core.select_task_outputs(tool.Sequence, tool.Spatial, task=tool.Ifc.get().by_id(self.task))
|
||||
|
||||
|
||||
class SelectTaskRelatedProducts(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.select_task_related_products"
|
||||
bl_label = "Select All Output Products"
|
||||
|
||||
@@ -75,6 +75,8 @@ def update_active_task_index(self, context):
|
||||
bonsai.bim.module.pset.data.refresh()
|
||||
if self.editing_task_type == "SEQUENCE":
|
||||
tool.Sequence.load_task_properties()
|
||||
if self.should_auto_select:
|
||||
tool.Sequence.select_active_task_elements(task)
|
||||
|
||||
|
||||
def update_active_task_outputs(self, context):
|
||||
@@ -485,6 +487,9 @@ class BIMWorkScheduleProperties(PropertyGroup):
|
||||
other_columns: EnumProperty(
|
||||
items=[
|
||||
("Controls.Calendar", "Calendar", ""),
|
||||
("Controls.TotalElements", "Total Elements", "The total number of assigned elements to this task"),
|
||||
("Controls.TotalInputs", "Total Inputs", "The total number of input elements to this task"),
|
||||
("Controls.TotalOutputs", "Total Outputs", "The total number of input elements to this task"),
|
||||
],
|
||||
name="Special Columns",
|
||||
)
|
||||
@@ -518,9 +523,9 @@ class BIMWorkScheduleProperties(PropertyGroup):
|
||||
active_task_input_index: IntProperty(name="Active Task Input Index")
|
||||
task_outputs: CollectionProperty(name="Task Outputs", type=TaskProduct)
|
||||
active_task_output_index: IntProperty(name="Active Task Output Index")
|
||||
show_nested_outputs: BoolProperty(name="Show Nested Tasks", default=False, update=update_active_task_outputs)
|
||||
show_nested_resources: BoolProperty(name="Show Nested Tasks", default=False, update=update_active_task_resources)
|
||||
show_nested_inputs: BoolProperty(name="Show Nested Tasks", default=False, update=update_active_task_inputs)
|
||||
show_nested_outputs: BoolProperty(name="Show Nested Task Elements", default=False, update=update_active_task_outputs)
|
||||
show_nested_resources: BoolProperty(name="Show Nested Task Elements", default=False, update=update_active_task_resources)
|
||||
show_nested_inputs: BoolProperty(name="Show Nested Task Elements", default=False, update=update_active_task_inputs)
|
||||
product_input_tasks: CollectionProperty(name="Product Task Inputs", type=TaskProduct)
|
||||
product_output_tasks: CollectionProperty(name="Product Task Outputs", type=TaskProduct)
|
||||
active_product_output_task_index: IntProperty(name="Active Product Output Task Index")
|
||||
@@ -531,6 +536,9 @@ class BIMWorkScheduleProperties(PropertyGroup):
|
||||
filter_by_active_schedule: BoolProperty(
|
||||
name="Filter By Active Schedule", default=False, update=update_filter_by_active_schedule
|
||||
)
|
||||
should_auto_select: BoolProperty(
|
||||
name="Auto Select", description="Auto select elements when task is selected", default=False
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
work_schedule_predefined_types: str
|
||||
|
||||
@@ -290,13 +290,21 @@ class BIM_PT_work_schedules(Panel):
|
||||
row2 = self.layout.row(align=True)
|
||||
row2.alignment = "RIGHT"
|
||||
|
||||
row2.prop(self.props, "enable_reorder", text="", icon="SORTALPHA")
|
||||
row2.operator("bim.enable_editing_task_sequence", text="", icon="TRACKING")
|
||||
row2.operator("bim.enable_editing_task_time", text="", icon="TIME").task = ifc_definition_id
|
||||
row2.operator("bim.enable_editing_task_calendar", text="", icon="VIEW_ORTHO").task = (
|
||||
col = row2.column()
|
||||
row_ = col.row(align=True)
|
||||
op = row_.operator("bim.select_task_elements", icon="RESTRICT_SELECT_OFF", text="Select Elements")
|
||||
op.task = task.ifc_definition_id
|
||||
row_.prop(self.props, "should_auto_select", icon="OBJECT_HIDDEN", text="")
|
||||
|
||||
col = row2.column()
|
||||
row_ = col.row(align=True)
|
||||
row_.prop(self.props, "enable_reorder", text="", icon="SORTALPHA")
|
||||
row_.operator("bim.enable_editing_task_sequence", text="", icon="TRACKING")
|
||||
row_.operator("bim.enable_editing_task_time", text="", icon="TIME").task = ifc_definition_id
|
||||
row_.operator("bim.enable_editing_task_calendar", text="", icon="VIEW_ORTHO").task = (
|
||||
ifc_definition_id
|
||||
)
|
||||
row2.operator("bim.enable_editing_task_attributes", text="", icon="GREASEPENCIL").task = (
|
||||
row_.operator("bim.enable_editing_task_attributes", text="", icon="GREASEPENCIL").task = (
|
||||
ifc_definition_id
|
||||
)
|
||||
row.operator("bim.add_task", text="Add", icon="ADD").task = ifc_definition_id
|
||||
@@ -716,11 +724,10 @@ class BIM_PT_task_icom(Panel):
|
||||
input_id = self.props.task_inputs[self.props.active_task_input_index].ifc_definition_id
|
||||
op.related_object = input_id
|
||||
|
||||
op = row2.operator("bim.select_task_related_inputs", icon="RESTRICT_SELECT_OFF", text="Select")
|
||||
op = row2.operator("bim.select_task_related_inputs", icon="RESTRICT_SELECT_OFF", text="")
|
||||
op.task = task.ifc_definition_id
|
||||
row2.prop(self.props, "show_nested_inputs", icon="OUTLINER", text="")
|
||||
|
||||
row2 = col.row()
|
||||
row2.prop(self.props, "show_nested_inputs", text="Show Nested")
|
||||
row2 = col.row()
|
||||
row2.template_list("BIM_UL_task_inputs", "", self.props, "task_inputs", self.props, "active_task_input_index")
|
||||
|
||||
@@ -744,8 +751,7 @@ class BIM_PT_task_icom(Panel):
|
||||
op.related_object_type = "RESOURCE"
|
||||
op.resource = self.props.task_resources[self.props.active_task_resource_index].ifc_definition_id
|
||||
|
||||
row2 = col.row()
|
||||
row2.prop(self.props, "show_nested_resources", text="Show Nested")
|
||||
row2.prop(self.props, "show_nested_resources", icon="OUTLINER", text="")
|
||||
|
||||
row2 = col.row()
|
||||
row2.template_list(
|
||||
@@ -773,10 +779,10 @@ class BIM_PT_task_icom(Panel):
|
||||
output_id = self.props.task_outputs[self.props.active_task_output_index].ifc_definition_id
|
||||
op.relating_product = output_id
|
||||
|
||||
op = row2.operator("bim.select_task_related_products", icon="RESTRICT_SELECT_OFF", text="Select")
|
||||
op = row2.operator("bim.select_task_related_products", icon="RESTRICT_SELECT_OFF", text="")
|
||||
op.task = task.ifc_definition_id
|
||||
row2 = col.row()
|
||||
row2.prop(self.props, "show_nested_outputs", text="Show Nested")
|
||||
row2.prop(self.props, "show_nested_outputs", icon="OUTLINER", text="")
|
||||
|
||||
row2 = col.row()
|
||||
row2.template_list(
|
||||
"BIM_UL_task_outputs", "", self.props, "task_outputs", self.props, "active_task_output_index"
|
||||
@@ -893,7 +899,7 @@ class BIM_UL_tasks(UIList):
|
||||
split2 = split1.split(factor=0.9 - min(0.5, 0.15 * len(self.props.columns)))
|
||||
split2.prop(item, "name", emboss=False, text="")
|
||||
|
||||
BIM_UL_tasks.draw_custom_columns(self.props, split2, item, task)
|
||||
BIM_UL_tasks.draw_custom_columns(self.props, split2, item, task, item.ifc_definition_id)
|
||||
|
||||
if self.props.active_task_id and self.props.editing_task_type == "ATTRIBUTES":
|
||||
row.prop(
|
||||
@@ -960,6 +966,7 @@ class BIM_UL_tasks(UIList):
|
||||
row: bpy.types.UILayout,
|
||||
item: Optional[bpy.types.PropertyGroup] = None,
|
||||
task: Optional[dict[str, Any]] = None,
|
||||
ifc_definition_id: int = 0,
|
||||
*,
|
||||
header: bool = False,
|
||||
) -> None:
|
||||
@@ -999,6 +1006,21 @@ class BIM_UL_tasks(UIList):
|
||||
row.label(text=item.derived_calendar + "*")
|
||||
else:
|
||||
row.label(text=item.calendar or "-")
|
||||
elif column.name == "Controls.TotalInputs":
|
||||
if header:
|
||||
row.label(text="# Inputs")
|
||||
else:
|
||||
row.label(text=str(SequenceData.data["tasks"][ifc_definition_id]["TotalInputs"]))
|
||||
elif column.name == "Controls.TotalOutputs":
|
||||
if header:
|
||||
row.label(text="# Outputs")
|
||||
else:
|
||||
row.label(text=str(SequenceData.data["tasks"][ifc_definition_id]["TotalOutputs"]))
|
||||
elif column.name == "Controls.TotalElements":
|
||||
if header:
|
||||
row.label(text="# Elements")
|
||||
else:
|
||||
row.label(text=str(SequenceData.data["tasks"][ifc_definition_id]["TotalElements"]))
|
||||
else:
|
||||
ifc_class, name = column.name.split(".")
|
||||
if header:
|
||||
|
||||
@@ -148,14 +148,14 @@ class SpatialDecompositionData:
|
||||
elif tool.Ifc.get_schema() == "IFC4X3":
|
||||
results = {
|
||||
"IfcBridge": ["IfcBridge", "IfcBridgePart", "IfcSpace"],
|
||||
"IfcBridgePart": ["IfcSpace"],
|
||||
"IfcBridgePart": ["IfcBridgePart", "IfcSpace"],
|
||||
"IfcBuilding": ["IfcBuilding", "IfcBuildingStorey", "IfcSpace"],
|
||||
"IfcBuildingStorey": ["IfcBuildingStorey", "IfcSpace"],
|
||||
"IfcExternalSpatialElement": ["IfcExternalSpatialElement"],
|
||||
"IfcFacility": ["IfcFacility", "IfcFacilityPartCommon", "IfcSpace"],
|
||||
"IfcFacilityPartCommon": ["IfcSpace"],
|
||||
"IfcFacilityPartCommon": ["IfcFacilityPartCommon", "IfcSpace"],
|
||||
"IfcMarineFacility": ["IfcMarineFacility", "IfcMarinePart", "IfcSpace"],
|
||||
"IfcMarinePart": ["IfcSpace"],
|
||||
"IfcMarinePart": ["IfcMarinePart", "IfcSpace"],
|
||||
"IfcProject": [
|
||||
"IfcBridge",
|
||||
"IfcBuilding",
|
||||
@@ -168,9 +168,9 @@ class SpatialDecompositionData:
|
||||
"IfcSpace",
|
||||
],
|
||||
"IfcRailway": ["IfcRailway", "IfcRailwayPart", "IfcSpace"],
|
||||
"IfcRailwayPart": ["IfcSpace"],
|
||||
"IfcRailwayPart": ["IfcRailwayPart", "IfcSpace"],
|
||||
"IfcRoad": ["IfcRoad", "IfcRoadPart", "IfcSpace"],
|
||||
"IfcRoadPart": ["IfcSpace"],
|
||||
"IfcRoadPart": ["IfcRoadPart", "IfcSpace"],
|
||||
"IfcSite": [
|
||||
"IfcBridge",
|
||||
"IfcBuilding",
|
||||
|
||||
@@ -70,7 +70,7 @@ class GridDecorator:
|
||||
|
||||
grid_props = tool.Spatial.get_grid_props()
|
||||
for axis in grid_props.grid_axes:
|
||||
if not (obj := axis.obj) or obj.hide_get() == True:
|
||||
if not (obj := axis.obj) or obj.visible_get() is False:
|
||||
continue
|
||||
if obj.select_get() and context.mode != "OBJECT":
|
||||
continue
|
||||
@@ -125,21 +125,22 @@ class GridDecorator:
|
||||
unselected_edges = []
|
||||
grid_props = tool.Spatial.get_grid_props()
|
||||
for axis in grid_props.grid_axes:
|
||||
if (obj := axis.obj) and obj.hide_get() == False:
|
||||
if obj.select_get():
|
||||
if context.mode != "OBJECT":
|
||||
continue
|
||||
edges = selected_edges
|
||||
verts = selected_verts
|
||||
else:
|
||||
edges = unselected_edges
|
||||
verts = unselected_verts
|
||||
i = len(verts)
|
||||
edges.append([i, i + 1])
|
||||
matrix_world = obj.matrix_world
|
||||
v1 = matrix_world @ obj.data.vertices[0].co
|
||||
v2 = matrix_world @ obj.data.vertices[1].co
|
||||
verts.extend([v1, v2])
|
||||
if not (obj := axis.obj) or obj.visible_get() is False:
|
||||
continue
|
||||
if obj.select_get():
|
||||
if context.mode != "OBJECT":
|
||||
continue
|
||||
edges = selected_edges
|
||||
verts = selected_verts
|
||||
else:
|
||||
edges = unselected_edges
|
||||
verts = unselected_verts
|
||||
i = len(verts)
|
||||
edges.append([i, i + 1])
|
||||
matrix_world = obj.matrix_world
|
||||
v1 = matrix_world @ obj.data.vertices[0].co
|
||||
v2 = matrix_world @ obj.data.vertices[1].co
|
||||
verts.extend([v1, v2])
|
||||
|
||||
if unselected_verts:
|
||||
self.draw_batch("LINES", unselected_verts, decorator_color_background, unselected_edges)
|
||||
|
||||
@@ -550,9 +550,7 @@ class ToggleGrids(bpy.types.Operator):
|
||||
is_visible: bpy.props.BoolProperty(name="Is Visible", default=False, options={"SKIP_SAVE"})
|
||||
|
||||
def execute(self, context):
|
||||
for element in tool.Ifc.get().by_type("IfcGrid") + tool.Ifc.get().by_type("IfcGridAxis"):
|
||||
if obj := tool.Ifc.get_object(element):
|
||||
obj.hide_set(not self.is_visible)
|
||||
tool.Spatial.set_grid_visibility(self.is_visible)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -564,11 +562,5 @@ class ToggleSpatialElements(bpy.types.Operator):
|
||||
is_visible: bpy.props.BoolProperty(name="Is Visible", default=False, options={"SKIP_SAVE"})
|
||||
|
||||
def execute(self, context):
|
||||
if tool.Ifc.get().schema == "IFC2X3":
|
||||
elements = tool.Ifc.get().by_type("IfcSpatialStructureElement")
|
||||
else:
|
||||
elements = tool.Ifc.get().by_type("IfcSpatialElement")
|
||||
for element in elements:
|
||||
if obj := tool.Ifc.get_object(element):
|
||||
obj.hide_set(not self.is_visible)
|
||||
tool.Spatial.set_space_visibility(self.is_visible)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -224,7 +224,7 @@ class BIMSpatialDecompositionProperties(PropertyGroup):
|
||||
is_visible: BoolProperty(
|
||||
name="Is Visible",
|
||||
description="Show or hide spatial elements, such as buildings, sites, etc",
|
||||
default=True,
|
||||
default=False,
|
||||
update=update_spatial_is_visible,
|
||||
)
|
||||
container_filter: StringProperty(name="Container Filter", default="", options={"TEXTEDIT_UPDATE"})
|
||||
|
||||
@@ -135,6 +135,7 @@ class IfcTesterNamespace(socketio.AsyncNamespace):
|
||||
try:
|
||||
request_id = data.get("id")
|
||||
ids_string = data.get("ids")
|
||||
props = tool.Tester.get_tester_props()
|
||||
|
||||
if not request_id:
|
||||
await self.emit("error", {"error": "No request ID provided"}, room=sid)
|
||||
@@ -176,7 +177,7 @@ class IfcTesterNamespace(socketio.AsyncNamespace):
|
||||
json_report = json_reporter.to_string()
|
||||
|
||||
# HTML report
|
||||
html_reporter = ifctester.reporter.Html(ids)
|
||||
html_reporter = ifctester.reporter.Html(ids, hide_skipped=props.hide_skipped_specs)
|
||||
html_reporter.report()
|
||||
html_report = html_reporter.to_string()
|
||||
|
||||
@@ -267,7 +268,7 @@ class ExecuteIfcTester(bpy.types.Operator):
|
||||
start = time.time()
|
||||
|
||||
if props.generate_html_report:
|
||||
engine = ifctester.reporter.Html(specs)
|
||||
engine = ifctester.reporter.Html(specs, props.hide_skipped_specs)
|
||||
engine.report()
|
||||
output_path = output.as_posix()
|
||||
engine.to_file(output_path)
|
||||
@@ -337,9 +338,21 @@ class StartIfcTesterWebapp(bpy.types.Operator):
|
||||
websocket_server_thread = threading.Thread(target=run_websocket_server, daemon=True)
|
||||
websocket_server_thread.start()
|
||||
|
||||
py_version = sys.version_info
|
||||
bonsai_lib_path = (
|
||||
Path(bpy.utils.user_resource("EXTENSIONS"))
|
||||
/ ".local"
|
||||
/ "lib"
|
||||
/ f"python{py_version.major}.{py_version.minor}"
|
||||
/ "site-packages"
|
||||
)
|
||||
env = os.environ.copy()
|
||||
env["BONSAI_LIB_PATH"] = str(bonsai_lib_path)
|
||||
env["BONSAI_VERSION"] = tool.Blender.get_bonsai_version()
|
||||
|
||||
# Start the Flask server as subprocess
|
||||
webapp_process = subprocess.Popen(
|
||||
[sys.executable, webapp_serve_path, "--host", "127.0.0.1", "--port", str(webapp_port)]
|
||||
[sys.executable, webapp_serve_path, "--host", "127.0.0.1", "--port", str(webapp_port)], env=env
|
||||
)
|
||||
|
||||
# Update properties
|
||||
@@ -472,6 +485,7 @@ class SelectRequirement(bpy.types.Operator):
|
||||
props.n_entities = len(failed_entities)
|
||||
props.has_entities = True if props.n_entities > 0 else False
|
||||
props.failed_entities.clear()
|
||||
props.active_requirement_index = self.req_index
|
||||
|
||||
for e in failed_entities:
|
||||
new_entity = props.failed_entities.add()
|
||||
|
||||
@@ -71,6 +71,8 @@ class IfcTesterProperties(PropertyGroup):
|
||||
generate_ods_report: BoolProperty(default=False, name="Generate ODS report", options=set())
|
||||
flag: BoolProperty(default=False, name="Flag Failed Entities", options=set())
|
||||
active_specification_index: IntProperty(name="Active Specification Index", update=update_active_specification_index)
|
||||
active_requirement_index: IntProperty(name="Active Requirement Index")
|
||||
|
||||
old_index: IntProperty(name="", default=0)
|
||||
active_failed_entity_index: IntProperty(name="Active Failed Entity Index")
|
||||
specifications: CollectionProperty(name="Specifications", type=Specification)
|
||||
@@ -80,6 +82,7 @@ class IfcTesterProperties(PropertyGroup):
|
||||
webapp_server_port: IntProperty(name="Webapp Server Port", default=0)
|
||||
webapp_is_running: BoolProperty(default=False, name="Webapp Is Running", options=set())
|
||||
websocket_server_port: IntProperty(name="WebSocket Server Port", default=0)
|
||||
hide_skipped_specs: BoolProperty(default=False, name="Hide skipped Specifications", options=set())
|
||||
|
||||
if TYPE_CHECKING:
|
||||
specs: MultipleFileSelect
|
||||
@@ -89,6 +92,7 @@ class IfcTesterProperties(PropertyGroup):
|
||||
generate_ods_report: bool
|
||||
flag: bool
|
||||
active_specification_index: int
|
||||
active_requirement_index: int
|
||||
old_index: int
|
||||
active_failed_entity_index: int
|
||||
specifications: bpy.types.bpy_prop_collection_idprop[Specification]
|
||||
|
||||
@@ -20,6 +20,8 @@ from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from bonsai.bim.ui import draw_multiline_text
|
||||
|
||||
import bpy
|
||||
from bpy.types import Panel, UIList
|
||||
import bonsai.tool as tool
|
||||
@@ -60,7 +62,8 @@ class BIM_PT_tester(Panel):
|
||||
row.prop(props, "generate_ods_report")
|
||||
row = self.layout.row()
|
||||
row.prop(props, "flag")
|
||||
|
||||
row = self.layout.row()
|
||||
row.prop(props, "hide_skipped_specs")
|
||||
if not tool.Ifc.get() or not props.should_load_from_memory:
|
||||
row = self.layout.row(align=True)
|
||||
props.ifc_files.layout_file_select(row, "*.ifc;*.ifczip;*.ifcxml", "IFC File(s)")
|
||||
@@ -95,13 +98,16 @@ class BIM_PT_tester(Panel):
|
||||
"active_specification_index",
|
||||
)
|
||||
|
||||
self.draw_editable_ui()
|
||||
self.draw_editable_ui(context)
|
||||
row = self.layout.row()
|
||||
row.operator("bim.export_bcf", text="Export BCF", icon="EXPORT")
|
||||
|
||||
def draw_editable_ui(self) -> None:
|
||||
def draw_editable_ui(self, context: bpy.types.Context) -> None:
|
||||
props = tool.Tester.get_tester_props()
|
||||
specification = TesterData.data["specification"]
|
||||
is_skipped = specification["total_checks"] == 0 and specification["cardinality"] == "optional"
|
||||
if props.hide_skipped_specs and is_skipped:
|
||||
return
|
||||
|
||||
n_requirements = len(specification["requirements"])
|
||||
|
||||
@@ -110,6 +116,13 @@ class BIM_PT_tester(Panel):
|
||||
row.label(
|
||||
text=f'Passed: {specification["total_checks_pass"]}/{specification["total_checks"]} ({specification["percent_checks_pass"]}%)'
|
||||
)
|
||||
row = self.layout.row()
|
||||
if specification.get("instructions"):
|
||||
row.label(text="Instructions:")
|
||||
box = self.layout.box()
|
||||
column = box.column(align=True)
|
||||
draw_multiline_text(column, specification.get("instructions"), context=context)
|
||||
|
||||
row = self.layout.row()
|
||||
row.label(text=f"Requirements ({n_requirements}):")
|
||||
if props.flag:
|
||||
@@ -132,6 +145,16 @@ class BIM_PT_tester(Panel):
|
||||
and props.n_entities > 0
|
||||
and len(props.failed_entities) > 0
|
||||
):
|
||||
|
||||
requirement = specification["requirements"][props.active_requirement_index]
|
||||
metadata = requirement.get("metadata")
|
||||
if metadata and metadata.get("@instructions"):
|
||||
row = self.layout.row()
|
||||
row.label(text="Instructions:")
|
||||
box = self.layout.box()
|
||||
column = box.column(align=True)
|
||||
draw_multiline_text(column, metadata.get("@instructions"), context=context)
|
||||
|
||||
row = self.layout.row()
|
||||
row.label(text=f"Failed entities [{props.n_entities}]:")
|
||||
self.layout.template_list(
|
||||
@@ -160,6 +183,34 @@ class BIM_UL_tester_specifications(UIList):
|
||||
row.label(text=item.name, icon="CHECKMARK" if item.status else "CANCEL")
|
||||
row.label(text=item.description)
|
||||
|
||||
def filter_items(self, context: bpy.types.Context, data: IfcTesterProperties, propname: str):
|
||||
items = getattr(data, propname)
|
||||
filter_flags = [self.bitflag_filter_item] * len(items)
|
||||
|
||||
props = tool.Tester.get_tester_props()
|
||||
if props.hide_skipped_specs:
|
||||
for idx, item in enumerate(items):
|
||||
report = tool.Tester.report[idx]
|
||||
if report["total_checks"] != 0:
|
||||
filter_flags[idx] |= self.bitflag_filter_item
|
||||
else:
|
||||
filter_flags[idx] &= ~self.bitflag_filter_item
|
||||
|
||||
filter_name = self.filter_name
|
||||
if filter_name:
|
||||
name_filtered = bpy.types.UI_UL_list.filter_items_by_name(
|
||||
filter_name,
|
||||
self.bitflag_filter_item,
|
||||
items,
|
||||
"name",
|
||||
)
|
||||
if len(name_filtered) == len(filter_flags):
|
||||
for idx, flag in enumerate(name_filtered):
|
||||
if flag == 0:
|
||||
filter_flags[idx] &= ~self.bitflag_filter_item
|
||||
|
||||
return filter_flags, []
|
||||
|
||||
|
||||
class BIM_UL_tester_failed_entities(UIList):
|
||||
def draw_item(
|
||||
|
||||
@@ -121,9 +121,6 @@ class UnassignType(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=new_active_representation,
|
||||
should_reload=False,
|
||||
is_global=False,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -148,11 +148,6 @@ class AddOpening(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=voided_obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
# Don't sync changes because object has an opening,
|
||||
# therefore bim.update_representaiton wouldn't work either way.
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
tool.Geometry.lock_scale(voided_obj)
|
||||
|
||||
@@ -196,9 +191,6 @@ class RemoveOpening(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=building_obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
tool.Geometry.unlock_scale_object_with_openings(obj)
|
||||
tool.Geometry.clear_cache(element)
|
||||
|
||||
@@ -40,6 +40,7 @@ from bonsai.bim.module.bsdd.prop import BIMBSDDProperties, BSDDProperty
|
||||
from bonsai.bim.module.pset.prop import IfcProperty
|
||||
from typing import Optional, TYPE_CHECKING, Literal
|
||||
from natsort import natsorted
|
||||
import textwrap
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -373,11 +374,6 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
|
||||
description="Path to create and store temporary files. If left blank, a system default will be used.",
|
||||
subtype="DIR_PATH",
|
||||
)
|
||||
spatial_elements_unselectable: BoolProperty(
|
||||
name="Make Spatial Elements Unselectable By Default",
|
||||
default=True,
|
||||
description="If disabled, it will be possible to select spatial elements in the 3D viewport.\nIt is recommended to keep this `Enabled`, as this can have unintended consequences",
|
||||
)
|
||||
decorations_colour: bpy.props.FloatVectorProperty(
|
||||
name="Decorations Color", subtype="COLOR", default=(1, 1, 1, 1), min=0.0, max=1.0, size=4
|
||||
)
|
||||
@@ -503,7 +499,6 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
|
||||
activate_workspace: bool
|
||||
should_setup_toolbar: bool
|
||||
should_play_chaching_sound: bool
|
||||
spatial_elements_unselectable: bool
|
||||
tmp_dir: str
|
||||
decorations_colour: tuple[float, float, float, float]
|
||||
decorator_color_selected: tuple[float, float, float, float]
|
||||
@@ -565,7 +560,6 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
|
||||
layout.prop(self, "should_setup_toolbar")
|
||||
layout.prop(self, "should_use_snap")
|
||||
layout.prop(self, "should_play_chaching_sound")
|
||||
layout.prop(self, "spatial_elements_unselectable")
|
||||
|
||||
def draw_model_settings(self, layout: bpy.types.UILayout, context: bpy.types.Context) -> None:
|
||||
layout.prop(self, "occurrence_name_style")
|
||||
@@ -1498,6 +1492,31 @@ def draw_custom_context_menu(self: bpy.types.Menu, context: bpy.types.Context) -
|
||||
url_op.uri = url
|
||||
|
||||
|
||||
def draw_multiline_text(
|
||||
layout: bpy.types.UILayout,
|
||||
text: str,
|
||||
*,
|
||||
context: bpy.types.Context | None = None,
|
||||
) -> None:
|
||||
"""Render a read-only text box that wraps long text."""
|
||||
assert layout
|
||||
|
||||
region_width = 200
|
||||
if context and context.region:
|
||||
region_width = context.region.width
|
||||
|
||||
approximate_char_width = 7 # Empirical average width for Blender UI font (px)
|
||||
wrap_width = max(20, int(region_width / approximate_char_width))
|
||||
|
||||
for paragraph in text.splitlines():
|
||||
if not paragraph:
|
||||
layout.label(text="")
|
||||
continue
|
||||
|
||||
for line in textwrap.wrap(paragraph, width=wrap_width):
|
||||
layout.label(text=line)
|
||||
|
||||
|
||||
class BIM_PT_decorators_overlay(Panel):
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "HEADER"
|
||||
|
||||
@@ -39,7 +39,7 @@ def disable_editing_text(drawing: type[tool.Drawing], obj: bpy.types.Object) ->
|
||||
def edit_text(drawing: type[tool.Drawing], obj: bpy.types.Object) -> None:
|
||||
drawing.synchronise_ifc_and_text_attributes(obj)
|
||||
drawing.update_text_size_pset(obj)
|
||||
drawing.update_newline_at_and_symbol(obj)
|
||||
drawing.update_text_annotation_properties(obj)
|
||||
drawing.disable_editing_text(obj)
|
||||
|
||||
|
||||
@@ -440,8 +440,7 @@ def add_annotation(
|
||||
drawing_tool.show_decorations()
|
||||
obj = drawing_tool.create_annotation_object(drawing, object_type)
|
||||
element = ifc.get_entity(obj)
|
||||
# TODO: element is never None?
|
||||
if not element:
|
||||
if not element: # Brand new annotation
|
||||
relating_type_rep = drawing_tool.get_annotation_representation(relating_type) if relating_type else None
|
||||
element = drawing_tool.run_root_assign_class(
|
||||
obj=obj,
|
||||
|
||||
@@ -115,24 +115,9 @@ def switch_representation(
|
||||
geometry: type[tool.Geometry],
|
||||
obj: bpy.types.Object,
|
||||
representation: ifcopenshell.entity_instance,
|
||||
should_reload: bool = True,
|
||||
is_global: bool = True,
|
||||
should_sync_changes_first: bool = False,
|
||||
apply_openings: bool = True,
|
||||
) -> None:
|
||||
"""Function can switch to representation that wasn't yet assigned to that object. See #2766.
|
||||
|
||||
:param should_sync_changes_first: sync ifc representation with current state of `obj.data`
|
||||
:param should_reload: reload `obj.data` from ifc representation
|
||||
:param is_global: replace mesh data for all users of `obj.data`, not just `obj`
|
||||
|
||||
"""
|
||||
if should_sync_changes_first and ifc.is_edited(obj) and not geometry.is_box_representation(representation):
|
||||
representation_id = geometry.get_representation_id(representation)
|
||||
geometry.run_geometry_update_representation(obj=obj)
|
||||
if not geometry.does_representation_id_exist(representation_id):
|
||||
return
|
||||
|
||||
"""Switch obj representation (even if not yet assigned to that object. See #2766.)"""
|
||||
if not geometry.get_object_data(obj) and geometry.is_text_literal(representation):
|
||||
return
|
||||
|
||||
@@ -142,9 +127,7 @@ def switch_representation(
|
||||
geometry.reimport_element_representations(obj, representation, apply_openings=apply_openings)
|
||||
|
||||
|
||||
def get_representation_ifc_parameters(
|
||||
geometry: type[tool.Geometry], obj: bpy.types.Object, should_sync_changes_first: bool = False
|
||||
) -> None:
|
||||
def get_representation_ifc_parameters(geometry: type[tool.Geometry], obj: bpy.types.Object) -> None:
|
||||
geometry.import_representation_parameters(geometry.get_object_data(obj))
|
||||
|
||||
|
||||
|
||||
@@ -72,6 +72,10 @@ def assign_class(
|
||||
"""
|
||||
Args:
|
||||
context: is not optional if `should_add_representation` is True
|
||||
|
||||
TODO: Do NOT use should_add_representation. Because it internally calls
|
||||
geometry.add_representation which is 1,000 lines of Blender -> IFC magic.
|
||||
Instead, explicitly create a representation using tool.Geometry.
|
||||
"""
|
||||
if ifc.get_entity(obj):
|
||||
return
|
||||
|
||||
@@ -394,7 +394,7 @@ class Drawing:
|
||||
def sync_object_placement(cls, obj): pass
|
||||
def synchronise_ifc_and_text_attributes(cls, obj): pass
|
||||
def update_embedded_svg_location(cls, uri, old_location, new_location): pass
|
||||
def update_newline_at_and_symbol(cls, obj): pass
|
||||
def update_text_annotation_properties(cls, obj): pass
|
||||
def update_text_size_pset(cls, obj): pass
|
||||
|
||||
|
||||
@@ -1092,7 +1092,7 @@ class Type:
|
||||
def get_type_occurrences(cls, element_type): pass
|
||||
def has_material_usage(cls, element): pass
|
||||
def run_geometry_add_representation(cls, obj=None, context=None, ifc_representation_class=None, profile_set_usage=None): pass
|
||||
def run_geometry_switch_representation(cls, obj=None, representation=None, should_reload=None, is_global=None): pass
|
||||
def run_geometry_switch_representation(cls, obj=None, representation=None): pass
|
||||
|
||||
|
||||
@interface
|
||||
|
||||
@@ -477,6 +477,8 @@ class Blender(bonsai.core.tool.Blender):
|
||||
def ensure_bin_in_path(cls) -> None:
|
||||
"""Check 'bin' folder is in PATH, if not add for this session"""
|
||||
bin_dir = str(Path(__file__).parent.parent.resolve() / "libs" / "bin")
|
||||
if not os.path.isdir(bin_dir):
|
||||
return # Maybe the user is using a system-wide Python package. See #7157.
|
||||
current_path = os.environ["PATH"]
|
||||
if bin_dir not in current_path:
|
||||
os.environ["PATH"] = current_path + os.pathsep + bin_dir
|
||||
@@ -1859,6 +1861,75 @@ class Blender(bonsai.core.tool.Blender):
|
||||
|
||||
return unit_scale
|
||||
|
||||
@classmethod
|
||||
def reset_object_visibility(cls):
|
||||
override = cls.get_viewport_context()
|
||||
with bpy.context.temp_override(**override):
|
||||
bpy.ops.object.hide_view_clear(select=False)
|
||||
|
||||
@classmethod
|
||||
def isolate_objects(cls, objs):
|
||||
previously_selected = {o.name for o in bpy.context.selected_objects}
|
||||
previously_active = bpy.context.view_layer.objects.active
|
||||
|
||||
override = cls.get_viewport_context()
|
||||
with bpy.context.temp_override(**override):
|
||||
bpy.ops.object.hide_view_clear(select=False)
|
||||
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
for obj in objs:
|
||||
obj.select_set(True)
|
||||
with bpy.context.temp_override(**override):
|
||||
bpy.ops.object.hide_view_set(unselected=True)
|
||||
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
for name in previously_selected:
|
||||
obj = bpy.data.objects.get(name)
|
||||
if obj:
|
||||
obj.select_set(True)
|
||||
bpy.context.view_layer.objects.active = previously_active
|
||||
|
||||
@classmethod
|
||||
def hide_objects(cls, objs):
|
||||
previously_selected = {o.name for o in bpy.context.selected_objects}
|
||||
previously_active = bpy.context.view_layer.objects.active
|
||||
|
||||
override = cls.get_viewport_context()
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
for obj in objs:
|
||||
obj.select_set(True)
|
||||
with bpy.context.temp_override(**override):
|
||||
bpy.ops.object.hide_view_set(unselected=False)
|
||||
|
||||
for name in previously_selected:
|
||||
obj = bpy.data.objects.get(name)
|
||||
if obj:
|
||||
obj.select_set(True)
|
||||
bpy.context.view_layer.objects.active = previously_active
|
||||
|
||||
@classmethod
|
||||
def show_objects(cls, objs):
|
||||
previously_selected = {o.name for o in bpy.context.selected_objects}
|
||||
previously_active = bpy.context.view_layer.objects.active
|
||||
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
override = cls.get_viewport_context()
|
||||
with bpy.context.temp_override(**override):
|
||||
bpy.ops.object.hide_view_clear(select=True)
|
||||
|
||||
for obj in bpy.context.selected_objects:
|
||||
if obj in objs:
|
||||
obj.select_set(False)
|
||||
with bpy.context.temp_override(**override):
|
||||
bpy.ops.object.hide_view_set(unselected=False)
|
||||
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
for name in previously_selected:
|
||||
obj = bpy.data.objects.get(name)
|
||||
if obj:
|
||||
obj.select_set(True)
|
||||
bpy.context.view_layer.objects.active = previously_active
|
||||
|
||||
@classmethod
|
||||
def validate_shader_batch_data(cls, pos: Any, indices: Optional[Any]) -> bool:
|
||||
"""Validate shader batch data.
|
||||
|
||||
@@ -65,8 +65,11 @@ class Collector(bonsai.core.tool.Collector):
|
||||
collection = cls._create_project_child_collection("IfcTypeProduct")
|
||||
cls.link_collection_object_safe(collection, obj)
|
||||
elif element.is_a("IfcSpace"):
|
||||
if tool.Geometry.is_locked(element):
|
||||
tool.Geometry.lock_object(obj)
|
||||
collection = cls._create_project_child_collection("IfcSpace")
|
||||
cls.link_collection_object_safe(collection, obj)
|
||||
obj.hide_viewport = True
|
||||
elif element.is_a("IfcStructuralItem"):
|
||||
collection = cls._create_project_child_collection("IfcStructuralItem")
|
||||
cls.link_collection_object_safe(collection, obj)
|
||||
@@ -90,6 +93,7 @@ class Collector(bonsai.core.tool.Collector):
|
||||
cls.link_collection_object_safe(collection, obj)
|
||||
project_obj = tool.Ifc.get_object(tool.Ifc.get().by_type("IfcProject")[0])
|
||||
cls.link_collection_child_safe(tool.Blender.get_object_bim_props(project_obj).collection, collection)
|
||||
obj.hide_viewport = True
|
||||
elif (
|
||||
tool.Ifc.get_schema() != "IFC2X3"
|
||||
and element.is_a("IfcSpatialElement")
|
||||
@@ -101,6 +105,7 @@ class Collector(bonsai.core.tool.Collector):
|
||||
cls.link_collection_object_safe(collection, obj)
|
||||
project_obj = tool.Ifc.get_object(tool.Ifc.get().by_type("IfcProject")[0])
|
||||
cls.link_collection_child_safe(tool.Blender.get_object_bim_props(project_obj).collection, collection)
|
||||
obj.hide_viewport = True
|
||||
elif element.is_a("IfcAnnotation") and element.ObjectType == "DRAWING":
|
||||
if collection := cls._create_own_collection(obj):
|
||||
cls.link_collection_object_safe(collection, obj)
|
||||
|
||||
@@ -820,7 +820,11 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
@classmethod
|
||||
def import_annotations_in_group(cls, group: ifcopenshell.entity_instance) -> None:
|
||||
elements = set(
|
||||
[e for e in cls.get_group_elements(group) if e.is_a("IfcAnnotation") and e.ObjectType != "DRAWING"]
|
||||
[
|
||||
e
|
||||
for e in cls.get_group_elements(group)
|
||||
if e.is_a("IfcAnnotation") and e.ObjectType != "DRAWING" and not tool.Ifc.get_object(e)
|
||||
]
|
||||
)
|
||||
logger = logging.getLogger("ImportIFC")
|
||||
ifc_import_settings = bonsai.bim.import_ifc.IfcImportSettings.factory(bpy.context, None, logger)
|
||||
@@ -1098,6 +1102,8 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
props.font_size = str(text_data["FontSize"])
|
||||
props.newline_at = text_data["Newline_At"]
|
||||
props.set_symbol(text_data["Symbol"])
|
||||
props.reverse_list = text_data["Reverse_List"]
|
||||
props.list_separator = text_data["List_Separator"]
|
||||
|
||||
@classmethod
|
||||
def import_assigned_product(cls, obj: bpy.types.Object) -> None:
|
||||
@@ -1172,9 +1178,6 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@@ -1235,22 +1238,25 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def update_newline_at_and_symbol(cls, obj: bpy.types.Object) -> None:
|
||||
def update_text_annotation_properties(cls, obj: bpy.types.Object) -> None:
|
||||
"""Update all EPset_Annotation properties from the text props"""
|
||||
props = cls.get_text_props(obj)
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
assert element
|
||||
newline_at = int(props.newline_at)
|
||||
symbol = props.get_symbol()
|
||||
|
||||
ifc_file = tool.Ifc.get()
|
||||
pset = tool.Pset.get_element_pset(element, "EPset_Annotation")
|
||||
if not pset:
|
||||
pset = ifcopenshell.api.pset.add_pset(ifc_file, product=element, name="EPset_Annotation")
|
||||
|
||||
ifcopenshell.api.pset.edit_pset(
|
||||
ifc_file,
|
||||
pset=pset,
|
||||
properties={
|
||||
"Newline_At": newline_at,
|
||||
"Symbol": symbol,
|
||||
"Newline_At": int(props.newline_at),
|
||||
"Symbol": props.get_symbol(),
|
||||
"Reverse_List": props.reverse_list,
|
||||
"List_Separator": props.list_separator or "",
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1461,10 +1467,8 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
def ensure_referenced_drawing_obj_exists(drawing: ifcopenshell.entity_instance):
|
||||
obj = tool.Ifc.get_object(drawing)
|
||||
if obj is None:
|
||||
# Annotations in that drawing are lazy loaded as needed
|
||||
obj = cls.import_drawing(drawing)
|
||||
# there is no need for other drawing annotations in that case
|
||||
# but we import them anyway so we won't break that drawing activation
|
||||
cls.import_annotations_in_group(cls.get_drawing_group(drawing))
|
||||
tool.Blender.get_layer_collection(obj.users_collection[0]).hide_viewport = True
|
||||
|
||||
target_view = ifcopenshell.util.element.get_pset(reference_element, "EPset_Drawing", "TargetView")
|
||||
@@ -1548,12 +1552,10 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
context=context,
|
||||
ifc_representation_class=None,
|
||||
)
|
||||
if representation := ifcopenshell.util.representation.get_representation(element, context):
|
||||
cls.reload_representation(obj=obj, representation=representation)
|
||||
bpy.data.curves.remove(data)
|
||||
|
||||
assert element
|
||||
representation = ifcopenshell.util.representation.get_representation(element, context)
|
||||
assert representation
|
||||
obj.data["ios_edges_item_ids"] = (representation.Items[0].id(),)
|
||||
return element
|
||||
|
||||
@classmethod
|
||||
@@ -1641,6 +1643,8 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
context=context,
|
||||
ifc_representation_class=None,
|
||||
)
|
||||
if representation := ifcopenshell.util.representation.get_representation(element, context):
|
||||
cls.reload_representation(obj=obj, representation=representation)
|
||||
return element
|
||||
|
||||
@classmethod
|
||||
@@ -1678,6 +1682,8 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
context=context,
|
||||
ifc_representation_class=None,
|
||||
)
|
||||
if representation := ifcopenshell.util.representation.get_representation(element, context):
|
||||
cls.reload_representation(obj=obj, representation=representation)
|
||||
return element
|
||||
|
||||
@classmethod
|
||||
@@ -1778,6 +1784,8 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
context=context,
|
||||
ifc_representation_class=None,
|
||||
)
|
||||
if representation := ifcopenshell.util.representation.get_representation(element, context):
|
||||
cls.reload_representation(obj=obj, representation=representation)
|
||||
return element
|
||||
|
||||
@classmethod
|
||||
@@ -1834,9 +1842,17 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
return bool(a_tree.overlap(b_tree))
|
||||
|
||||
@classmethod
|
||||
def replace_text_literal_variables(cls, text: str, product: Optional[ifcopenshell.entity_instance] = None) -> str:
|
||||
def replace_text_literal_variables(
|
||||
cls,
|
||||
text: str,
|
||||
product: Optional[ifcopenshell.entity_instance] = None,
|
||||
reverse_list: bool = False,
|
||||
list_separator: str = ", ",
|
||||
) -> str:
|
||||
if not product:
|
||||
return text
|
||||
if list_separator:
|
||||
list_separator = list_separator.encode().decode("unicode_escape")
|
||||
|
||||
for command in re.findall("``.*?``", text):
|
||||
original_command = command
|
||||
@@ -1845,13 +1861,14 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
value = '"' + str(value).replace('"', '\\"') + '"'
|
||||
command = command.replace(variable, value)
|
||||
text = text.replace(original_command, ifcopenshell.util.selector.format(command[2:-2]))
|
||||
|
||||
for variable in re.findall("{{.*?}}", text):
|
||||
value = ifcopenshell.util.selector.get_element_value(product, variable[2:-2])
|
||||
if isinstance(value, (list, tuple)):
|
||||
value = ", ".join(str(v) for v in value)
|
||||
if reverse_list:
|
||||
value = list_separator.join(str(v) for v in reversed(value))
|
||||
else:
|
||||
value = list_separator.join(str(v) for v in value)
|
||||
text = text.replace(variable, str(value))
|
||||
|
||||
return text
|
||||
|
||||
@classmethod
|
||||
@@ -2172,12 +2189,10 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
|
||||
@classmethod
|
||||
def activate_drawing(cls, camera: bpy.types.Object) -> None:
|
||||
selected_objects_before = bpy.context.selected_objects
|
||||
non_ifc_objects_hide = {o: o.hide_get() for o in bpy.context.view_layer.objects if not tool.Ifc.get_entity(o)}
|
||||
|
||||
# Sync viewport objects visibility with selectors from EPset_Drawing/Include and /Exclude
|
||||
drawing = tool.Ifc.get_entity(camera)
|
||||
assert drawing and isinstance(camera.data, bpy.types.Camera)
|
||||
cls.import_annotations_in_group(cls.get_drawing_group(drawing))
|
||||
|
||||
filtered_elements = cls.get_drawing_elements(drawing) | cls.get_drawing_spaces(drawing)
|
||||
filtered_elements.add(drawing)
|
||||
@@ -2185,13 +2200,7 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
target_view = cls.get_drawing_target_view(drawing)
|
||||
subcontexts = cls.get_drawing_subcontexts(target_view)
|
||||
|
||||
# Hide everything first, then selectively show. This is significantly faster.
|
||||
with bpy.context.temp_override(**tool.Blender.get_viewport_context()):
|
||||
bpy.ops.object.hide_view_set(unselected=False)
|
||||
bpy.ops.object.hide_view_set(unselected=True)
|
||||
|
||||
# switch representations and hide elements without representations
|
||||
element_obj_names = set()
|
||||
# Switch representations
|
||||
for element in filtered_elements:
|
||||
obj = tool.Ifc.get_object(element)
|
||||
if not obj:
|
||||
@@ -2215,34 +2224,22 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=priority_representation,
|
||||
should_reload=False,
|
||||
is_global=True,
|
||||
should_sync_changes_first=True,
|
||||
)
|
||||
has_context = True
|
||||
break
|
||||
|
||||
# Don't hide IfcAnnotations or Aggregates as some of them might exist without representations
|
||||
if has_context or element.is_a("IfcAnnotation") or element.IsDecomposedBy:
|
||||
element_obj_names.add(obj.name)
|
||||
|
||||
# Note that render visibility is only set on drawing generation time for speed.
|
||||
visible_objects = []
|
||||
for obj in bpy.context.view_layer.objects:
|
||||
if obj.name in element_obj_names:
|
||||
obj.hide_set(False)
|
||||
obj.hide_render = False
|
||||
continue
|
||||
if (hide := non_ifc_objects_hide.get(obj)) is not None:
|
||||
obj.hide_set(hide)
|
||||
obj.hide_render = hide
|
||||
if element := tool.Ifc.get_entity(obj):
|
||||
if element in filtered_elements:
|
||||
visible_objects.append(obj)
|
||||
else:
|
||||
obj.hide_render = True
|
||||
if obj.hide_get() is False:
|
||||
visible_objects.append(obj)
|
||||
tool.Blender.isolate_objects(visible_objects)
|
||||
|
||||
cls.import_camera_props(drawing, camera.data)
|
||||
|
||||
for obj in selected_objects_before:
|
||||
obj.select_set(True)
|
||||
|
||||
@classmethod
|
||||
def get_elements_in_camera_view(
|
||||
cls, camera: bpy.types.Object, objs: list[bpy.types.Object]
|
||||
|
||||
@@ -1350,9 +1350,6 @@ class Geometry(bonsai.core.tool.Geometry):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
apply_openings=True,
|
||||
)
|
||||
|
||||
@@ -1388,9 +1385,6 @@ class Geometry(bonsai.core.tool.Geometry):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=new_representation,
|
||||
should_reload=False,
|
||||
should_sync_changes_first=False,
|
||||
is_global=True,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -248,7 +248,7 @@ class Ifc(bonsai.core.tool.Ifc):
|
||||
|
||||
Marking object as edited is an optimization mechanism - instead of saving
|
||||
changed geometry to IFC, we mark it as changed and then it's saved later
|
||||
(typically during project save or switch_representation(should_sync_changes_first=True)).
|
||||
(typically during project save).
|
||||
|
||||
Other caveat of using edited objects is that it won't have an effect for objects with openings,
|
||||
since we can't deduce non-openings representation from edited representation with openings.
|
||||
|
||||
@@ -1091,9 +1091,6 @@ class Model(bonsai.core.tool.Model):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=new_representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@@ -1306,9 +1303,6 @@ class Model(bonsai.core.tool.Model):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=body,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@@ -1652,9 +1646,6 @@ class Model(bonsai.core.tool.Model):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
apply_openings=True,
|
||||
)
|
||||
|
||||
@@ -2468,9 +2459,6 @@ class Model(bonsai.core.tool.Model):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=rep,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
tool.Geometry.record_object_materials(obj)
|
||||
|
||||
|
||||
@@ -155,7 +155,6 @@ class Raycast(bonsai.core.tool.Raycast):
|
||||
@classmethod
|
||||
def point_is_visible_in_clipping_plane(cls, vertex):
|
||||
normals = tool.Project.get_clipping_planes_normals()
|
||||
print("NORMALS", normals)
|
||||
if not normals:
|
||||
return True
|
||||
for normal in normals:
|
||||
|
||||
@@ -326,9 +326,6 @@ class Root(bonsai.core.tool.Root):
|
||||
tool.Geometry,
|
||||
obj=voided_obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -512,14 +512,12 @@ class Sequence(bonsai.core.tool.Sequence):
|
||||
@classmethod
|
||||
def get_task_inputs(cls, task: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
|
||||
props = cls.get_work_schedule_props()
|
||||
is_deep = props.show_nested_inputs
|
||||
return ifcopenshell.util.sequence.get_task_inputs(task, is_deep)
|
||||
return ifcopenshell.util.sequence.get_task_inputs(task, is_recursive=props.show_nested_inputs)
|
||||
|
||||
@classmethod
|
||||
def get_task_outputs(cls, task: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
|
||||
props = cls.get_work_schedule_props()
|
||||
is_deep = props.show_nested_outputs
|
||||
return ifcopenshell.util.sequence.get_task_outputs(task, is_deep)
|
||||
return ifcopenshell.util.sequence.get_task_outputs(task, is_recursive=props.show_nested_outputs)
|
||||
|
||||
@classmethod
|
||||
def are_entities_same_class(cls, entities: list[ifcopenshell.entity_instance]) -> bool:
|
||||
@@ -540,8 +538,7 @@ class Sequence(bonsai.core.tool.Sequence):
|
||||
if not task:
|
||||
return
|
||||
props = cls.get_work_schedule_props()
|
||||
is_deep = props.show_nested_resources
|
||||
return ifcopenshell.util.sequence.get_task_resources(task, is_deep)
|
||||
return ifcopenshell.util.sequence.get_task_resources(task, props.show_nested_resources)
|
||||
|
||||
@classmethod
|
||||
def load_task_inputs(cls, inputs: list[ifcopenshell.entity_instance]) -> None:
|
||||
@@ -1582,7 +1579,7 @@ class Sequence(bonsai.core.tool.Sequence):
|
||||
@classmethod
|
||||
def create_new_task_json(cls, task, json, type_map=None, baseline_schedule=None):
|
||||
task_time = task.TaskTime
|
||||
resources = ifcopenshell.util.sequence.get_task_resources(task, is_deep=False)
|
||||
resources = ifcopenshell.util.sequence.get_task_resources(task, is_recursive=False)
|
||||
|
||||
string_resources = ""
|
||||
resources_usage = ""
|
||||
@@ -1689,8 +1686,8 @@ class Sequence(bonsai.core.tool.Sequence):
|
||||
) -> list[ifcopenshell.entity_instance]:
|
||||
products = []
|
||||
for task in ifcopenshell.util.sequence.get_root_tasks(work_schedule):
|
||||
products.extend(ifcopenshell.util.sequence.get_task_inputs(task, is_deep=True))
|
||||
products.extend(ifcopenshell.util.sequence.get_task_outputs(task, is_deep=True))
|
||||
products.extend(ifcopenshell.util.sequence.get_task_inputs(task, is_recursive=True))
|
||||
products.extend(ifcopenshell.util.sequence.get_task_outputs(task, is_recursive=True))
|
||||
return products
|
||||
|
||||
@classmethod
|
||||
@@ -1789,6 +1786,16 @@ class Sequence(bonsai.core.tool.Sequence):
|
||||
cls.load_task_outputs(outputs)
|
||||
cls.load_task_resources(task)
|
||||
|
||||
@classmethod
|
||||
def select_active_task_elements(cls, task):
|
||||
if not task:
|
||||
return
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
props = cls.get_work_schedule_props()
|
||||
for element in cls.get_task_inputs(task) | cls.get_task_outputs(task):
|
||||
if obj := tool.Ifc.get_object(element):
|
||||
obj.select_set(True)
|
||||
|
||||
@classmethod
|
||||
def refresh_task_resources(cls):
|
||||
task = cls.get_highlighted_task()
|
||||
|
||||
@@ -175,7 +175,6 @@ class Spatial(bonsai.core.tool.Spatial):
|
||||
|
||||
@classmethod
|
||||
def select_products(cls, products: Iterable[ifcopenshell.entity_instance], unhide: bool = False) -> None:
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
for product in products:
|
||||
obj = tool.Ifc.get_object(product)
|
||||
if obj and bpy.context.view_layer.objects.get(obj.name):
|
||||
@@ -1198,11 +1197,30 @@ class Spatial(bonsai.core.tool.Spatial):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=body,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def set_space_visibility(cls, is_visible: bool) -> None:
|
||||
if tool.Ifc.get().schema == "IFC2X3":
|
||||
elements = tool.Ifc.get().by_type("IfcSpatialStructureElement")
|
||||
else:
|
||||
elements = tool.Ifc.get().by_type("IfcSpatialElement")
|
||||
for element in elements:
|
||||
if obj := tool.Ifc.get_object(element):
|
||||
if obj.hide_viewport is True and is_visible:
|
||||
obj.hide_viewport = False
|
||||
elif obj.hide_viewport is False and not is_visible:
|
||||
obj.hide_viewport = True
|
||||
|
||||
@classmethod
|
||||
def set_grid_visibility(cls, is_visible: bool) -> None:
|
||||
for element in tool.Ifc.get().by_type("IfcGrid") + tool.Ifc.get().by_type("IfcGridAxis"):
|
||||
if obj := tool.Ifc.get_object(element):
|
||||
if obj.hide_viewport is True and is_visible:
|
||||
obj.hide_viewport = False
|
||||
elif obj.hide_viewport is False and not is_visible:
|
||||
obj.hide_viewport = True
|
||||
|
||||
@classmethod
|
||||
def toggle_spaces_visibility_wired_and_textured(cls, spaces: list[ifcopenshell.entity_instance]) -> None:
|
||||
first_obj = tool.Ifc.get_object(spaces[0])
|
||||
|
||||
@@ -131,15 +131,10 @@ class Type(bonsai.core.tool.Type):
|
||||
cls,
|
||||
obj: bpy.types.Object,
|
||||
representation: ifcopenshell.entity_instance,
|
||||
should_reload: bool = False,
|
||||
is_global: bool = False,
|
||||
) -> None:
|
||||
return bonsai.core.geometry.switch_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=should_reload,
|
||||
is_global=is_global,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
@@ -41,6 +41,7 @@ import socketio
|
||||
import threading
|
||||
import queue
|
||||
import json
|
||||
import platformdirs
|
||||
from time import sleep
|
||||
from pathlib import Path
|
||||
import bonsai.core.sequence
|
||||
@@ -65,6 +66,13 @@ IFC_TASK_ATTRIBUTE_MAP = {
|
||||
}
|
||||
|
||||
|
||||
def get_pid_file_path():
|
||||
"""Get the path to the PID file in the user's cache directory."""
|
||||
cache_dir = Path(platformdirs.user_cache_dir("bonsai"))
|
||||
cache_dir.mkdir(parents=True, exist_ok=True)
|
||||
return cache_dir / "running_pid.json"
|
||||
|
||||
|
||||
class Web(bonsai.core.tool.Web):
|
||||
@classmethod
|
||||
def get_web_props(cls) -> WebProperties:
|
||||
@@ -213,7 +221,7 @@ class Web(bonsai.core.tool.Web):
|
||||
cls.disconnect_websocket_server()
|
||||
|
||||
# sleep(0.5)
|
||||
pid_file = tool.Blender.get_data_dir_path("webui") / "running_pid.json"
|
||||
pid_file = get_pid_file_path()
|
||||
with open(pid_file, "r") as f:
|
||||
pids = json.load(f)
|
||||
|
||||
@@ -250,7 +258,7 @@ class Web(bonsai.core.tool.Web):
|
||||
while True:
|
||||
if time.time() - start > max_time:
|
||||
return False
|
||||
pid_file = tool.Blender.get_data_dir_path("webui") / "running_pid.json"
|
||||
pid_file = get_pid_file_path()
|
||||
try:
|
||||
with open(pid_file, "r") as f:
|
||||
data = json.load(f)
|
||||
|
||||
@@ -174,6 +174,14 @@ If there are changes to the IfcOpenShell binaries, you may replace the two
|
||||
``*ifcopenshell_wrapper*`` files with new ones downloaded from the automated
|
||||
`IfcOpenShell builds directory <https://builds.ifcopenshell.org/>`__.
|
||||
|
||||
If you wish to run the IfcTester webapp, you will also need to have ``npm`` and
|
||||
setup IfcTester:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
cd src/ifctester
|
||||
make webapp-prepare
|
||||
|
||||
.. seealso::
|
||||
|
||||
There is a `useful Blender Addon
|
||||
|
||||
@@ -37,7 +37,7 @@ class TestEditText:
|
||||
def test_run(self, drawing):
|
||||
drawing.synchronise_ifc_and_text_attributes("obj").should_be_called()
|
||||
drawing.update_text_size_pset("obj").should_be_called()
|
||||
drawing.update_newline_at_and_symbol("obj").should_be_called()
|
||||
drawing.update_text_annotation_properties("obj").should_be_called()
|
||||
drawing.disable_editing_text("obj").should_be_called()
|
||||
subject.edit_text(drawing, obj="obj")
|
||||
|
||||
|
||||
@@ -203,9 +203,6 @@ class TestSwitchRepresentation:
|
||||
geometry,
|
||||
obj="obj",
|
||||
representation="mapped_rep",
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=True,
|
||||
apply_openings=True,
|
||||
)
|
||||
|
||||
@@ -222,9 +219,6 @@ class TestSwitchRepresentation:
|
||||
geometry,
|
||||
obj="obj",
|
||||
representation="mapped_rep",
|
||||
should_reload=False,
|
||||
is_global=False,
|
||||
should_sync_changes_first=True,
|
||||
)
|
||||
|
||||
def test_not_switching_if_an_updated_representation_is_the_same_one_we_were_going_to_switch_to(self, ifc, geometry):
|
||||
@@ -238,9 +232,6 @@ class TestSwitchRepresentation:
|
||||
geometry,
|
||||
obj="obj",
|
||||
representation="mapped_rep",
|
||||
should_reload=False,
|
||||
is_global=False,
|
||||
should_sync_changes_first=True,
|
||||
)
|
||||
|
||||
|
||||
@@ -248,7 +239,7 @@ class TestGetRepresentationIfcParameters:
|
||||
def test_run(self, geometry):
|
||||
geometry.get_object_data("obj").should_be_called().will_return("data")
|
||||
geometry.import_representation_parameters("data").should_be_called()
|
||||
subject.get_representation_ifc_parameters(geometry, obj="obj", should_sync_changes_first=False)
|
||||
subject.get_representation_ifc_parameters(geometry, obj="obj")
|
||||
|
||||
|
||||
class TestRemoveRepresentation:
|
||||
|
||||
@@ -36,6 +36,7 @@ from typing_extensions import NotRequired
|
||||
class CsvHeader(TypedDict):
|
||||
Index: int
|
||||
Name: int
|
||||
Description: NotRequired[str]
|
||||
Unit: int
|
||||
Identification: NotRequired[int]
|
||||
Value: NotRequired[int]
|
||||
@@ -80,6 +81,7 @@ class CostItem(TypedDict):
|
||||
|
||||
Identification: Union[str, None]
|
||||
Name: Union[str, None]
|
||||
Description: Union[str, None]
|
||||
Unit: Union[str, None]
|
||||
CostValues: Union[dict[str, float], float, None]
|
||||
|
||||
@@ -183,15 +185,6 @@ class Csv2Ifc:
|
||||
available_fields = set(self.headers.keys())
|
||||
missing_fields = mandatory_fields - available_fields
|
||||
|
||||
# TODO: 25-04-17 Deprecated 'Description' argument, should fully remove later.
|
||||
if missing_fields and "Name" in missing_fields and "Description" in available_fields:
|
||||
print(
|
||||
"WARNING. 'Description' column is deprecated and should be renamed to 'Name'. It will be deprecated soon completely."
|
||||
)
|
||||
self.headers["Name"] = self.headers["Description"]
|
||||
del self.headers["Description"]
|
||||
missing_fields.remove("Name")
|
||||
|
||||
if missing_fields:
|
||||
raise Exception(f"Missing mandatory fields in CSV header: {', '.join(missing_fields)}")
|
||||
|
||||
@@ -212,6 +205,7 @@ class Csv2Ifc:
|
||||
|
||||
def get_row_cost_data(self, row: list[str]) -> CostItem:
|
||||
name = row[self.headers["Name"]]
|
||||
description = row[self.headers["Description"]] if "Description" in self.headers else None
|
||||
identification = row[self.headers["Identification"]] if "Identification" in self.headers else None
|
||||
quantity = row[(self.headers["Quantity"])] if "Quantity" in self.headers else None
|
||||
unit = row[self.headers["Unit"]]
|
||||
@@ -241,6 +235,7 @@ class Csv2Ifc:
|
||||
return {
|
||||
"Identification": str(identification) if identification else None,
|
||||
"Name": str(name) if name else None,
|
||||
"Description": str(description) if description else None,
|
||||
"Unit": str(unit) if unit else None,
|
||||
"CostValues": cost_values,
|
||||
"Quantity": float(quantity) if quantity else None,
|
||||
@@ -276,6 +271,7 @@ class Csv2Ifc:
|
||||
cost_item["ifc"] = ifcopenshell.api.cost.add_cost_item(self.file, cost_item=parent)
|
||||
|
||||
cost_item["ifc"].Name = cost_item["Name"]
|
||||
cost_item["ifc"].Description = cost_item["Description"]
|
||||
cost_item["ifc"].Identification = cost_item["Identification"]
|
||||
|
||||
cost_values = cost_item["CostValues"]
|
||||
|
||||
@@ -7,6 +7,14 @@
|
||||
|
||||
using namespace ifcopenshell::geometry;
|
||||
|
||||
const char* ifcopenshell::not_implemented_error::what() const noexcept {
|
||||
return "Not implemented.";
|
||||
}
|
||||
|
||||
const char* ifcopenshell::not_supported_error::what() const noexcept {
|
||||
return "Not supported.";
|
||||
}
|
||||
|
||||
bool ifcopenshell::geometry::kernels::AbstractKernel::convert(const taxonomy::ptr item, IfcGeom::ConversionResults& results) {
|
||||
if (settings_.get<settings::CacheShapes>().get()) {
|
||||
auto it = cache_.find(item);
|
||||
|
||||
@@ -36,21 +36,26 @@ inline static bool ALMOST_THE_SAME(const T& a, const T& b, double tolerance = AL
|
||||
}
|
||||
|
||||
namespace ifcopenshell {
|
||||
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4275)
|
||||
#endif
|
||||
|
||||
class IFC_GEOM_API not_implemented_error : public std::exception {
|
||||
public:
|
||||
const char* what() const noexcept override {
|
||||
return "Not implemented.";
|
||||
}
|
||||
const char* what() const noexcept override;
|
||||
};
|
||||
|
||||
class IFC_GEOM_API not_supported_error : public std::exception {
|
||||
public:
|
||||
const char* what() const noexcept override {
|
||||
return "Not supported.";
|
||||
}
|
||||
const char* what() const noexcept override;
|
||||
};
|
||||
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
namespace geometry { namespace kernels {
|
||||
|
||||
class IFC_GEOM_API AbstractKernel {
|
||||
|
||||
@@ -282,6 +282,7 @@ namespace IfcGeom {
|
||||
virtual ConversionResultShape* halfspaces() = 0;
|
||||
virtual ConversionResultShape* box() = 0;
|
||||
virtual ConversionResultShape* solid() = 0;
|
||||
virtual ConversionResultShape* wrap_in_compound() = 0;
|
||||
|
||||
virtual std::vector<ConversionResultShape*> vertices() = 0;
|
||||
virtual std::vector<ConversionResultShape*> edges() = 0;
|
||||
|
||||
@@ -94,3 +94,7 @@ std::istream& ifcopenshell::geometry::settings::operator>>(std::istream& in, Tri
|
||||
}
|
||||
return in;
|
||||
}
|
||||
|
||||
IfcGeom::geometry_exception::~geometry_exception() = default;
|
||||
|
||||
IfcGeom::too_many_faces_exception::~too_many_faces_exception() = default;
|
||||
|
||||
@@ -84,8 +84,9 @@ namespace ifcopenshell {
|
||||
}
|
||||
if constexpr (HasDefault<Derived>()) {
|
||||
return Derived::defaultvalue;
|
||||
} else {
|
||||
throw std::runtime_error("Setting not set");
|
||||
}
|
||||
throw std::runtime_error("Setting not set");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -654,6 +655,12 @@ namespace ifcopenshell {
|
||||
|
||||
// @todo find a place
|
||||
namespace IfcGeom {
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4275)
|
||||
#endif
|
||||
|
||||
class IFC_GEOM_API geometry_exception : public std::runtime_error {
|
||||
protected:
|
||||
std::string message;
|
||||
@@ -661,12 +668,19 @@ namespace IfcGeom {
|
||||
geometry_exception(const std::string& m)
|
||||
: std::runtime_error(m)
|
||||
{}
|
||||
~geometry_exception() override;
|
||||
};
|
||||
|
||||
class IFC_GEOM_API too_many_faces_exception : public geometry_exception {
|
||||
public:
|
||||
too_many_faces_exception()
|
||||
: geometry_exception("Too many faces for operation") {}
|
||||
~too_many_faces_exception() override;
|
||||
};
|
||||
}
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@@ -68,7 +68,7 @@ IfcGeom::ConversionResultShape* IfcGeom::Representation::BRep::as_compound(bool
|
||||
delete accum;
|
||||
accum = n;
|
||||
} else {
|
||||
accum = s;
|
||||
accum = s->wrap_in_compound();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -610,6 +610,11 @@ ConversionResultShape * ifcopenshell::geometry::CgalShape::box()
|
||||
throw std::runtime_error("Not implemented");
|
||||
}
|
||||
|
||||
ConversionResultShape* ifcopenshell::geometry::CgalShape::wrap_in_compound()
|
||||
{
|
||||
return new CgalShape(poly(), convex_tag_);
|
||||
}
|
||||
|
||||
std::vector<ConversionResultShape*> ifcopenshell::geometry::CgalShape::vertices()
|
||||
{
|
||||
// @todo this is ridiculous
|
||||
@@ -1001,4 +1006,10 @@ void ifcopenshell::geometry::CgalShapeHalfSpaceDecomposition::map(const std::vec
|
||||
shape_ = std::move(nw);
|
||||
}
|
||||
|
||||
|
||||
ConversionResultShape* ifcopenshell::geometry::CgalShapeHalfSpaceDecomposition::wrap_in_compound()
|
||||
{
|
||||
throw std::runtime_error("Not implemented");
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -244,6 +244,7 @@ namespace ifcopenshell { namespace geometry {
|
||||
virtual ConversionResultShape* halfspaces();
|
||||
virtual ConversionResultShape* solid();
|
||||
virtual ConversionResultShape* box();
|
||||
virtual ConversionResultShape* wrap_in_compound();
|
||||
|
||||
virtual std::vector<ConversionResultShape*> vertices();
|
||||
virtual std::vector<ConversionResultShape*> edges();
|
||||
@@ -312,6 +313,7 @@ namespace ifcopenshell { namespace geometry {
|
||||
virtual ConversionResultShape* halfspaces();
|
||||
virtual ConversionResultShape* solid();
|
||||
virtual ConversionResultShape* box();
|
||||
virtual ConversionResultShape* wrap_in_compound();
|
||||
|
||||
virtual std::vector<ConversionResultShape*> vertices();
|
||||
virtual std::vector<ConversionResultShape*> edges();
|
||||
|
||||
@@ -484,6 +484,15 @@ ConversionResultShape * ifcopenshell::geometry::OpenCascadeShape::box()
|
||||
throw std::runtime_error("Not implemented");
|
||||
}
|
||||
|
||||
ConversionResultShape* ifcopenshell::geometry::OpenCascadeShape::wrap_in_compound()
|
||||
{
|
||||
TopoDS_Compound compound;
|
||||
BRep_Builder builder;
|
||||
builder.MakeCompound(compound);
|
||||
builder.Add(compound, shape_);
|
||||
return new OpenCascadeShape(std::move(compound));
|
||||
}
|
||||
|
||||
std::vector<ConversionResultShape*> ifcopenshell::geometry::OpenCascadeShape::vertices()
|
||||
{
|
||||
TopTools_IndexedMapOfShape map;
|
||||
@@ -556,7 +565,9 @@ ConversionResultShape* ifcopenshell::geometry::OpenCascadeShape::concat(Conversi
|
||||
auto& left = shape_;
|
||||
auto& right = ((ifcopenshell::geometry::OpenCascadeShape*)other)->shape_;
|
||||
|
||||
if (left.ShapeType() == TopAbs_COMPOUND) {
|
||||
// This reads a bit strange, but we want to specifically avoid compounds of faces that are
|
||||
// the result of shell instances that are not sewn into a shell (yet).
|
||||
if (left.ShapeType() == TopAbs_COMPOUND && !IfcGeom::util::is_compound_of_faces(left)) {
|
||||
compound = TopoDS::Compound(left);
|
||||
} else {
|
||||
builder.MakeCompound(compound);
|
||||
|
||||
@@ -90,6 +90,7 @@ namespace ifcopenshell {
|
||||
virtual ConversionResultShape* halfspaces();
|
||||
virtual ConversionResultShape* solid();
|
||||
virtual ConversionResultShape* box();
|
||||
virtual ConversionResultShape* wrap_in_compound();
|
||||
|
||||
virtual std::vector<ConversionResultShape*> vertices();
|
||||
virtual std::vector<ConversionResultShape*> edges();
|
||||
|
||||
@@ -607,7 +607,7 @@ gp_Pnt IfcGeom::util::point_above_plane(const gp_Pln& pln, bool agree) {
|
||||
}
|
||||
}
|
||||
|
||||
bool IfcGeom::util::is_compound(const TopoDS_Shape& shape) {
|
||||
bool IfcGeom::util::is_compound_of_faces(const TopoDS_Shape& shape) {
|
||||
bool has_solids = TopExp_Explorer(shape, TopAbs_SOLID).More() != 0;
|
||||
bool has_shells = TopExp_Explorer(shape, TopAbs_SHELL).More() != 0;
|
||||
bool has_compounds = TopExp_Explorer(shape, TopAbs_COMPOUND).More() != 0;
|
||||
|
||||
@@ -51,7 +51,7 @@ namespace IfcGeom {
|
||||
IFC_GEOMLIBRARY_API bool create_solid_from_compound(const TopoDS_Shape& compound, TopoDS_Shape& solid, double tol);
|
||||
IFC_GEOMLIBRARY_API bool shape_to_face_list(const TopoDS_Shape& s, TopTools_ListOfShape& li);
|
||||
IFC_GEOMLIBRARY_API bool create_solid_from_faces(const TopTools_ListOfShape& face_list, TopoDS_Shape& solid, double tol, bool force_sewing = false);
|
||||
IFC_GEOMLIBRARY_API bool is_compound(const TopoDS_Shape& shape);
|
||||
IFC_GEOMLIBRARY_API bool is_compound_of_faces(const TopoDS_Shape& shape);
|
||||
IFC_GEOMLIBRARY_API bool is_convex(const TopoDS_Wire& wire, double tol);
|
||||
IFC_GEOMLIBRARY_API TopoDS_Shape halfspace_from_plane(const gp_Pln& pln, const gp_Pnt& cent);
|
||||
IFC_GEOMLIBRARY_API gp_Pln plane_from_face(const TopoDS_Face& face);
|
||||
|
||||
@@ -1431,7 +1431,7 @@ bool IfcGeom::util::boolean_operation(const boolean_settings& settings, const To
|
||||
}
|
||||
|
||||
TopoDS_Shape IfcGeom::util::ensure_fit_for_subtraction(const TopoDS_Shape& shape, double tol) {
|
||||
const bool is_comp = is_compound(shape);
|
||||
const bool is_comp = is_compound_of_faces(shape);
|
||||
if (!is_comp) {
|
||||
return shape;
|
||||
}
|
||||
|
||||
@@ -223,6 +223,8 @@ namespace {
|
||||
};
|
||||
}
|
||||
|
||||
ifcopenshell::geometry::taxonomy::topology_error::~topology_error() = default;
|
||||
|
||||
bool ifcopenshell::geometry::taxonomy::less(item::const_ptr a, item::const_ptr b) {
|
||||
if (a == b) {
|
||||
return false;
|
||||
|
||||
@@ -85,14 +85,24 @@ typedef std::uniqe_ptr<const item> ptr;
|
||||
#define DECLARE_PTR(item) \
|
||||
typedef item* ptr; \
|
||||
typedef item const* ptr;
|
||||
#endif
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4275)
|
||||
#endif
|
||||
|
||||
class IFC_GEOM_API topology_error : public std::runtime_error {
|
||||
public:
|
||||
topology_error() : std::runtime_error("Generic topology error") {}
|
||||
topology_error(const char* const s) : std::runtime_error(s) {}
|
||||
~topology_error() override;
|
||||
};
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
// Implementer note: If you add a new item type, be sure to do the following
|
||||
// 1) Add a new kind to this list
|
||||
// 2) Update the values array used by kind_to_string()
|
||||
|
||||
@@ -143,7 +143,7 @@ class Usecase:
|
||||
return next(e for e in self.file.get_inverse(self.task_time) if e.is_a("IfcTask"))
|
||||
|
||||
def handle_resource_calculation(self):
|
||||
resources = ifcopenshell.util.sequence.get_task_resources(self.task, is_deep=False)
|
||||
resources = ifcopenshell.util.sequence.get_task_resources(self.task, is_recursive=False)
|
||||
for resource in resources:
|
||||
if ifcopenshell.util.constraint.is_attribute_locked(resource, "Usage.ScheduleWork"):
|
||||
ifcopenshell.api.resource.calculate_resource_usage(self.file, resource=resource)
|
||||
|
||||
@@ -18,14 +18,18 @@
|
||||
|
||||
"""2D drawing generation and serialisation"""
|
||||
|
||||
from functools import reduce
|
||||
import itertools
|
||||
import math
|
||||
import json
|
||||
import functools
|
||||
import re
|
||||
import operator
|
||||
import warnings
|
||||
import shapely
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.geom
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.selector
|
||||
|
||||
from xml.dom.minidom import parseString
|
||||
from dataclasses import dataclass, fields, field
|
||||
@@ -53,8 +57,9 @@ class draw_settings:
|
||||
door_arcs: bool = False
|
||||
subtract_before_hlr: bool = False
|
||||
cache: bool = False
|
||||
css: bool = True
|
||||
css: str = ""
|
||||
storey_heights: str = "none"
|
||||
storey_filter: str = ""
|
||||
include_entities: str = ""
|
||||
exclude_entities: str = "IfcOpeningElement"
|
||||
drawing_guid: str = field(
|
||||
@@ -75,9 +80,24 @@ class draw_settings:
|
||||
include_curves: bool = False
|
||||
unify_inputs: bool = True
|
||||
arrange_spaces: bool = False
|
||||
arrange_zones: bool = False
|
||||
zone_filter: str = ""
|
||||
zone_label: str = ""
|
||||
mirror_y: bool = False
|
||||
|
||||
|
||||
class type_unsafe_value:
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
|
||||
def __eq__(self, other):
|
||||
try:
|
||||
v = type(other)(self.value)
|
||||
except:
|
||||
return False
|
||||
return v == other
|
||||
|
||||
|
||||
def main(
|
||||
settings: draw_settings,
|
||||
files: list[ifcopenshell.file],
|
||||
@@ -86,6 +106,13 @@ def main(
|
||||
progress_function: Callable = DO_NOTHING,
|
||||
):
|
||||
|
||||
def by_guid(g):
|
||||
for f in files:
|
||||
try:
|
||||
return f.by_guid(g)
|
||||
except:
|
||||
pass
|
||||
|
||||
geom_settings = ifcopenshell.geom.settings(
|
||||
# when not doing booleans, proper solids from shells isn't a requirement
|
||||
REORIENT_SHELLS=settings.subtract_before_hlr,
|
||||
@@ -106,11 +133,26 @@ def main(
|
||||
elif settings.exclude_entities:
|
||||
iterator_kwargs["exclude"] = settings.exclude_entities.split(",")
|
||||
|
||||
def yield_parents(el):
|
||||
yield el
|
||||
if par := ifcopenshell.util.element.get_parent(el):
|
||||
yield from yield_parents(par)
|
||||
|
||||
def has_selected_parent(el):
|
||||
return any(settings.storey_filter in (x.Name or x.GlobalId) for x in yield_parents(el))
|
||||
|
||||
def create_iter(f):
|
||||
if settings.storey_filter and iterator_kwargs.get("include"):
|
||||
iterator_kwargs["include"] = list(
|
||||
filter(has_selected_parent, sum((f.by_type(x) for x in iterator_kwargs["include"]), []))
|
||||
)
|
||||
return ifcopenshell.geom.iterator(geom_settings, f, **iterator_kwargs)
|
||||
|
||||
# We have to keep the iterator in memory because otherwise
|
||||
# the styles are cleared up.
|
||||
iterators = list(
|
||||
map(
|
||||
functools.partial(ifcopenshell.geom.iterator, geom_settings, **iterator_kwargs),
|
||||
create_iter,
|
||||
files,
|
||||
)
|
||||
)
|
||||
@@ -135,14 +177,7 @@ def main(
|
||||
# it's up to user to keep them valid for the provided projects.
|
||||
if settings.drawing_guid or settings.drawing_object_type:
|
||||
if settings.drawing_guid:
|
||||
found_guid = False
|
||||
for f in files:
|
||||
try:
|
||||
f.by_guid(settings.drawing_guid)
|
||||
found_guid = True
|
||||
except:
|
||||
pass
|
||||
if not found_guid:
|
||||
if not by_guid(settings.drawing_guid):
|
||||
raise ValueError(f"Unable to find guid {settings.drawing_guid!r}")
|
||||
sr.setElevationRefGuid(settings.drawing_guid)
|
||||
elif settings.drawing_object_type:
|
||||
@@ -163,7 +198,7 @@ def main(
|
||||
sr.setPrintSpaceNames(settings.space_names)
|
||||
sr.setPrintSpaceAreas(settings.space_areas)
|
||||
sr.setDrawDoorArcs(settings.door_arcs)
|
||||
sr.setNoCSS(not settings.css)
|
||||
sr.setNoCSS(not not settings.css)
|
||||
if settings.subtract_before_hlr:
|
||||
sr.setSubtractionSettings(W.ALWAYS)
|
||||
|
||||
@@ -421,26 +456,25 @@ def main(
|
||||
# This generally shouldn't happen
|
||||
g1.appendChild(g2)
|
||||
|
||||
if settings.arrange_spaces:
|
||||
if settings.arrange_spaces or settings.arrange_zones:
|
||||
|
||||
if settings.storey_filter:
|
||||
# delete storey groups not selected by filter
|
||||
# sometimes happens in case of elements protruding multiple stories
|
||||
# are assigned wrongly in the decomposition tree
|
||||
for rg in (g for g in yield_groups(svg1) if g.parentNode.tagName == "svg"):
|
||||
storey_guid = ifcopenshell.guid.compress(rg.attributes["id"].value.split("-", 1)[1].replace("-", ""))
|
||||
storey = by_guid(storey_guid)
|
||||
if settings.storey_filter not in (storey.Name or storey.GlobalId):
|
||||
rg.parentNode.removeChild(rg)
|
||||
|
||||
root_groups = [g for g in yield_groups(svg1) if g.parentNode.tagName == "svg"]
|
||||
ref_node = root_groups[-1].nextSibling
|
||||
parent = root_groups[0].parentNode
|
||||
zone_groups = []
|
||||
|
||||
for i in range(len(root_groups)):
|
||||
for j in range(len(root_groups)):
|
||||
if i != j:
|
||||
parent.removeChild(root_groups[j])
|
||||
for grp in root_groups:
|
||||
path_objects = [p for p in yield_groups(grp, "path") if "IfcSpace" in p.parentNode.getAttribute("class")]
|
||||
|
||||
# wasteful and looses data for unknown reasons
|
||||
# svg_contents = svg1.toxml()
|
||||
# polies = W.svg_to_polygons(svg_contents, "IfcSpace")
|
||||
|
||||
polies = [
|
||||
p.getAttribute("d")
|
||||
for p in yield_groups(svg1, "path")
|
||||
if "IfcSpace" in p.parentNode.getAttribute("class")
|
||||
]
|
||||
polies = [p.getAttribute("d") for p in path_objects]
|
||||
|
||||
def break_at_second(char, s, offset=1):
|
||||
i = s.find(char, offset)
|
||||
@@ -450,39 +484,185 @@ def main(
|
||||
warnings.warn("Polygons with holes are not supported")
|
||||
return s[0:i]
|
||||
|
||||
polies = [
|
||||
[[*map(float, s[1:].split(","))] for s in break_at_second("M", d).split(" ")[:-1]] for d in polies
|
||||
]
|
||||
def svg_to_coordlist(str):
|
||||
return [[*map(float, s[1:].split(","))] for s in break_at_second("M", str).split(" ")[:-1]]
|
||||
|
||||
polies = [svg_to_coordlist(d) for d in polies]
|
||||
|
||||
def create_poly(b):
|
||||
p = ifcopenshell.ifcopenshell_wrapper.polygon_2()
|
||||
p.boundary = b
|
||||
return p
|
||||
|
||||
polies = [create_poly(p) for p in polies]
|
||||
|
||||
def min_bound_extent(p):
|
||||
def min_bound_extent_p2(p):
|
||||
arr = numpy.array(p.boundary)
|
||||
return (arr.max(axis=0) - arr.min(axis=0)).min() > 0.5
|
||||
|
||||
polies = type(polies)(filter(min_bound_extent, polies))
|
||||
def min_bound_extent_li(p):
|
||||
arr = numpy.array(p)
|
||||
return (arr.max(axis=0) - arr.min(axis=0)).min() > 0.5
|
||||
|
||||
def polygon_to_svg_path(doc, polygon: shapely.Polygon) -> str:
|
||||
def ring_to_path(coords):
|
||||
coords = list(coords)[:-1]
|
||||
return "M " + " L ".join(f"{x},{y}" for x, y in coords) + " Z"
|
||||
|
||||
rings = [polygon.exterior, *polygon.interiors]
|
||||
d = " ".join(ring_to_path(r.coords) for r in rings)
|
||||
|
||||
path = doc.createElement("path")
|
||||
path.setAttribute("d", d)
|
||||
path.setAttribute("fill-rule", "evenodd")
|
||||
return path
|
||||
|
||||
included = list(map(min_bound_extent_li, polies))
|
||||
polies = [p for p, incl in zip(polies, included) if incl]
|
||||
|
||||
path_objects = [p for p, incl in zip(path_objects, included) if incl]
|
||||
section_polies = list(map(shapely.Polygon, polies))
|
||||
polies = [create_poly(p) for p in polies]
|
||||
|
||||
def has_relevant_zone(i):
|
||||
inst = by_guid(path_objects[i].parentNode.attributes["ifc:guid"].value)
|
||||
groups = [rel.RelatingGroup for rel in inst.HasAssignments if rel.is_a("IfcRelAssignsToGroup")]
|
||||
if settings.zone_filter:
|
||||
query, val = settings.zone_filter.rsplit("=", 1)
|
||||
# cast to type encountered in pset prop
|
||||
val = type_unsafe_value(val)
|
||||
groups = [g for g in groups if ifcopenshell.util.selector.get_element_value(g, query) == val]
|
||||
return len(groups) > 0
|
||||
|
||||
if settings.arrange_zones:
|
||||
path_objects, section_polies, polies = zip(
|
||||
*(tup for i, tup in enumerate(zip(path_objects, section_polies, polies)) if has_relevant_zone(i))
|
||||
)
|
||||
|
||||
arranged = W.arrange_polygons(polies)
|
||||
svg_data_3 = W.polygons_to_svg(arranged, False)
|
||||
dom3 = parseString(svg_data_3)
|
||||
svg3 = dom3.childNodes[0]
|
||||
g3 = next(yield_groups(svg3))
|
||||
arranged_polies = []
|
||||
for p in g3.getElementsByTagName("path"):
|
||||
p.setAttribute("style", "fill: none; stroke: black; stroke-width: 0.2")
|
||||
zone_groups.append(g3)
|
||||
parent.removeChild(root_groups[i])
|
||||
for j in range(len(root_groups)):
|
||||
parent.insertBefore(root_groups[j], ref_node)
|
||||
# arranged path
|
||||
sp = shapely.Polygon(svg_to_coordlist(p.getAttribute("d")))
|
||||
arranged_polies.append(sp)
|
||||
|
||||
# Mapping of arrange_poly_idx (stored as list) -> instance
|
||||
# Obtained by point containment of sectionpoly in arranged
|
||||
mapping = [None] * len(arranged_polies)
|
||||
for i, p in enumerate(section_polies):
|
||||
g = by_guid(path_objects[i].parentNode.attributes["ifc:guid"].value)
|
||||
contains = [ap.contains(p.representative_point()) for ap in arranged_polies]
|
||||
# arrangement, so this can generally be assumed
|
||||
if sum(contains) == 1:
|
||||
idx = contains.index(True)
|
||||
# mapping[idx].append(g)
|
||||
if mapping[idx] is None:
|
||||
mapping[idx] = g
|
||||
else:
|
||||
warnings.warn(f"Not applying {g}; overlapping with {mapping[idx]}")
|
||||
else:
|
||||
warnings.warn(f"Point {p.representative_point()} not covered by arrangement")
|
||||
|
||||
if settings.arrange_zones:
|
||||
# @todo test whether output with only arrange_spaces is unaltered for bimlegal to function
|
||||
|
||||
zone_assignment = [None] * len(arranged_polies)
|
||||
|
||||
for i, m in enumerate(mapping):
|
||||
if m is not None:
|
||||
groups = [rel.RelatingGroup for rel in m.HasAssignments if rel.is_a("IfcRelAssignsToGroup")]
|
||||
if settings.zone_filter:
|
||||
query, val = settings.zone_filter.rsplit("=", 1)
|
||||
# cast to type encountered in pset prop
|
||||
val = type_unsafe_value(val)
|
||||
groups = [
|
||||
g for g in groups if ifcopenshell.util.selector.get_element_value(g, query) == val
|
||||
]
|
||||
zone_assignment[i] = frozenset(groups)
|
||||
|
||||
all_zones = list(set(itertools.chain.from_iterable(a or () for a in zone_assignment)))
|
||||
all_zone_memberships = [
|
||||
{i for i, zns in enumerate(zone_assignment) if zns is not None and zn in zns} for zn in all_zones
|
||||
]
|
||||
|
||||
def union_find(sets):
|
||||
merged = True
|
||||
while merged:
|
||||
merged = False
|
||||
result = []
|
||||
while sets:
|
||||
first, *rest = sets
|
||||
first = set(first)
|
||||
|
||||
# merge any overlapping sets into `first`
|
||||
changed = True
|
||||
while changed:
|
||||
changed = False
|
||||
new_rest = []
|
||||
for r in rest:
|
||||
if first & r: # overlap?
|
||||
first |= r
|
||||
changed = True
|
||||
merged = True
|
||||
else:
|
||||
new_rest.append(r)
|
||||
rest = new_rest
|
||||
result.append(first)
|
||||
sets = rest
|
||||
sets[:] = result
|
||||
return sets
|
||||
|
||||
to_merge = union_find(all_zone_memberships)
|
||||
zone_groups.append(dom1.createElement("g"))
|
||||
for idx_set in to_merge:
|
||||
zone = next(iter(zone_assignment[max(idx_set)]))
|
||||
|
||||
poly = shapely.unary_union([arranged_polies[i] for i in idx_set])
|
||||
poly = poly.geoms if poly.geom_type == "MultiPolygon" else [poly]
|
||||
g = dom1.createElement("g")
|
||||
|
||||
def classes():
|
||||
yield zone.is_a()
|
||||
for propname, propval in reduce(
|
||||
operator.or_, ifcopenshell.util.element.get_psets(zone).values()
|
||||
).items():
|
||||
if propval is True:
|
||||
yield propname
|
||||
|
||||
g.setAttribute("class", " ".join(classes()))
|
||||
for p in poly:
|
||||
g.appendChild(polygon_to_svg_path(dom1, p))
|
||||
|
||||
if (lbl := settings.zone_label) and (
|
||||
val := ifcopenshell.util.selector.get_element_value(zone, lbl)
|
||||
):
|
||||
pt = p.representative_point()
|
||||
text_el = dom1.createElement("text")
|
||||
text_el.setAttribute("x", str(pt.x))
|
||||
text_el.setAttribute("y", str(pt.y))
|
||||
text_el.appendChild(dom1.createTextNode(str(val)))
|
||||
g.appendChild(text_el)
|
||||
zone_groups[-1].appendChild(g)
|
||||
else:
|
||||
zone_groups.append(g3)
|
||||
|
||||
for rg, zg in zip(root_groups, zone_groups):
|
||||
for p in rg.getElementsByTagName("path"):
|
||||
p.setAttribute("style", "fill: none; stroke: black; stroke-width: 0.05")
|
||||
if not settings.arrange_zones:
|
||||
for p in rg.getElementsByTagName("path"):
|
||||
p.setAttribute("style", "fill: none; stroke: black; stroke-width: 0.05")
|
||||
rg.appendChild(zg)
|
||||
|
||||
if settings.css:
|
||||
svg = dom1.documentElement
|
||||
style = dom1.createElement("style")
|
||||
style.setAttribute("type", "text/css")
|
||||
style.appendChild(
|
||||
dom1.createTextNode(open(settings.css[1:]).read() if settings.css.startswith("@") else settings.css)
|
||||
)
|
||||
svg.insertBefore(style, svg.firstChild)
|
||||
|
||||
data = dom1.toprettyxml()
|
||||
data = data.encode("ascii", "xmlcharrefreplace")
|
||||
|
||||
|
||||
@@ -976,9 +976,8 @@ class file:
|
||||
"""Write ifc model to file.
|
||||
|
||||
:param format: Force use of a specific format. Guessed from file name
|
||||
if None. Supported formats : .ifc, .ifcXML, .ifcZIP (equivalent to
|
||||
format=".ifc" with zipped=True) For zipped .ifcXML use
|
||||
format=".ifcXML" with zipped=True
|
||||
if None. Supported formats : .ifc, .ifcZIP (equivalent to
|
||||
format=".ifc" with zipped=True)
|
||||
:param zipped: zip the file after it is written
|
||||
|
||||
Example:
|
||||
@@ -986,10 +985,8 @@ class file:
|
||||
.. code:: python
|
||||
|
||||
model.write("path/to/model.ifc")
|
||||
model.write("path/to/model.ifcXML")
|
||||
model.write("path/to/model.ifcZIP")
|
||||
model.write("path/to/model.ifcZIP", format=".ifcXML", zipped=True)
|
||||
model.write("path/to/model.anyextension", format=".ifcXML")
|
||||
model.write("path/to/model.anyextension", format=".ifc")
|
||||
"""
|
||||
path = Path(path)
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
@@ -997,15 +994,7 @@ class file:
|
||||
if format == None:
|
||||
format = ifcopenshell.guess_format(path)
|
||||
if format == ".ifcXML":
|
||||
serializer = ifcopenshell_wrapper.XmlSerializer(self, str(path))
|
||||
serializer.finalize()
|
||||
if zipped:
|
||||
unzipped_path = path.with_suffix(format)
|
||||
path.rename(unzipped_path)
|
||||
with zipfile.ZipFile(path, "w") as zip_file:
|
||||
zip_file.write(unzipped_path, unzipped_path.name, compress_type=zipfile.ZIP_DEFLATED)
|
||||
unzipped_path.unlink()
|
||||
return
|
||||
raise NotImplementedError("Writing .ifcXML files is not supported")
|
||||
if format == ".ifcZIP":
|
||||
return self.write(path, ".ifc", zipped=True)
|
||||
self.wrapped_data.write(str(path))
|
||||
|
||||
@@ -173,7 +173,7 @@
|
||||
"Identification": "ItemReference"
|
||||
},
|
||||
"IfcMaterialProperties": {
|
||||
"Name": "Material"
|
||||
"Properties": "ExtendedProperties"
|
||||
},
|
||||
"IfcBlobTexture": {
|
||||
"Mode": "TextureType",
|
||||
|
||||
@@ -5,4 +5,5 @@
|
||||
, "IfcAnnotationSurfaceOccurrence": "IfcStyledItem"
|
||||
, "IfcAnnotationSymbolOccurrence": "IfcStyledItem"
|
||||
, "IfcAnnotationTextOccurrence": "IfcStyledItem"
|
||||
, "IfcExtendedMaterialProperties": "IfcMaterialProperties"
|
||||
}
|
||||
|
||||
@@ -329,61 +329,28 @@ def guess_date_range(work_schedule: ifcopenshell.entity_instance):
|
||||
return earliest, latest
|
||||
|
||||
|
||||
def get_direct_task_outputs(task: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
|
||||
return [rel.RelatingProduct for rel in task.HasAssignments if rel.is_a("IfcRelAssignsToProduct")]
|
||||
def get_task_outputs(
|
||||
task: ifcopenshell.entity_instance, is_recursive: bool = False
|
||||
) -> set[ifcopenshell.entity_instance]:
|
||||
if is_recursive:
|
||||
return {o for subtask in [task] + list(get_all_nested_tasks(task)) for o in get_task_outputs(subtask)}
|
||||
return {rel.RelatingProduct for rel in task.HasAssignments if rel.is_a("IfcRelAssignsToProduct")}
|
||||
|
||||
|
||||
def get_task_outputs(task: ifcopenshell.entity_instance, is_deep: bool = False) -> list[ifcopenshell.entity_instance]:
|
||||
if not is_deep:
|
||||
return get_direct_task_outputs(task)
|
||||
else:
|
||||
return [output for nested_task in get_all_nested_tasks(task) for output in get_direct_task_outputs(nested_task)]
|
||||
def get_task_inputs(
|
||||
task: ifcopenshell.entity_instance, is_recursive: bool = False
|
||||
) -> set[ifcopenshell.entity_instance]:
|
||||
if is_recursive:
|
||||
return {o for subtask in [task] + list(get_all_nested_tasks(task)) for o in get_task_inputs(subtask)}
|
||||
return {o for rel in task.OperatesOn for o in rel.RelatedObjects if o.is_a("IfcProduct")}
|
||||
|
||||
|
||||
def get_task_inputs(task: ifcopenshell.entity_instance, is_deep: bool = False) -> list[ifcopenshell.entity_instance]:
|
||||
if not is_deep:
|
||||
return [
|
||||
object
|
||||
for rel in task.OperatesOn
|
||||
if rel.is_a("IfcRelAssignsToProcess")
|
||||
for object in rel.RelatedObjects
|
||||
if object.is_a("IfcProduct")
|
||||
]
|
||||
else:
|
||||
return [
|
||||
output
|
||||
for nested_task in get_all_nested_tasks(task)
|
||||
for output in [
|
||||
object
|
||||
for rel in nested_task.OperatesOn
|
||||
if rel.is_a("IfcRelAssignsToProcess")
|
||||
for object in rel.RelatedObjects
|
||||
if object.is_a("IfcProduct")
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
def get_task_resources(task: ifcopenshell.entity_instance, is_deep: bool = False) -> list[ifcopenshell.entity_instance]:
|
||||
if not is_deep:
|
||||
return [
|
||||
object
|
||||
for rel in task.OperatesOn
|
||||
if rel.is_a("IfcRelAssignsToProcess")
|
||||
for object in rel.RelatedObjects
|
||||
if object.is_a("IfcResource")
|
||||
]
|
||||
else:
|
||||
return [
|
||||
resource
|
||||
for nested_task in get_all_nested_tasks(task)
|
||||
for resource in [
|
||||
object
|
||||
for rel in nested_task.OperatesOn
|
||||
if rel.is_a("IfcRelAssignsToProcess")
|
||||
for object in rel.RelatedObjects
|
||||
if object.is_a("IfcResource")
|
||||
]
|
||||
]
|
||||
def get_task_resources(
|
||||
task: ifcopenshell.entity_instance, is_recursive: bool = False
|
||||
) -> set[ifcopenshell.entity_instance]:
|
||||
if is_recursive:
|
||||
return {r for subtask in [task] + list(get_all_nested_tasks(task)) for r in get_task_resources(subtask)}
|
||||
return {o for rel in task.OperatesOn for o in rel.RelatedObjects if o.is_a("IfcResource")}
|
||||
|
||||
|
||||
def has_task_outputs(task: ifcopenshell.entity_instance) -> bool:
|
||||
|
||||
@@ -118,3 +118,20 @@ END-ISO-10303-21;
|
||||
assert qt_float_count_measure_ifc4x3.is_a("IfcQuantityNumber")
|
||||
assert isinstance(qt_float_count_measure_ifc4x3[3], float)
|
||||
assert qt_float_count_measure_ifc4x3[3] == 723.0
|
||||
|
||||
def test_migrate_extended_material_properties_ifc2x3_ifc4(self):
|
||||
ifc2x3_file = ifcopenshell.api.project.create_file(version="IFC2X3")
|
||||
material = ifc2x3_file.createIfcMaterial(Name="Material")
|
||||
props = [ifc2x3_file.createIfcPropertySingleValue(Name="Foo")]
|
||||
original_element = ifc2x3_file.createIfcExtendedMaterialProperties(Material=material, ExtendedProperties=props)
|
||||
ifc4_file = ifcopenshell.api.project.create_file()
|
||||
|
||||
migrator = subject.Migrator()
|
||||
for element in ifc2x3_file:
|
||||
migrator.migrate(element, ifc4_file)
|
||||
|
||||
new_element = ifc4_file.by_type("IfcMaterialProperties")[0]
|
||||
assert new_element.Material.is_a("IfcMaterial")
|
||||
assert new_element.Material.Name == "Material"
|
||||
assert new_element.Properties[0].is_a("IfcPropertySingleValue")
|
||||
assert new_element.Properties[0].Name == "Foo"
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace {
|
||||
}
|
||||
#ifdef IFOPSH_WITH_ROCKSDB
|
||||
else {
|
||||
T val;
|
||||
T val = T{};
|
||||
const bool is_header = entity_or_type->schema() == &Header_section_schema::get_schema();
|
||||
if constexpr (
|
||||
// the following types cannot be directly deserialized from rocksdb, but need to be constructed
|
||||
@@ -51,6 +51,12 @@ namespace {
|
||||
(is_header ? entity_or_type->name() : std::to_string(instance_name_)) + "|" +
|
||||
std::to_string(index_), &str);
|
||||
impl::deserialize(array_.db_ptr, str, val);
|
||||
} else {
|
||||
static_assert(
|
||||
std::is_same_v<T, EnumerationReference> ||
|
||||
std::is_same_v<std::remove_cv_t<std::remove_pointer_t<T>>, IfcUtil::IfcBaseClass>,
|
||||
"RocksDB deserialization must be specialized for this EnumerationReference and IfcBaseClass*"
|
||||
);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
@@ -194,13 +200,15 @@ AttributeValue::operator IfcUtil::IfcBaseClass* () const
|
||||
std::to_string(index_), &str);
|
||||
size_t v;
|
||||
memcpy(&v, str.data() + 2, sizeof(size_t));
|
||||
if (str[1] == 'i') {
|
||||
if (str.size() > 1 && str[1] == 'i') {
|
||||
// entity reference, by #Name
|
||||
return array_.db_ptr->assert_existance(v, IfcParse::impl::rocks_db_file_storage::entityinstance_ref);
|
||||
} else if (str[1] == 't') {
|
||||
} else if (str.size() > 1 && str[1] == 't') {
|
||||
// type reference by Identity
|
||||
return array_.db_ptr->assert_existance(v, IfcParse::impl::rocks_db_file_storage::typedecl_ref);
|
||||
}
|
||||
} else {
|
||||
throw std::runtime_error("Invalid data encountered");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -339,28 +347,28 @@ bool impl::serialize(std::string& val, const aggregate_of_aggregate_of_instance:
|
||||
return true;
|
||||
}
|
||||
|
||||
bool impl::serialize(std::string& val, const Blank& t)
|
||||
bool impl::serialize(std::string& val, const Blank&)
|
||||
{
|
||||
val.resize(1);
|
||||
val[0] = TypeEncoder::encode_type<Blank>();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool impl::serialize(std::string& val, const Derived& t)
|
||||
bool impl::serialize(std::string& val, const Derived&)
|
||||
{
|
||||
val.resize(1);
|
||||
val[0] = TypeEncoder::encode_type<Derived>();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool impl::serialize(std::string& val, const empty_aggregate_t& t)
|
||||
bool impl::serialize(std::string& val, const empty_aggregate_t&)
|
||||
{
|
||||
val.resize(1);
|
||||
val[0] = TypeEncoder::encode_type<empty_aggregate_t>();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool impl::serialize(std::string& val, const empty_aggregate_of_aggregate_t& t)
|
||||
bool impl::serialize(std::string& val, const empty_aggregate_of_aggregate_t&)
|
||||
{
|
||||
val.resize(1);
|
||||
val[0] = TypeEncoder::encode_type<empty_aggregate_of_aggregate_t>();
|
||||
@@ -397,6 +405,7 @@ bool impl::deserialize(IfcParse::impl::rocks_db_file_storage*, const std::string
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool impl::deserialize(IfcParse::impl::rocks_db_file_storage*, const std::string& val, boost::dynamic_bitset<>& t) {
|
||||
|
||||
@@ -266,7 +266,6 @@ namespace impl {
|
||||
if (val[0] != TypeEncoder::encode_type<T>()) {
|
||||
return false;
|
||||
}
|
||||
auto s = (val.size() - 1) / sizeof(T);
|
||||
memcpy(&t, val.data() + 1, sizeof(T));
|
||||
return true;
|
||||
}
|
||||
@@ -404,7 +403,7 @@ public:
|
||||
template<typename Visitor>
|
||||
auto apply_visitor(void* storage, const IfcParse::declaration* decl, std::size_t identity, std::size_t index, Visitor&& visitor) const {
|
||||
// @todo do we need visitation on all data/storage/attribute levels?
|
||||
AttributeValue((IfcParse::impl::rocks_db_file_storage*)storage, identity, decl, index).apply_visitor(std::forward<Visitor>(visitor));
|
||||
AttributeValue((IfcParse::impl::rocks_db_file_storage*)storage, identity, decl, (uint8_t) index).apply_visitor(std::forward<Visitor>(visitor));
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
#include "IfcException.h"
|
||||
|
||||
IfcParse::IfcException::~IfcException() = default;
|
||||
|
||||
IfcParse::IfcAttributeOutOfRangeException::~IfcAttributeOutOfRangeException() = default;
|
||||
|
||||
IfcParse::IfcInvalidTokenException::~IfcInvalidTokenException() = default;
|
||||
@@ -41,8 +41,8 @@ class IFC_PARSE_API IfcException : public std::exception {
|
||||
public:
|
||||
IfcException(const std::string& message)
|
||||
: message_(message) {}
|
||||
virtual ~IfcException() throw() {}
|
||||
virtual const char* what() const throw() {
|
||||
~IfcException() override;
|
||||
const char* what() const noexcept override {
|
||||
return message_.c_str();
|
||||
}
|
||||
};
|
||||
@@ -51,7 +51,7 @@ class IFC_PARSE_API IfcAttributeOutOfRangeException : public IfcException {
|
||||
public:
|
||||
IfcAttributeOutOfRangeException(const std::string& exception)
|
||||
: IfcException(exception) {}
|
||||
~IfcAttributeOutOfRangeException() throw() {}
|
||||
~IfcAttributeOutOfRangeException() override;
|
||||
};
|
||||
|
||||
class IFC_PARSE_API IfcInvalidTokenException : public IfcException {
|
||||
@@ -70,7 +70,7 @@ class IFC_PARSE_API IfcInvalidTokenException : public IfcException {
|
||||
: IfcException(
|
||||
std::string("Unexpected '") + std::string(1, character) + "' at offset " +
|
||||
boost::lexical_cast<std::string>(token_start)) {}
|
||||
~IfcInvalidTokenException() throw() {}
|
||||
~IfcInvalidTokenException() override;
|
||||
};
|
||||
|
||||
} // namespace IfcParse
|
||||
|
||||
@@ -127,6 +127,10 @@ public:
|
||||
return storage_.byref_excl_;
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<IfcUtil::IfcBaseClass>> steal_instances() {
|
||||
return storage_.steal_instances();
|
||||
}
|
||||
|
||||
InstanceStreamer(const std::string& fn);
|
||||
|
||||
InstanceStreamer(void* data, int length);
|
||||
|
||||
@@ -725,6 +725,7 @@ void IfcParse::impl::in_memory_file_storage::load(unsigned entity_instance_name,
|
||||
// the 2nd pass.
|
||||
load(entity_instance_name, entity, ps, attribute_index == -1 ? (int)attribute_index_within_data : attribute_index);
|
||||
auto* simple_type_instance = (schema ? schema : file->schema())->instantiate(decl, ps.construct(entity_instance_name, *references_to_resolve, decl, boost::none, attribute_index == -1 ? (int)attribute_index_within_data : attribute_index));
|
||||
read_simple_type_instances.emplace_back(simple_type_instance);
|
||||
//@todo decide addEntity(((IfcUtil::IfcBaseClass*)*entity));
|
||||
context.push(simple_type_instance);
|
||||
simple_type_instance->file_ = file;
|
||||
@@ -1650,6 +1651,9 @@ void IfcParse::impl::in_memory_file_storage::read_from_stream(IfcParse::IfcSpfSt
|
||||
|
||||
good_ = streamer.status();
|
||||
byref_excl_ = streamer.inverses();
|
||||
|
||||
// Move the storage of simple type instances so that they are retained during the lifetime of the file
|
||||
read_simple_type_instances = streamer.steal_instances();
|
||||
|
||||
Logger::Status("\rDone scanning file ");
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user