mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-06 16:01:36 +00:00
Compare commits
160 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c62ed5d3b6 | |||
| ffb6c82541 | |||
| a9e366b238 | |||
| 1ca0dfb1e0 | |||
| c11ba23a3d | |||
| 556c769b40 | |||
| 78173dd546 | |||
| 2196c16097 | |||
| 7af5db7cd4 | |||
| c58672cc25 | |||
| 85e022d76e | |||
| 872f40933f | |||
| 4491018f5e | |||
| a21c440b5b | |||
| 143e094db8 | |||
| 94e9e945d7 | |||
| 5e56115050 | |||
| 1ad654aab3 | |||
| 596ffb093b | |||
| 3576362fd8 | |||
| 2c949bb39a | |||
| 34faa0617b | |||
| 15f6b3803f | |||
| 8514a426bb | |||
| 2062324294 | |||
| 90c6218210 | |||
| 321bb7178d | |||
| 42dda0a07a | |||
| adb17d91f9 | |||
| 646cc59687 | |||
| 38759348cd | |||
| ff44f4f395 | |||
| a31051a937 | |||
| 5cfd69f337 | |||
| f4100f7951 | |||
| 785d5118d0 | |||
| fddd85817a | |||
| a689fc6377 | |||
| 8e5a7ed24e | |||
| 788217c73c | |||
| 1abc3a1422 | |||
| c31c34b7d2 | |||
| b22ecba179 | |||
| 22bc98b48b | |||
| cf70026570 | |||
| 3c4a865e0b | |||
| 8523bb317a | |||
| 4b00b268c0 | |||
| f0ca34d107 | |||
| 3834ecb06a | |||
| 047c9e3ee9 | |||
| 786b796f37 | |||
| 3fd8be72c2 | |||
| cb80081473 | |||
| 5ac84e2be7 | |||
| c8466e0606 | |||
| db5167bbaf | |||
| 7d6e26bfd5 | |||
| 2fd339ac6b | |||
| 6f6dc08696 | |||
| 6cb063ebd2 | |||
| fb95e3eb51 | |||
| 17a061f917 | |||
| 51db65b868 | |||
| faab9e04a3 | |||
| bea92455dd | |||
| 6be1b80a82 | |||
| 2927eadada | |||
| 4c5e90dc77 | |||
| f860973172 | |||
| 94ed0cd59c | |||
| 93ea1f98ac | |||
| b68a92f251 | |||
| 902da2e720 | |||
| d384aaeb9c | |||
| 133494af97 | |||
| 64fc9462b6 | |||
| 3d86e5fff3 | |||
| a24bb069aa | |||
| 159355d63a | |||
| 598cf4ba79 | |||
| c86a4c88a9 | |||
| 6a9e76c2af | |||
| 2070071c49 | |||
| 9bbf3bcaba | |||
| 0395c0c6c7 | |||
| a175ec4d28 | |||
| 139627b6ec | |||
| 6f2eb0a116 | |||
| d26561acee | |||
| de0374bdbd | |||
| 51463ab674 | |||
| 6012695cfe | |||
| 081b3fcf33 | |||
| 27a2c94b4a | |||
| b9f76042bb | |||
| e7ef307984 | |||
| 1f68e52ac2 | |||
| 87dfb75c21 | |||
| 8afb1b8b30 | |||
| 5572991a7c | |||
| 653b9288c0 | |||
| 233b0856af | |||
| ec29e3d60b | |||
| 70bc32b3df | |||
| ca781655b0 | |||
| 03e2cb70bb | |||
| c7c95e1f9d | |||
| b040c5e25b | |||
| 60b007d28a | |||
| 52130a3eb6 | |||
| 5e849d7b81 | |||
| a7a28f613b | |||
| 46b3bfecd1 | |||
| 11b62a0fdc | |||
| 732bb93826 | |||
| a0bda1afd3 | |||
| e0790601df | |||
| a9a4b0ccf0 | |||
| f8848051ca | |||
| b81031eecf | |||
| 95a7ea8dce | |||
| 98edb5a34c | |||
| 8ca2a55616 | |||
| 74141bec73 | |||
| f60c4fd3c5 | |||
| d9ee3522bc | |||
| 2538c47d01 | |||
| ec3dc71c0e | |||
| e5edd9a34d | |||
| f72359918d | |||
| dfd505a794 | |||
| 4a311fe4de | |||
| 9d4493e1da | |||
| a1d8fbdbe9 | |||
| 952abde48e | |||
| 168718063f | |||
| 4abcbbb4f9 | |||
| 1ac1b289c1 | |||
| c25173aa12 | |||
| ea8d303883 | |||
| 9d79337fa7 | |||
| 82a6ddb01c | |||
| a7152bdef0 | |||
| 6484a529b0 | |||
| 6e1e653ceb | |||
| 0488e4cccb | |||
| 3cf92fd508 | |||
| fb2bb7554e | |||
| be7661a83b | |||
| 9684e21d93 | |||
| ee5c401823 | |||
| 4aa3ffb765 | |||
| c9affcc622 | |||
| bddb8d1e56 | |||
| c4d3dad639 | |||
| adbf9093d3 | |||
| c8defbb77f | |||
| a14fc0a7f5 | |||
| 8d2717d25b |
@@ -35,7 +35,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
pyver: [py39, py310, py311, py312]
|
||||
pyver: [py310, py311, py312]
|
||||
config:
|
||||
- {
|
||||
name: "Windows Build",
|
||||
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
pyver: [py39, py310, py311, py312]
|
||||
pyver: [py310, py311, py312]
|
||||
config:
|
||||
- {
|
||||
name: "Windows Build",
|
||||
|
||||
@@ -47,6 +47,7 @@ jobs:
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install xmlschema xsdata numpy lxml pytest isodate lark networkx tabulate python-dateutil shapely
|
||||
pip install mathutils
|
||||
pip install src/bcf --no-deps
|
||||
pip install https://github.com/Andrej730/aud/archive/refs/heads/master-reduced-size.zip
|
||||
|
||||
@@ -125,4 +126,9 @@ jobs:
|
||||
python tests.py
|
||||
cd ../src/ifcopenshell-python
|
||||
mv ifcopenshell ifcopenshell-local # Force testing on installed module
|
||||
make test-safe
|
||||
make test
|
||||
cd ../bcf && make test
|
||||
pip install requests
|
||||
cd ../bsdd && make test
|
||||
cd ../ifcpatch && make test
|
||||
cd ../ifctester && make test
|
||||
|
||||
@@ -46,12 +46,12 @@ Contents
|
||||
| ifcblender | Historic Blender IFC import add-on | LGPL-3.0-or-later\* |
|
||||
| ifccityjson | Convert CityJSON to IFC | LGPL-3.0-or-later | [](https://pypi.org/project/ifccityjson/) |
|
||||
| ifcclash | Clash detection library and CLI app | LGPL-3.0-or-later | [](https://pypi.org/project/ifcclash/) |
|
||||
| ifcconvert | CLI app to convert IFC to many other formats | LGPL-3.0-or-later\* | [](https://docs.ifcopenshell.org/ifcconvert/installation.html)
|
||||
| ifcconvert | CLI app to convert IFC to many other formats | LGPL-3.0-or-later\* | [](https://docs.ifcopenshell.org/ifcconvert/installation.html) [](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=ifcconvert&expanded=true)
|
||||
| ifccsv | Library and CLI app to export and import schedules from IFC | LGPL-3.0-or-later | [](https://pypi.org/project/ifccsv/) |
|
||||
| ifcdiff | Compare changes between IFC models | LGPL-3.0-or-later | [](https://pypi.org/project/ifcdiff/) |
|
||||
| ifcfm | Extract IFC data for FM handover requirements | LGPL-3.0-or-later | [](https://pypi.org/project/ifcfm/) |
|
||||
| ifcmax | Historic extension for IFC support in 3DS Max | LGPL-3.0-or-later\* | [](https://docs.ifcopenshell.org/ifcmax.html)
|
||||
| ifcopenshell-python | Python library for IFC manipulation | LGPL-3.0-or-later\* | [](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=ifcopenshell-python&expanded=true) [](https://pypi.org/project/ifcopenshell/) [](https://anaconda.org/conda-forge/ifcopenshell) [](https://anaconda.org/ifcopenshell/ifcopenshell) [](https://hub.docker.com/r/aecgeeks/ifcopenshell) [](https://aur.archlinux.org/packages/ifcopenshell) [](https://aur.archlinux.org/packages/ifcopenshell-git) |
|
||||
| ifcopenshell-python | Python library for IFC manipulation | LGPL-3.0-or-later\* | [](https://docs.ifcopenshell.org/ifcopenshell-python/installation.html) [](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=ifcopenshell-python&expanded=true) [](https://pypi.org/project/ifcopenshell/) [](https://anaconda.org/conda-forge/ifcopenshell) [](https://anaconda.org/ifcopenshell/ifcopenshell) [](https://hub.docker.com/r/aecgeeks/ifcopenshell) [](https://aur.archlinux.org/packages/ifcopenshell) [](https://aur.archlinux.org/packages/ifcopenshell-git) |
|
||||
| ifcpatch | Utility to run pre-packaged scripts to manipulate IFCs | LGPL-3.0-or-later | [](https://pypi.org/project/ifcpatch/) |
|
||||
| ifcsverchok | Blender Add-on for visual node programming with IFC | GPL-3.0-or-later | [](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=ifcsverchok&expanded=true)
|
||||
| ifctester | Library, CLI and webapp for IDS model auditing | LGPL-3.0-or-later | [](https://pypi.org/project/ifctester/) |
|
||||
|
||||
@@ -221,10 +221,11 @@ endif()
|
||||
|
||||
if(GLTF_SUPPORT OR CITYJSON_SUPPORT)
|
||||
UNIFY_ENVVARS_AND_CACHE(JSON_INCLUDE_DIR)
|
||||
find_file(json_hpp "json.hpp" ${JSON_INCLUDE_DIR}/nlohmann)
|
||||
find_path(json_header_path "json.hpp" ${JSON_INCLUDE_DIR} PATH_SUFFIXES "nlohmann")
|
||||
set(JSON_INCLUDE_DIR ${json_header_path})
|
||||
|
||||
if(json_hpp)
|
||||
message(STATUS "JSON for Modern C++ header file found")
|
||||
if(json_header_path)
|
||||
message(STATUS "JSON for Modern C++ header file found in ${JSON_INCLUDE_DIR}")
|
||||
else()
|
||||
message(FATAL_ERROR "Unable to find JSON for Modern C++ header file, aborting")
|
||||
endif()
|
||||
|
||||
+29
-2
@@ -1,7 +1,34 @@
|
||||
[tool.black]
|
||||
line-length = 120
|
||||
include = 'src/(bcf|bcfserver|blenderbim|bsdd|foundationserver|ifc2ca|ifc4d|ifc5d|ifcbimtester|ifcblender|ifccityjson|ifcclash|ifccsv|ifcdiff|ifcfm|ifcpatch|ifctester|ifcopenshell-python|ifcsverchok|opencdeserver)/.*.py$'
|
||||
extend-exclude = '(src/ifcopenshell-python/ifcopenshell/express/*|src/ifc2ca/templates/*)'
|
||||
include = '''
|
||||
src/(
|
||||
bcf
|
||||
|bcfserver
|
||||
|blenderbim
|
||||
|bsdd
|
||||
|foundationserver
|
||||
|ifc2ca
|
||||
|ifc4d
|
||||
|ifc5d
|
||||
|ifcbimtester
|
||||
|ifcblender
|
||||
|ifccityjson
|
||||
|ifcclash
|
||||
|ifccsv
|
||||
|ifcdiff
|
||||
|ifcfm
|
||||
|ifcpatch
|
||||
|ifctester
|
||||
|ifcopenshell-python
|
||||
|ifcsverchok
|
||||
|opencdeserver
|
||||
)/.*.py$
|
||||
'''
|
||||
extend-exclude = '''
|
||||
src/ifcopenshell-python/ifcopenshell/express/*
|
||||
|src/ifcopenshell-python/ifcopenshell/mvd/*
|
||||
|src/ifc2ca/templates/*
|
||||
'''
|
||||
|
||||
[tool.pyright]
|
||||
reportInvalidTypeForm = false
|
||||
|
||||
@@ -33,6 +33,10 @@ models:
|
||||
cd src && xsdata generate -p bcf.v2.model --unnest-classes --kw-only --slots -ds Google bcf/v2/xsd
|
||||
cd src && xsdata generate -p bcf.v3.model --unnest-classes --kw-only --slots -ds Google bcf/v3/xsd
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
pytest -p no:pytest-blender tests
|
||||
|
||||
# .PHONY
|
||||
# api:
|
||||
# openapi-python-client generate --url https://api.swaggerhub.com/apis/buildingSMART/BCF/3.0
|
||||
|
||||
@@ -5,8 +5,8 @@ from numpy.typing import NDArray
|
||||
|
||||
|
||||
def camera_vectors_from_element_placement(
|
||||
elem_placement: NDArray[np.float_],
|
||||
) -> tuple[NDArray[np.float_], NDArray[np.float_], NDArray[np.float_]]:
|
||||
elem_placement: NDArray[np.float64],
|
||||
) -> tuple[NDArray[np.float64], NDArray[np.float64], NDArray[np.float64]]:
|
||||
"""
|
||||
Calculate the vectors of a camera pointing to an element.
|
||||
|
||||
@@ -21,8 +21,8 @@ def camera_vectors_from_element_placement(
|
||||
|
||||
|
||||
def camera_vectors_from_target_position(
|
||||
target_position: NDArray[np.float_], offset: Optional[NDArray[np.float_]] = None
|
||||
) -> tuple[NDArray[np.float_], NDArray[np.float_], NDArray[np.float_]]:
|
||||
target_position: NDArray[np.float64], offset: Optional[NDArray[np.float64]] = None
|
||||
) -> tuple[NDArray[np.float64], NDArray[np.float64], NDArray[np.float64]]:
|
||||
"""
|
||||
Calculate the vectors of a camera pointing to a target point.
|
||||
|
||||
@@ -50,7 +50,7 @@ def camera_vectors_from_target_position(
|
||||
# return camera_position, -mat[:3, 2], mat[:3, 1]
|
||||
|
||||
|
||||
def unit_vector(v: NDArray[np.float_]) -> NDArray[np.float_]:
|
||||
def unit_vector(v: NDArray[np.float64]) -> NDArray[np.float64]:
|
||||
"""
|
||||
Return the unit vector of a vector.
|
||||
|
||||
|
||||
@@ -278,7 +278,7 @@ class TopicHandler:
|
||||
self.add_visinfo_handler(new_viewpoint)
|
||||
return new_viewpoint
|
||||
|
||||
def add_viewpoint_from_point_and_guids(self, position: NDArray[np.float_], *guids: str) -> None:
|
||||
def add_viewpoint_from_point_and_guids(self, position: NDArray[np.float64], *guids: str) -> None:
|
||||
"""Add a viewpoint pointing at an XYZ point in space
|
||||
|
||||
Args:
|
||||
|
||||
@@ -176,7 +176,7 @@ class VisualizationInfoHandler:
|
||||
@classmethod
|
||||
def create_from_point_and_guids(
|
||||
cls,
|
||||
position: NDArray[np.float_],
|
||||
position: NDArray[np.float64],
|
||||
*guids: str,
|
||||
xml_handler: Optional[AbstractXmlParserSerializer] = None,
|
||||
) -> "VisualizationInfoHandler":
|
||||
@@ -224,7 +224,7 @@ def build_viewpoint(element: entity_instance) -> mdl.VisualizationInfo:
|
||||
)
|
||||
|
||||
|
||||
def build_viewpoint_from_position_and_guids(position: NDArray[np.float_], *guids: str) -> mdl.VisualizationInfo:
|
||||
def build_viewpoint_from_position_and_guids(position: NDArray[np.float64], *guids: str) -> mdl.VisualizationInfo:
|
||||
"""
|
||||
Return a BCF viewpoint of an IFC element.
|
||||
|
||||
@@ -261,7 +261,7 @@ def build_components(*guids: str) -> mdl.Components:
|
||||
)
|
||||
|
||||
|
||||
def build_camera(elem_placement: NDArray[np.float_]) -> mdl.PerspectiveCamera:
|
||||
def build_camera(elem_placement: NDArray[np.float64]) -> mdl.PerspectiveCamera:
|
||||
"""
|
||||
Return a BCF camera for an IFC element placement matrix.
|
||||
|
||||
@@ -275,7 +275,7 @@ def build_camera(elem_placement: NDArray[np.float_]) -> mdl.PerspectiveCamera:
|
||||
|
||||
|
||||
def build_camera_from_vectors(
|
||||
camera_position: NDArray[np.float_], camera_dir: NDArray[np.float_], camera_up: NDArray[np.float_]
|
||||
camera_position: NDArray[np.float64], camera_dir: NDArray[np.float64], camera_up: NDArray[np.float64]
|
||||
) -> mdl.PerspectiveCamera:
|
||||
"""
|
||||
Return a BCF camera for an IFC element placement matrix.
|
||||
|
||||
@@ -175,7 +175,7 @@ class TopicHandler:
|
||||
new_viewpoint = VisualizationInfoHandler.create_new(element, self._xml_handler)
|
||||
self.add_visinfo_handler(new_viewpoint)
|
||||
|
||||
def add_viewpoint_from_point_and_guids(self, position: NDArray[np.float_], *guids: str) -> None:
|
||||
def add_viewpoint_from_point_and_guids(self, position: NDArray[np.float64], *guids: str) -> None:
|
||||
"""
|
||||
Add a viewpoint tergeting an IFC element to the topic.
|
||||
|
||||
|
||||
@@ -175,7 +175,7 @@ class VisualizationInfoHandler:
|
||||
@classmethod
|
||||
def create_from_point_and_guids(
|
||||
cls,
|
||||
position: NDArray[np.float_],
|
||||
position: NDArray[np.float64],
|
||||
*guids: str,
|
||||
xml_handler: Optional[AbstractXmlParserSerializer] = None,
|
||||
) -> "VisualizationInfoHandler":
|
||||
@@ -218,7 +218,7 @@ def build_viewpoint(element: entity_instance) -> mdl.VisualizationInfo:
|
||||
)
|
||||
|
||||
|
||||
def build_viewpoint_from_position_and_guids(position: NDArray[np.float_], *guids: str) -> mdl.VisualizationInfo:
|
||||
def build_viewpoint_from_position_and_guids(position: NDArray[np.float64], *guids: str) -> mdl.VisualizationInfo:
|
||||
"""
|
||||
Return a BCF viewpoint of an IFC element.
|
||||
|
||||
@@ -255,7 +255,7 @@ def build_components(*guids: str) -> mdl.Components:
|
||||
)
|
||||
|
||||
|
||||
def build_camera(elem_placement: NDArray[np.float_]) -> mdl.PerspectiveCamera:
|
||||
def build_camera(elem_placement: NDArray[np.float64]) -> mdl.PerspectiveCamera:
|
||||
"""
|
||||
Return a BCF camera for an IFC element placement matrix.
|
||||
|
||||
@@ -269,7 +269,7 @@ def build_camera(elem_placement: NDArray[np.float_]) -> mdl.PerspectiveCamera:
|
||||
|
||||
|
||||
def build_camera_from_vectors(
|
||||
camera_position: NDArray[np.float_], camera_dir: NDArray[np.float_], camera_up: NDArray[np.float_]
|
||||
camera_position: NDArray[np.float64], camera_dir: NDArray[np.float64], camera_up: NDArray[np.float64]
|
||||
) -> mdl.PerspectiveCamera:
|
||||
"""
|
||||
Return a BCF camera for an IFC element placement matrix.
|
||||
|
||||
+19
-13
@@ -49,11 +49,6 @@ LAST_COMMIT_HASH:=$(shell git rev-parse HEAD)
|
||||
PYVERSION:=py310
|
||||
PYPI_IMP:=cp
|
||||
|
||||
ifeq ($(PYVERSION), py39)
|
||||
PYLIBDIR:=python3.9
|
||||
PYNUMBER:=39
|
||||
PYPI_VERSION:=3.9
|
||||
endif
|
||||
ifeq ($(PYVERSION), py310)
|
||||
PYLIBDIR:=python3.10
|
||||
PYNUMBER:=310
|
||||
@@ -189,25 +184,30 @@ else ifeq ($(PLATFORM), macos)
|
||||
else
|
||||
cd build && . env/$(VENV_ACTIVATE) && $(PIP) download pyradiance $(PYPI_PLATFORM) --python-version $(PYPI_VERSION) --implementation $(PYPI_IMP) --only-binary=:all: --dest=./wheels
|
||||
endif
|
||||
# tomllib replacement for < Python 3.11, used to parse blender_manifest
|
||||
ifeq ($(PYVERSION), py310)
|
||||
cd build && . env/$(VENV_ACTIVATE) && $(PIP) download tomli --dest=./wheels
|
||||
endif
|
||||
|
||||
# Provides jsgantt-improved supports for web-based construction sequencing gantt charts
|
||||
cd build/blenderbim/bim/data/gantt/ && wget https://raw.githubusercontent.com/jsGanttImproved/jsgantt-improved/master/dist/jsgantt.js
|
||||
cd build/blenderbim/bim/data/gantt/ && wget https://raw.githubusercontent.com/jsGanttImproved/jsgantt-improved/master/dist/jsgantt.css
|
||||
|
||||
# Provides IFCJSON functionality
|
||||
cd build && wget https://github.com/IFCJSON-Team/IFC2JSON_python/archive/master.zip
|
||||
cd build && unzip master.zip && rm master.zip
|
||||
# TODO: replace with main repo if https://github.com/IFCJSON-Team/IFC2JSON_python/pull/3 is merged.
|
||||
cd build && wget -O ifc2json.zip https://github.com/Moult/IFC2JSON_python/archive/refs/heads/feature-ios-v0.8.0.zip
|
||||
cd build && unzip ifc2json.zip && rm ifc2json.zip
|
||||
# IFCJSON doesn't have pyproject.toml, so we use python command.
|
||||
cd build && . env/$(VENV_ACTIVATE) && cd IFC2JSON_python-master/file_converters && \
|
||||
cd build && . env/$(VENV_ACTIVATE) && cd IFC2JSON_python-*/file_converters && \
|
||||
$(PYTHON) -c "from setuptools import setup; \
|
||||
setup( \
|
||||
name='ifcjson', \
|
||||
version='0.0.0', \
|
||||
version='0.0.1', \
|
||||
author='Jan Brouwer', \
|
||||
author_email='jan@brewsky.nl', \
|
||||
packages=['ifcjson'], \
|
||||
)" bdist_wheel
|
||||
cp -r build/IFC2JSON_python-master/file_converters/dist/*.whl build/wheels/
|
||||
cp -r build/IFC2JSON_python-*/file_converters/dist/*.whl build/wheels/
|
||||
|
||||
# Brickschema requires pkg_resources which is provided by Blender.
|
||||
# Provides Brickschema functionality
|
||||
@@ -346,9 +346,15 @@ endif
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
make test-core
|
||||
make test-tool
|
||||
make test-bim
|
||||
@echo "Running all tests..."
|
||||
@ERROR=0; \
|
||||
make test-core || ERROR=1; \
|
||||
make test-tool || ERROR=1; \
|
||||
make test-bim || ERROR=1; \
|
||||
if [ $$ERROR -ne 0 ]; then \
|
||||
echo "One or more tests failed"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
.PHONY: test-core
|
||||
test-core:
|
||||
|
||||
@@ -68,11 +68,14 @@ def initialize_bbim_semver():
|
||||
in `addon_utils.modules()->bl_info['version']`,
|
||||
therefore we just parse it from .toml.
|
||||
"""
|
||||
import tomllib
|
||||
if sys.version_info >= (3, 11):
|
||||
import tomllib as toml
|
||||
else:
|
||||
import tomli as toml
|
||||
|
||||
toml_path = Path(__file__).parent / "blender_manifest.toml"
|
||||
with open(toml_path, "rb") as f:
|
||||
manifest = tomllib.load(f)
|
||||
manifest = toml.load(f)
|
||||
semver_pattern = r"^(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$"
|
||||
version_str = manifest["version"]
|
||||
re_version = re.match(semver_pattern, version_str)
|
||||
@@ -82,9 +85,6 @@ def initialize_bbim_semver():
|
||||
bbim_semver["version"] = version_str
|
||||
|
||||
|
||||
initialize_bbim_semver()
|
||||
|
||||
|
||||
def get_debug_info():
|
||||
bbim_version = bbim_semver["version"]
|
||||
|
||||
@@ -177,6 +177,7 @@ def clean_up_dlls_safe_links() -> None:
|
||||
|
||||
|
||||
if IN_BLENDER:
|
||||
initialize_bbim_semver()
|
||||
|
||||
def get_binary_info() -> dict[str, Any]:
|
||||
info = {}
|
||||
|
||||
@@ -98,11 +98,12 @@ classes = [
|
||||
operator.AddIfcFile,
|
||||
operator.BIM_OT_add_section_plane,
|
||||
operator.BIM_OT_delete_object,
|
||||
operator.BIM_OT_open_webbrowser,
|
||||
operator.BIM_OT_remove_section_plane,
|
||||
operator.BIM_OT_select_object,
|
||||
operator.BIM_OT_show_description,
|
||||
operator.BIM_OT_multiple_file_selector,
|
||||
operator.ClippingPlaneCutWithCappings,
|
||||
operator.CloseBlendWarning,
|
||||
operator.CloseError,
|
||||
operator.EditBlenderCollection,
|
||||
operator.FileAssociate,
|
||||
@@ -121,6 +122,7 @@ classes = [
|
||||
prop.StrProperty,
|
||||
operator.BIM_OT_enum_property_search, # /!\ Register AFTER prop.StrProperty
|
||||
prop.ObjProperty,
|
||||
prop.MultipleFileSelect,
|
||||
prop.Attribute,
|
||||
prop.BIMAreaProperties,
|
||||
prop.BIMTabProperties,
|
||||
|
||||
@@ -2,7 +2,9 @@ import sys
|
||||
import os
|
||||
import webbrowser
|
||||
|
||||
blenderbim_lib_path = os.environ.get("blenderbim_lib_path")
|
||||
blenderbim_lib_path = os.environ.get("BLENDERBIM_LIB_PATH")
|
||||
blenderbim_version = os.environ.get("BLENDERBIM_VERSION")
|
||||
|
||||
if blenderbim_lib_path:
|
||||
sys.path.insert(0, blenderbim_lib_path)
|
||||
|
||||
@@ -52,9 +54,16 @@ class WebNamespace(socketio.AsyncNamespace):
|
||||
"web_operator",
|
||||
data,
|
||||
namespace="/blender",
|
||||
room=data["blenderId"],
|
||||
room=data.get("blenderId", None),
|
||||
)
|
||||
|
||||
async def on_get_svg(self, sid, data):
|
||||
print("hello world!")
|
||||
file_path = data["path"]
|
||||
with open(file_path, "r") as file:
|
||||
svg_data = file.read()
|
||||
await sio.emit("svg_data", svg_data, room=sid, namespace="/web")
|
||||
|
||||
async def send_cached_messages(self, sid):
|
||||
# Send cached messages to the connected web client
|
||||
for blenderId, messages in blender_messages.items():
|
||||
@@ -98,6 +107,12 @@ class BlenderNamespace(socketio.AsyncNamespace):
|
||||
blender_messages[sid]["gantt_data"] = data
|
||||
await sio.emit("gantt_data", {"blenderId": sid, "data": data}, namespace="/web")
|
||||
|
||||
async def on_drawings_data(self, sid, data):
|
||||
print(f"Drawings directory from Blender client {sid}")
|
||||
print(data)
|
||||
blender_messages[sid]["drwings_data"] = data
|
||||
await sio.emit("drawings_data", {"blenderId": sid, "data": data}, namespace="/web")
|
||||
|
||||
|
||||
# Attach namespaces
|
||||
sio.register_namespace(WebNamespace("/web"))
|
||||
@@ -108,26 +123,45 @@ sio.register_namespace(BlenderNamespace("/blender"))
|
||||
async def index(request):
|
||||
with open("templates/index.html", "r") as f:
|
||||
template = f.read()
|
||||
html_content = pystache.render(template, {"port": sio_port})
|
||||
html_content = pystache.render(template, {"port": sio_port, "version": blenderbim_version})
|
||||
return web.Response(text=html_content, content_type="text/html")
|
||||
|
||||
|
||||
async def gantt(request):
|
||||
with open("templates/gantt.html", "r") as f:
|
||||
template = f.read()
|
||||
html_content = pystache.render(template, {"port": sio_port, "version": blenderbim_version})
|
||||
return web.Response(text=html_content, content_type="text/html")
|
||||
|
||||
|
||||
async def drawings(request):
|
||||
with open("templates/drawings.html", "r") as f:
|
||||
template = f.read()
|
||||
html_content = pystache.render(template, {"port": sio_port})
|
||||
return web.Response(text=html_content, content_type="text/html")
|
||||
|
||||
|
||||
async def open_web_browser(app):
|
||||
webbrowser.open(f"http://127.0.0.1:{sio_port}/")
|
||||
async def on_startup(app):
|
||||
pid_file = "running_pid.json"
|
||||
|
||||
if os.path.exists(pid_file):
|
||||
with open(pid_file, "r") as f:
|
||||
pids = json.load(f)
|
||||
else:
|
||||
pids = {}
|
||||
|
||||
pids[str(os.getpid())] = sio_port
|
||||
|
||||
with open(pid_file, "w") as f:
|
||||
json.dump(pids, f, indent=4)
|
||||
|
||||
|
||||
app.router.add_get("/", index)
|
||||
app.router.add_get("/drawings", drawings)
|
||||
app.router.add_get("/gantt", gantt)
|
||||
app.router.add_static("/jsgantt/", path="../gantt", name="jsgantt")
|
||||
app.router.add_static("/static/", path="./static", name="static")
|
||||
app.on_startup.append(open_web_browser)
|
||||
app.on_startup.append(on_startup)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
@@ -0,0 +1,249 @@
|
||||
:root {
|
||||
--font-family: Arial, sans-serif;
|
||||
--base-font-size: 16px;
|
||||
--margin-tiny: 0.125rem;
|
||||
--margin-small: 0.625rem;
|
||||
--margin-medium: 1.25rem;
|
||||
--margin-large: 2.5rem;
|
||||
--padding-tiny: 0.125rem;
|
||||
--padding-small: 0.625rem;
|
||||
--padding-medium: 1rem;
|
||||
--font-size-large: 1.2rem;
|
||||
--logo-height: 2.5rem;
|
||||
--nav-height: 2.5rem;
|
||||
}
|
||||
|
||||
:root.dark {
|
||||
color-scheme: dark;
|
||||
--bg-color: #252525;
|
||||
--text-color: #e0e0e0;
|
||||
--nav-bg-color: #121212;
|
||||
--nav-border-color: #25682a;
|
||||
--nav-link-color: #fff;
|
||||
--nav-link-hover-color: #3fb449;
|
||||
--warning-color: #FFDB8F;
|
||||
--border-color: #464444;
|
||||
}
|
||||
|
||||
:root.light {
|
||||
color-scheme: light;
|
||||
--bg-color: #ffffff;
|
||||
--text-color: #000000;
|
||||
--nav-bg-color: #f8f8f8;
|
||||
--nav-border-color: #cccccc;
|
||||
--nav-link-color: #000000;
|
||||
--nav-link-hover-color: #38a63d;
|
||||
--warning-color: #FF4500;
|
||||
--border-color: #222;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: var(--base-font-size);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--bg-color);
|
||||
color: var(--text-color);
|
||||
margin: 0;
|
||||
font-family: var(--font-family);
|
||||
}
|
||||
|
||||
#container {
|
||||
margin-top: var(--margin-medium);
|
||||
margin-left: var(--margin-small);
|
||||
margin-right: var(--margin-small);
|
||||
margin-bottom: var(--margin-medium);
|
||||
height: calc(100vh - var(--nav-height));
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
nav {
|
||||
background-color: var(--nav-bg-color);
|
||||
height: var(--nav-height);
|
||||
padding: var(--padding-medium) 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
border-bottom: 2px solid var(--nav-border-color);
|
||||
}
|
||||
|
||||
nav .logo {
|
||||
margin-left: var(--margin-large);
|
||||
height: var(--logo-height);
|
||||
}
|
||||
|
||||
nav ul {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
margin-left: 0.0625rem;
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
nav ul li {
|
||||
margin-right: 3.125rem;
|
||||
}
|
||||
|
||||
nav ul li a {
|
||||
text-decoration: none;
|
||||
color: var(--nav-link-color);
|
||||
font-size: var(--font-size-large);
|
||||
}
|
||||
|
||||
nav ul li a:hover,
|
||||
nav ul li a.active {
|
||||
color: var(--nav-link-hover-color);
|
||||
}
|
||||
|
||||
.warning {
|
||||
color: var(--warning-color);
|
||||
margin-bottom: var(--margin-small);
|
||||
padding: var(--padding-tiny);
|
||||
display: none;
|
||||
}
|
||||
|
||||
#toggle-theme {
|
||||
border: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
font-size: var(--font-size-large);
|
||||
margin-right: var(--margin-medium);
|
||||
}
|
||||
|
||||
#toggle-theme:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#client-list {
|
||||
position: absolute;
|
||||
top: calc(0 + var(--nav-height));
|
||||
overflow-y: auto;
|
||||
overflow: hidden;
|
||||
transition: opacity 0.3s ease-out, visibility 0.3s ease-out;
|
||||
border: 1px solid var(--table-border-color);
|
||||
background-color: var(--nav-bg-color);
|
||||
z-index: 1;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
#client-list.show {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
#connected-list-div {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.client {
|
||||
padding: var(--margin-small);
|
||||
border-bottom: 1px solid var(--table-border-color);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.client-details {
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
padding-left: var(--padding-small);
|
||||
transition: max-height 0.2s ease-out, padding 0.2s ease-out, opacity 0.1s ease-out;
|
||||
background-color: var(--nav-bg-color);
|
||||
margin-top: var(--margin-tiny);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.client-details.show {
|
||||
max-height: none;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.client-detail {
|
||||
border-bottom: 1px solid var(--table-border-color);
|
||||
padding: var(--padding-small);
|
||||
}
|
||||
|
||||
#show-connected-button {
|
||||
width: auto;
|
||||
background-color: var(--bg-color);
|
||||
border: none;
|
||||
font-size: var(--base-font-size);
|
||||
}
|
||||
|
||||
#show-connected-button:hover,
|
||||
.scroll-button:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.scroll-button {
|
||||
font-size: var(--base-font-size);
|
||||
margin-left: var(--margin-tiny);
|
||||
margin-top: var(--margin-small);
|
||||
}
|
||||
|
||||
|
||||
.left {
|
||||
flex: 1;
|
||||
background-color: var(--bg-color);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.right {
|
||||
flex: 4;
|
||||
background-color: var(--bg-color);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
#svg-container {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.panel {
|
||||
background-color: var(--nav-bg-color);
|
||||
border: 1px solid var(--border-color);
|
||||
padding: 10px;
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.card-header,
|
||||
.card-body {
|
||||
background-color: var(--bg-color);
|
||||
border: 1px solid var(--border-color);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: var(--nav-bg-color);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
li.svg-name {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
#dropdown-menu:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* remove bootstrap css confilcts */
|
||||
*,
|
||||
::after,
|
||||
::before {
|
||||
box-sizing: unset;
|
||||
}
|
||||
|
||||
.btn,
|
||||
.btn:hover {
|
||||
color: var(--nav-link-hover-color);
|
||||
}
|
||||
@@ -1,13 +1,17 @@
|
||||
:root {
|
||||
--font-family: Arial, sans-serif;
|
||||
--base-font-size: 16px;
|
||||
--margin-tiny: 0.125rem;
|
||||
--margin-small: 0.625rem;
|
||||
--margin-medium: 1.25rem;
|
||||
--margin-large: 2.5rem;
|
||||
--padding-small: 0.125rem;
|
||||
--padding-tiny: 0.125rem;
|
||||
--padding-small: 0.625rem;
|
||||
--padding-medium: 1rem;
|
||||
--font-size-small: 1rem;
|
||||
--font-size-large: 1.2rem;
|
||||
--logo-height: 2.5rem;
|
||||
--nav-height: 2.5rem;
|
||||
--folder-collapse-font-size: 0.75rem;
|
||||
--folder-collapse-font-family: Courier, "Courier New", monospace;
|
||||
--box-shadow: 0 0 0.7rem #5f5f5f66;
|
||||
@@ -31,6 +35,7 @@
|
||||
--group-item-bg-color: #4b4b4b;
|
||||
--input-bg-color: #3b3b3b;
|
||||
--input-border-color: #000;
|
||||
--details-border-color: #464444;
|
||||
}
|
||||
|
||||
:root.light {
|
||||
@@ -42,6 +47,7 @@
|
||||
--nav-link-color: #000000;
|
||||
--nav-link-hover-color: #38a63d;
|
||||
--warning-color: #FF4500;
|
||||
--details-border-color: #222;
|
||||
}
|
||||
|
||||
html {
|
||||
@@ -53,9 +59,13 @@ body {
|
||||
color: var(--primary-text-color);
|
||||
margin: 0;
|
||||
font-family: var(--font-family);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
#container {
|
||||
flex: 1;
|
||||
margin-top: var(--margin-medium);
|
||||
margin-left: var(--margin-small);
|
||||
margin-right: var(--margin-small);
|
||||
@@ -107,7 +117,7 @@ nav ul li a.active {
|
||||
.warning {
|
||||
color: var(--warning-color);
|
||||
margin-bottom: var(--margin-small);
|
||||
padding: var(--padding-small);
|
||||
padding: var(--padding-tiny);
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -141,6 +151,87 @@ nav ul li a.active {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#client-list {
|
||||
position: absolute;
|
||||
top: calc(0 + var(--nav-height));
|
||||
overflow-y: auto;
|
||||
overflow: hidden;
|
||||
transition: opacity 0.3s ease-out, visibility 0.3s ease-out;
|
||||
border: 1px solid var(--table-border-color);
|
||||
background-color: var(--nav-bg-color);
|
||||
z-index: 1;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
#client-list.show {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
#connected-list-div {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.client {
|
||||
padding: var(--margin-small);
|
||||
border-bottom: 1px solid var(--table-border-color);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.client-details {
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
padding-left: var(--padding-small);
|
||||
transition: max-height 0.2s ease-out, padding 0.2s ease-out, opacity 0.1s ease-out;
|
||||
background-color: var(--nav-bg-color);
|
||||
margin-top: var(--margin-tiny);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.client-details.show {
|
||||
max-height: none;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.client-detail {
|
||||
border-bottom: 1px solid var(--table-border-color);
|
||||
padding: var(--padding-small);
|
||||
}
|
||||
|
||||
#show-connected-button {
|
||||
width: auto;
|
||||
background-color: var(--bg-color);
|
||||
border: none;
|
||||
font-size: var(--base-font-size);
|
||||
}
|
||||
|
||||
#show-connected-button:hover,
|
||||
.scroll-button:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.scroll-button {
|
||||
font-size: var(--base-font-size);
|
||||
margin-left: var(--margin-tiny);
|
||||
margin-top: var(--margin-small);
|
||||
}
|
||||
|
||||
footer {
|
||||
background-color: var(--nav-bg-color);
|
||||
text-align: right;
|
||||
padding: var(--padding-tiny);
|
||||
border-top: 1px solid var(--nav-border-color);
|
||||
}
|
||||
|
||||
footer p {
|
||||
margin: 0;
|
||||
color: var(--text-color);
|
||||
margin-right: var(--margin-small);
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.gantt-info {
|
||||
margin: 0.5rem;
|
||||
font-size: var(--font-size-small);
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
:root {
|
||||
--font-family: Arial, sans-serif;
|
||||
--base-font-size: 16px;
|
||||
--margin-tiny: 0.125rem;
|
||||
--margin-small: 0.625rem;
|
||||
--margin-medium: 1.25rem;
|
||||
--margin-large: 2.5rem;
|
||||
--padding-small: 0.125rem;
|
||||
--padding-tiny: 0.125rem;
|
||||
--padding-small: 0.625rem;
|
||||
--padding-medium: 1rem;
|
||||
--font-size-large: 1.2rem;
|
||||
--logo-height: 2.5rem;
|
||||
--nav-height: 2.5rem;
|
||||
}
|
||||
|
||||
:root.dark {
|
||||
@@ -18,6 +22,7 @@
|
||||
--nav-link-color: #fff;
|
||||
--nav-link-hover-color: #3fb449;
|
||||
--warning-color: #FFDB8F;
|
||||
--table-border-color: #464444;
|
||||
}
|
||||
|
||||
:root.light {
|
||||
@@ -29,6 +34,7 @@
|
||||
--nav-link-color: #000000;
|
||||
--nav-link-hover-color: #38a63d;
|
||||
--warning-color: #FF4500;
|
||||
--table-border-color: #222;
|
||||
}
|
||||
|
||||
html {
|
||||
@@ -40,29 +46,26 @@ body {
|
||||
color: var(--text-color);
|
||||
margin: 0;
|
||||
font-family: var(--font-family);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
#container {
|
||||
flex: 1;
|
||||
margin-top: var(--margin-medium);
|
||||
margin-left: var(--margin-small);
|
||||
margin-right: var(--margin-small);
|
||||
margin-bottom: var(--margin-medium);
|
||||
}
|
||||
|
||||
.table-container {
|
||||
margin-bottom: var(--margin-large);
|
||||
}
|
||||
|
||||
.csv-table {
|
||||
margin-top: var(--margin-small);
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
nav {
|
||||
background-color: var(--nav-bg-color);
|
||||
height: var(--nav-height);
|
||||
padding: var(--padding-medium) 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -102,7 +105,7 @@ nav ul li a.active {
|
||||
.warning {
|
||||
color: var(--warning-color);
|
||||
margin-bottom: var(--margin-small);
|
||||
padding: var(--padding-small);
|
||||
padding: var(--padding-tiny);
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -118,6 +121,95 @@ nav ul li a.active {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#client-list {
|
||||
position: absolute;
|
||||
top: calc(0 + var(--nav-height));
|
||||
overflow-y: auto;
|
||||
overflow: hidden;
|
||||
transition: opacity 0.3s ease-out, visibility 0.3s ease-out;
|
||||
border: 1px solid var(--table-border-color);
|
||||
background-color: var(--nav-bg-color);
|
||||
z-index: 1;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
#client-list.show {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
#connected-list-div {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.client {
|
||||
padding: var(--margin-small);
|
||||
border-bottom: 1px solid var(--table-border-color);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.client-details {
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
padding-left: var(--padding-small);
|
||||
transition: max-height 0.2s ease-out, padding 0.2s ease-out, opacity 0.1s ease-out;
|
||||
background-color: var(--nav-bg-color);
|
||||
margin-top: var(--margin-tiny);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.client-details.show {
|
||||
max-height: none;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.client-detail {
|
||||
border-bottom: 1px solid var(--table-border-color);
|
||||
padding: var(--padding-small);
|
||||
}
|
||||
|
||||
#show-connected-button {
|
||||
width: auto;
|
||||
background-color: var(--bg-color);
|
||||
border: none;
|
||||
font-size: var(--base-font-size);
|
||||
}
|
||||
|
||||
#show-connected-button:hover,
|
||||
.scroll-button:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.scroll-button {
|
||||
font-size: var(--base-font-size);
|
||||
margin-left: var(--margin-tiny);
|
||||
margin-top: var(--margin-small);
|
||||
}
|
||||
|
||||
footer {
|
||||
background-color: var(--nav-bg-color);
|
||||
text-align: right;
|
||||
padding: var(--padding-tiny);
|
||||
border-top: 1px solid var(--nav-border-color);
|
||||
}
|
||||
|
||||
footer p {
|
||||
margin: 0;
|
||||
color: var(--text-color);
|
||||
margin-right: var(--margin-small);
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
margin-bottom: var(--margin-large);
|
||||
}
|
||||
|
||||
.csv-table {
|
||||
margin-top: var(--margin-small);
|
||||
}
|
||||
|
||||
/* ------------ Overwriting Tabulator CSS rules ------------ */
|
||||
|
||||
:root.light .tabulator-header,
|
||||
@@ -127,6 +219,6 @@ nav ul li a.active {
|
||||
}
|
||||
|
||||
:root.light .tabulator {
|
||||
border-top: 1px solid #222;
|
||||
border-bottom: 2px solid #222;
|
||||
border-top: 1px solid var(--table-border-color);
|
||||
border-bottom: 2px solid var(--table-border-color);
|
||||
}
|
||||
@@ -0,0 +1,263 @@
|
||||
// keeps track of blenders connected
|
||||
const connectedClients = {};
|
||||
const allDrawings = {};
|
||||
let socket;
|
||||
|
||||
// Document ready function
|
||||
$(document).ready(function () {
|
||||
var systemTheme = window.matchMedia("(prefers-color-scheme: light)").matches
|
||||
? "light"
|
||||
: "dark";
|
||||
var theme = localStorage.getItem("theme") || systemTheme;
|
||||
setTheme(theme);
|
||||
|
||||
$("#dropdown-menu").change(function () {
|
||||
const blenderId = $(this).attr("id");
|
||||
const ifcFile = $(this).val();
|
||||
displayDrawingsNames(blenderId, ifcFile);
|
||||
});
|
||||
|
||||
connectSocket();
|
||||
});
|
||||
|
||||
// Function to connect to Socket.IO server
|
||||
function connectSocket() {
|
||||
const url = "ws://localhost:" + SOCKET_PORT + "/web";
|
||||
socket = io(url);
|
||||
console.log("socket: ", socket);
|
||||
|
||||
// Register socket event handlers
|
||||
socket.on("blender_connect", handleBlenderConnect);
|
||||
socket.on("blender_disconnect", handleBlenderDisconnect);
|
||||
socket.on("connect", handleWebConnect);
|
||||
socket.on("drawings_data", handleDrawingsData);
|
||||
socket.on("svg_data", handleSvgData);
|
||||
// socket.on("default_data", handleDefaultData);
|
||||
}
|
||||
|
||||
// function used to get drawings data from blenderbim
|
||||
function handleWebConnect() {
|
||||
const msg = {
|
||||
sourcePage: "drawings",
|
||||
operator: {
|
||||
type: "getDrawings",
|
||||
},
|
||||
};
|
||||
socket.emit("web_operator", msg);
|
||||
}
|
||||
|
||||
// Function to handle 'blender_connect' event
|
||||
function handleBlenderConnect(blenderId) {
|
||||
console.log("blender_connect: ", blenderId);
|
||||
if (!connectedClients.hasOwnProperty(blenderId)) {
|
||||
connectedClients[blenderId] = {
|
||||
shown: false,
|
||||
ifc_file: "",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Function to handle 'blender_disconnect' event
|
||||
function handleBlenderDisconnect(blenderId) {
|
||||
console.log("blender_disconnect: ", blenderId);
|
||||
if (connectedClients.hasOwnProperty(blenderId)) {
|
||||
delete connectedClients[blenderId];
|
||||
// remove(blenderId);
|
||||
}
|
||||
|
||||
$("#blender-count").text(function (i, text) {
|
||||
return parseInt(text, 10) - 1;
|
||||
});
|
||||
}
|
||||
|
||||
function handleDrawingsData(data) {
|
||||
const blenderId = data["blenderId"];
|
||||
|
||||
console.log(data);
|
||||
|
||||
const filename = data["data"]["ifc_file"];
|
||||
const drawings = data["data"]["drawings_data"]["drawings"];
|
||||
const sheets = data["data"]["drawings_data"]["sheets"];
|
||||
|
||||
allDrawings[filename] = { drawings: drawings, sheets: sheets };
|
||||
|
||||
console.log(connectedClients);
|
||||
|
||||
if (connectedClients.hasOwnProperty(blenderId)) {
|
||||
if (!connectedClients[blenderId].shown) {
|
||||
connectedClients[blenderId] = {
|
||||
shown: true,
|
||||
ifc_file: filename,
|
||||
};
|
||||
addSelectOption(blenderId, filename);
|
||||
} else {
|
||||
// update(blenderId, data, filename);
|
||||
}
|
||||
} else {
|
||||
connectedClients[blenderId] = {
|
||||
shown: true,
|
||||
ifc_file: filename,
|
||||
};
|
||||
addSelectOption(blenderId, filename);
|
||||
}
|
||||
}
|
||||
|
||||
function handleSvgData(data) {
|
||||
const svgContainer = document.getElementById("svg-container");
|
||||
svgContainer.innerHTML = "";
|
||||
svgContainer.style.backgroundColor = "white";
|
||||
|
||||
const dom = SVG(svgContainer).svg(data);
|
||||
const svgElement = dom.node.children[0];
|
||||
|
||||
// to make the svg not grow outside the svg container
|
||||
svgElement.setAttribute("width", "100%");
|
||||
svgElement.setAttribute("height", "100%");
|
||||
svgElement.style.maxWidth = "100%";
|
||||
svgElement.style.maxHeight = "100%";
|
||||
svgElement.style.width = "100%";
|
||||
svgElement.style.height = "100%";
|
||||
svgElement.style.boxSizing = "border-box"; // Ensure padding and borders are included in the element's total width and height
|
||||
|
||||
const panZoomControls = svgPanZoom(svgElement, {
|
||||
zoomEnabled: true,
|
||||
controlIconsEnabled: true,
|
||||
fit: true,
|
||||
center: true,
|
||||
});
|
||||
}
|
||||
|
||||
function addSelectOption(blenderId, filename) {
|
||||
const filenameOption = $("<option></option>")
|
||||
.attr("id", "blender-" + blenderId)
|
||||
.val(filename)
|
||||
.text(filename);
|
||||
$("#dropdown-menu").append(filenameOption);
|
||||
}
|
||||
|
||||
function displayDrawingsNames(blenderId, ifcFile) {
|
||||
drawings = allDrawings[ifcFile].drawings;
|
||||
sheets = allDrawings[ifcFile].sheets;
|
||||
|
||||
function createSvgNames(text, index, type) {
|
||||
var label = $("<li></lio>")
|
||||
.text(text)
|
||||
.attr("id", type + "-" + index)
|
||||
.addClass("svg-name")
|
||||
.click(function () {
|
||||
const id = $(this).attr("id").split("-");
|
||||
const index = parseInt(id[1]);
|
||||
const type = id[0];
|
||||
const ifcFile = $("#dropdown-menu").val();
|
||||
|
||||
var path = "";
|
||||
|
||||
if (type === "drawing") {
|
||||
path = allDrawings[ifcFile].drawings[index].path;
|
||||
} else if (type === "sheet") {
|
||||
path = allDrawings[ifcFile].sheets[index].path;
|
||||
}
|
||||
|
||||
const msg = {
|
||||
path: path,
|
||||
};
|
||||
console.log(msg);
|
||||
socket.emit("get_svg", msg);
|
||||
});
|
||||
|
||||
if (type === "drawing") $("#drawings-sub-panel").append(label);
|
||||
else if (type === "sheet") $("#sheets-sub-panel").append(label);
|
||||
}
|
||||
|
||||
$("#drawings-sub-panel").empty();
|
||||
drawings.forEach(function (drawing, index) {
|
||||
createSvgNames(drawing.name, index, "drawing");
|
||||
});
|
||||
|
||||
$("#sheets-sub-panel").empty();
|
||||
sheets.forEach(function (sheet, index) {
|
||||
createSvgNames(sheet.name, index, "sheet");
|
||||
});
|
||||
}
|
||||
|
||||
function setTheme(theme) {
|
||||
if (theme === "light") {
|
||||
$("html").removeClass("dark").addClass("light");
|
||||
$("#toggle-theme").html('<i class="fas fa-sun"></i>');
|
||||
} else {
|
||||
$("html").removeClass("light").addClass("dark");
|
||||
$("#toggle-theme").html('<i class="fas fa-moon"></i>');
|
||||
}
|
||||
localStorage.setItem("theme", theme);
|
||||
}
|
||||
|
||||
function toggleTheme() {
|
||||
if ($("html").hasClass("dark")) {
|
||||
setTheme("light");
|
||||
} else {
|
||||
setTheme("dark");
|
||||
}
|
||||
}
|
||||
|
||||
function toggleClientList() {
|
||||
var clientList = $("#client-list");
|
||||
|
||||
if (clientList.hasClass("show")) {
|
||||
clientList.removeClass("show");
|
||||
return;
|
||||
}
|
||||
|
||||
clientList.empty();
|
||||
|
||||
$.each(connectedClients, function (id, client) {
|
||||
if (!client.shown) return;
|
||||
|
||||
const dropdownIcon = $("<i>")
|
||||
.addClass("fas fa-chevron-down")
|
||||
.css("margin-left", "0.5rem");
|
||||
|
||||
const clientDiv = $("<div>").addClass("client").text(client.ifc_file);
|
||||
|
||||
clientDiv.append(dropdownIcon);
|
||||
|
||||
const clientDetailsDiv = $("<div>").addClass("client-details");
|
||||
|
||||
if (id) {
|
||||
const clientId = $("<div>")
|
||||
.addClass("client-detail")
|
||||
.text(`Blender ID: ${id}`);
|
||||
clientDetailsDiv.append(clientId);
|
||||
}
|
||||
|
||||
if (client.headers && client.headers.length) {
|
||||
const clientHeaders = $("<div>")
|
||||
.addClass("client-detail")
|
||||
.text(`Table Headers: ${client.headers.join(", ")}`);
|
||||
|
||||
const scrollButton = $("<button>")
|
||||
.addClass("scroll-button")
|
||||
.text("Scroll to Table")
|
||||
.on("click", function () {
|
||||
$("html, body").animate(
|
||||
{
|
||||
scrollTop: $("#table-" + id).offset().top,
|
||||
},
|
||||
600
|
||||
);
|
||||
clientList.removeClass("show");
|
||||
});
|
||||
|
||||
clientDetailsDiv.append(clientHeaders);
|
||||
clientDetailsDiv.append(scrollButton);
|
||||
}
|
||||
|
||||
clientDiv.append(clientDetailsDiv);
|
||||
|
||||
clientDiv.on("click", function () {
|
||||
clientDetailsDiv.toggleClass("show");
|
||||
});
|
||||
|
||||
clientList.append(clientDiv);
|
||||
});
|
||||
clientList.addClass("show");
|
||||
}
|
||||
@@ -60,6 +60,10 @@ function handleBlenderDisconnect(blenderId) {
|
||||
delete connectedClients[blenderId];
|
||||
removeGanttElement(blenderId);
|
||||
}
|
||||
|
||||
$("#blender-count").text(function (i, text) {
|
||||
return parseInt(text, 10) - 1;
|
||||
});
|
||||
}
|
||||
|
||||
// Function to handle 'gantt_data' event
|
||||
@@ -77,6 +81,7 @@ function handleGanttData(data) {
|
||||
if (!connectedClients[blenderId].shown) {
|
||||
connectedClients[blenderId] = {
|
||||
shown: true,
|
||||
ifc_file: filename,
|
||||
ganttTasks: ganttTasks,
|
||||
workSchedule: ganttWorkSched,
|
||||
};
|
||||
@@ -89,6 +94,7 @@ function handleGanttData(data) {
|
||||
} else {
|
||||
connectedClients[blenderId] = {
|
||||
shown: true,
|
||||
ifc_file: filename,
|
||||
ganttTasks: ganttTasks,
|
||||
workSchedule: ganttWorkSched,
|
||||
};
|
||||
@@ -105,6 +111,10 @@ function handleDefaultData(data) {
|
||||
|
||||
// Function to add a new gantt with data and filename
|
||||
function addGanttElement(blenderId, tasks, workSched, filename) {
|
||||
$("#blender-count").text(function (i, text) {
|
||||
return parseInt(text, 10) + 1;
|
||||
});
|
||||
|
||||
const ganttContainer = $("<div></div>")
|
||||
.addClass("gantt-container")
|
||||
.attr("id", "container-" + blenderId);
|
||||
@@ -382,3 +392,75 @@ function toggleTheme() {
|
||||
setTheme("dark");
|
||||
}
|
||||
}
|
||||
|
||||
function toggleClientList() {
|
||||
var clientList = $("#client-list");
|
||||
|
||||
if (clientList.hasClass("show")) {
|
||||
clientList.removeClass("show");
|
||||
return;
|
||||
}
|
||||
|
||||
clientList.empty();
|
||||
|
||||
$.each(connectedClients, function (id, client) {
|
||||
if (!client.shown) return;
|
||||
|
||||
const dropdownIcon = $("<i>")
|
||||
.addClass("fas fa-chevron-down")
|
||||
.css("margin-left", "0.5rem");
|
||||
|
||||
const clientDiv = $("<div>").addClass("client").text(client.ifc_file);
|
||||
|
||||
clientDiv.append(dropdownIcon);
|
||||
|
||||
const clientDetailsDiv = $("<div>").addClass("client-details");
|
||||
|
||||
if (id) {
|
||||
const clientId = $("<div>")
|
||||
.addClass("client-detail")
|
||||
.text(`Blender ID: ${id}`);
|
||||
clientDetailsDiv.append(clientId);
|
||||
}
|
||||
|
||||
if (client.workSchedule && client.gantt) {
|
||||
const clientScheduleName = $("<div>")
|
||||
.addClass("client-detail")
|
||||
.text(`Schedule Name: ${client.workSchedule.Name}`);
|
||||
|
||||
const clientScheduleDate = $("<div>")
|
||||
.addClass("client-detail")
|
||||
.text(
|
||||
`Schedule Date: ${new Date(
|
||||
client.workSchedule.CreationDate
|
||||
).toLocaleDateString()}`
|
||||
);
|
||||
|
||||
const scrollButton = $("<button>")
|
||||
.addClass("scroll-button")
|
||||
.text("Scroll to Gantt Chart")
|
||||
.on("click", function () {
|
||||
$("html, body").animate(
|
||||
{
|
||||
scrollTop: $("#gantt-" + id).offset().top,
|
||||
},
|
||||
600
|
||||
);
|
||||
clientList.removeClass("show");
|
||||
});
|
||||
|
||||
clientDetailsDiv.append(clientScheduleName);
|
||||
clientDetailsDiv.append(clientScheduleDate);
|
||||
clientDetailsDiv.append(scrollButton);
|
||||
}
|
||||
|
||||
clientDiv.append(clientDetailsDiv);
|
||||
|
||||
clientDiv.on("click", function () {
|
||||
clientDetailsDiv.toggleClass("show");
|
||||
});
|
||||
|
||||
clientList.append(clientDiv);
|
||||
});
|
||||
clientList.addClass("show");
|
||||
}
|
||||
|
||||
@@ -42,6 +42,10 @@ function handleBlenderDisconnect(blenderId) {
|
||||
delete connectedClients[blenderId];
|
||||
removeTableElement(blenderId);
|
||||
}
|
||||
|
||||
$("#blender-count").text(function (i, text) {
|
||||
return parseInt(text, 10) - 1;
|
||||
});
|
||||
}
|
||||
|
||||
// Function to handle 'csv_data' event
|
||||
@@ -81,6 +85,10 @@ function handleDefaultData(data) {
|
||||
|
||||
// Function to add a new table with data and filename
|
||||
function addTableElement(blenderId, csvData, filename) {
|
||||
$("#blender-count").text(function (i, text) {
|
||||
return parseInt(text, 10) + 1;
|
||||
});
|
||||
|
||||
// store headers of the csv data
|
||||
const firstLine = csvData.indexOf("\n");
|
||||
const csvHeaders = csvData.substring(0, firstLine).split(",");
|
||||
@@ -274,3 +282,66 @@ function toggleTheme() {
|
||||
setTheme("dark");
|
||||
}
|
||||
}
|
||||
|
||||
function toggleClientList() {
|
||||
var clientList = $("#client-list");
|
||||
|
||||
if (clientList.hasClass("show")) {
|
||||
clientList.removeClass("show");
|
||||
return;
|
||||
}
|
||||
|
||||
clientList.empty();
|
||||
|
||||
$.each(connectedClients, function (id, client) {
|
||||
if (!client.shown) return;
|
||||
|
||||
const dropdownIcon = $("<i>")
|
||||
.addClass("fas fa-chevron-down")
|
||||
.css("margin-left", "0.5rem");
|
||||
|
||||
const clientDiv = $("<div>").addClass("client").text(client.ifc_file);
|
||||
|
||||
clientDiv.append(dropdownIcon);
|
||||
|
||||
const clientDetailsDiv = $("<div>").addClass("client-details");
|
||||
|
||||
if (id) {
|
||||
const clientId = $("<div>")
|
||||
.addClass("client-detail")
|
||||
.text(`Blender ID: ${id}`);
|
||||
clientDetailsDiv.append(clientId);
|
||||
}
|
||||
|
||||
if (client.headers && client.headers.length) {
|
||||
const clientHeaders = $("<div>")
|
||||
.addClass("client-detail")
|
||||
.text(`Table Headers: ${client.headers.join(", ")}`);
|
||||
|
||||
const scrollButton = $("<button>")
|
||||
.addClass("scroll-button")
|
||||
.text("Scroll to Table")
|
||||
.on("click", function () {
|
||||
$("html, body").animate(
|
||||
{
|
||||
scrollTop: $("#table-" + id).offset().top,
|
||||
},
|
||||
600
|
||||
);
|
||||
clientList.removeClass("show");
|
||||
});
|
||||
|
||||
clientDetailsDiv.append(clientHeaders);
|
||||
clientDetailsDiv.append(scrollButton);
|
||||
}
|
||||
|
||||
clientDiv.append(clientDetailsDiv);
|
||||
|
||||
clientDiv.on("click", function () {
|
||||
clientDetailsDiv.toggleClass("show");
|
||||
});
|
||||
|
||||
clientList.append(clientDiv);
|
||||
});
|
||||
clientList.addClass("show");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Web Client</title>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css"
|
||||
/>
|
||||
<link rel="stylesheet" href="/static/css/drawings.css" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.6.0/css/all.min.css"
|
||||
/>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="https://code.jquery.com/jquery-3.6.0.min.js"
|
||||
></script>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/svg.js/3.1.1/svg.min.js"
|
||||
></script>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="https://cdn.jsdelivr.net/npm/svg-pan-zoom@3.6.1/dist/svg-pan-zoom.min.js"
|
||||
></script>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"
|
||||
></script>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="https://cdn.socket.io/4.0.0/socket.io.min.js"
|
||||
></script>
|
||||
<script>
|
||||
var SOCKET_PORT = {{port}};
|
||||
</script>
|
||||
<script defer src="./static/js/drawings.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<nav>
|
||||
<img
|
||||
src="https://blenderbim.org/assets/images/blender/blender-logo.png"
|
||||
alt="Logo"
|
||||
class="logo"
|
||||
/>
|
||||
<ul>
|
||||
<li><a href="/">IFC Data</a></li>
|
||||
<li><a href="/gantt">Gantt Chart</a></li>
|
||||
<li><a href="/drawings" class="active">Drawings</a></li>
|
||||
</ul>
|
||||
<button id="toggle-theme" onclick="toggleTheme()">
|
||||
<i class="fas fa-moon"></i>
|
||||
</button>
|
||||
</nav>
|
||||
<div id="connected-list-div">
|
||||
<button id="show-connected-button" onclick="toggleClientList()">
|
||||
Connected Blenders:
|
||||
<span id="blender-count">0</span>
|
||||
<i class="fas fa-chevron-down"></i>
|
||||
</button>
|
||||
<div id="client-list"></div>
|
||||
</div>
|
||||
<div class="container-fluid d-flex" id="container">
|
||||
<div class="col-2 left">
|
||||
<!-- Dropdown menu -->
|
||||
<div class="dropdown mb-3">
|
||||
<label for="dropdown-menu">IFC File:</label>
|
||||
<select class="p-0 pl-2" id="dropdown-menu">
|
||||
<!-- IFC files will be dynamically populated here -->
|
||||
<option value="" selected disabled hidden>IFC File</option>
|
||||
</select>
|
||||
</div>
|
||||
<!-- Panel with sub-panels -->
|
||||
<div class="panel">
|
||||
<div id="accordion">
|
||||
<div class="card pb-2">
|
||||
<div class="card-header overflow-hidden" id="headingOne">
|
||||
<button
|
||||
class="btn p-0 w-100 text-left"
|
||||
data-toggle="collapse"
|
||||
data-target="#collapseOne"
|
||||
>
|
||||
Drawings
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="collapseOne" class="collapse show">
|
||||
<div class="card-body">
|
||||
<ul class="sub-panel pl-2" id="drawings-sub-panel">
|
||||
<!-- Drawings will be dynamically populated here -->
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card pb-2">
|
||||
<div class="card-header overflow-hidden" id="headingTwo">
|
||||
<button
|
||||
class="btn p-0 w-100 text-left"
|
||||
data-toggle="collapse"
|
||||
data-target="#collapseTwo"
|
||||
>
|
||||
Sheets
|
||||
</button>
|
||||
</div>
|
||||
<div id="collapseTwo" class="collapse show">
|
||||
<div class="card-body">
|
||||
<ul class="sub-panel pl-2" id="sheets-sub-panel">
|
||||
<!-- Sheets will be dynamically populated here -->
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-10 right pl-3">
|
||||
<div id="svg-container"></div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -34,11 +34,23 @@
|
||||
<ul>
|
||||
<li><a href="/">IFC Data</a></li>
|
||||
<li><a href="/gantt" class="active">Gantt Chart</a></li>
|
||||
<li><a href="/drawings">Drawings</a></li>
|
||||
</ul>
|
||||
<button id="toggle-theme" onclick="toggleTheme()">
|
||||
<i class="fas fa-moon"></i>
|
||||
</button>
|
||||
</nav>
|
||||
<div id="connected-list-div">
|
||||
<button id="show-connected-button" onclick="toggleClientList()">
|
||||
Connected Blenders:
|
||||
<span id="blender-count">0</span>
|
||||
<i class="fas fa-chevron-down"></i>
|
||||
</button>
|
||||
<div id="client-list"></div>
|
||||
</div>
|
||||
<div id="container"></div>
|
||||
<footer>
|
||||
<p>BlenderBIM Version: {{version}}</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -41,11 +41,23 @@
|
||||
<ul>
|
||||
<li><a href="/" class="active">IFC Data</a></li>
|
||||
<li><a href="/gantt">Gantt Chart</a></li>
|
||||
<li><a href="/drawings">Drawings</a></li>
|
||||
</ul>
|
||||
<button id="toggle-theme" onclick="toggleTheme()">
|
||||
<i class="fas fa-moon"></i>
|
||||
</button>
|
||||
</nav>
|
||||
<div id="connected-list-div">
|
||||
<button id="show-connected-button" onclick="toggleClientList()">
|
||||
Connected Blenders:
|
||||
<span id="blender-count">0</span>
|
||||
<i class="fas fa-chevron-down"></i>
|
||||
</button>
|
||||
<div id="client-list"></div>
|
||||
</div>
|
||||
<div id="container"></div>
|
||||
<footer>
|
||||
<p>BlenderBIM Version: {{version}}</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -24,7 +24,6 @@ import numpy as np
|
||||
import datetime
|
||||
import zipfile
|
||||
import tempfile
|
||||
import addon_utils
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.placement
|
||||
|
||||
@@ -18,10 +18,9 @@
|
||||
|
||||
import os
|
||||
import bpy
|
||||
import json
|
||||
import addon_utils
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.representation
|
||||
import ifcopenshell.util.unit
|
||||
import ifcopenshell.api.owner.settings
|
||||
import blenderbim.bim
|
||||
import blenderbim.tool as tool
|
||||
@@ -170,6 +169,11 @@ def subscribe_to(obj, data_path, callback):
|
||||
|
||||
|
||||
def refresh_ui_data():
|
||||
"""Refresh cached UI data.
|
||||
|
||||
Note that calling non-ifc-operators by itself doesn't refresh the UI data
|
||||
and it need to be refreshed manually if needed.
|
||||
"""
|
||||
from blenderbim.bim import modules
|
||||
|
||||
for name, value in modules.items():
|
||||
@@ -299,3 +303,6 @@ def load_post(scene):
|
||||
continue
|
||||
tool.Blender.override_scene_panel(panel)
|
||||
tool.Blender.setup_tabs()
|
||||
|
||||
if tool.Ifc.get() and bpy.data.is_saved:
|
||||
bpy.context.scene.BIMProperties.has_blend_warning = True
|
||||
|
||||
@@ -23,6 +23,7 @@ import math
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.attribute
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.unit
|
||||
from ifcopenshell.util.doc import get_attribute_doc, get_predefined_type_doc, get_property_doc
|
||||
from mathutils import geometry
|
||||
from mathutils import Vector
|
||||
@@ -50,6 +51,8 @@ def draw_attribute(attribute, layout, copy_operator=None):
|
||||
return
|
||||
if value_name == "enum_value":
|
||||
prop_with_search(layout, attribute, "enum_value", text=attribute.name)
|
||||
elif value_name == "filepath_value":
|
||||
attribute.filepath_value.layout_file_select(layout, filter_glob=attribute.filter_glob, text=attribute.name)
|
||||
elif attribute.name in ["ScheduleDuration", "ActualDuration", "FreeFloat", "TotalFloat"]:
|
||||
propis = bpy.context.scene.BIMWorkScheduleProperties
|
||||
for item in propis.durations_attributes:
|
||||
|
||||
@@ -551,7 +551,7 @@ class IfcImporter:
|
||||
for axis in axes:
|
||||
shape = tool.Loader.create_generic_shape(axis.AxisCurve)
|
||||
mesh = self.create_mesh(axis, shape)
|
||||
obj = bpy.data.objects.new(f"IfcGridAxis/{axis.AxisTag}", mesh)
|
||||
obj = bpy.data.objects.new(tool.Loader.get_name(axis), mesh)
|
||||
if tool.Blender.get_addon_preferences().lock_grids_on_import:
|
||||
obj.lock_location = (True, True, True)
|
||||
obj.lock_rotation = (True, True, True)
|
||||
@@ -781,7 +781,7 @@ class IfcImporter:
|
||||
mesh = bpy.data.meshes.new(mesh_name)
|
||||
mesh.from_pydata([mathutils.Vector(vertex) * self.unit_scale], [], [])
|
||||
|
||||
obj = bpy.data.objects.new("{}/{}".format(product.is_a(), product.Name), mesh)
|
||||
obj = bpy.data.objects.new(tool.Loader.get_name(product), mesh)
|
||||
self.set_matrix_world(obj, tool.Loader.apply_blender_offset_to_matrix_world(obj, placement_matrix))
|
||||
self.link_element(product, obj)
|
||||
|
||||
@@ -847,7 +847,7 @@ class IfcImporter:
|
||||
mesh.from_pydata(vertex_list, [], [])
|
||||
tool.Ifc.link(representation, mesh)
|
||||
|
||||
obj = bpy.data.objects.new("{}/{}".format(product.is_a(), product.Name), mesh)
|
||||
obj = bpy.data.objects.new(tool.Loader.get_name(product), mesh)
|
||||
self.set_matrix_world(obj, tool.Loader.apply_blender_offset_to_matrix_world(obj, placement_matrix))
|
||||
self.link_element(product, obj)
|
||||
return product
|
||||
@@ -1261,7 +1261,7 @@ class IfcImporter:
|
||||
def place_objects_in_collections(self) -> None:
|
||||
for ifc_definition_id, obj in self.added_data.items():
|
||||
if isinstance(obj, bpy.types.Object):
|
||||
tool.Collector.assign(obj)
|
||||
tool.Collector.assign(obj, should_clean_users_collection=False)
|
||||
|
||||
def is_curve_annotation(self, element: ifcopenshell.entity_instance) -> bool:
|
||||
object_type = element.ObjectType
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.element
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.aggregate as core
|
||||
@@ -167,7 +168,7 @@ class BIM_OT_add_aggregate(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Ifc,
|
||||
tool.Collector,
|
||||
tool.Spatial,
|
||||
structure_obj=tool.Ifc.get_object(current_container),
|
||||
container=current_container,
|
||||
element_obj=aggregate,
|
||||
)
|
||||
core.assign_object(tool.Ifc, tool.Aggregate, tool.Collector, relating_obj=aggregate, related_obj=obj)
|
||||
|
||||
@@ -21,6 +21,7 @@ import json
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.guid
|
||||
import ifcopenshell.util.element
|
||||
import blenderbim.bim.helper
|
||||
import blenderbim.bim.handler
|
||||
import blenderbim.tool as tool
|
||||
@@ -36,7 +37,7 @@ class EnableEditingAttributes(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
obj = bpy.data.objects.get(self.obj)
|
||||
obj = bpy.data.objects[self.obj]
|
||||
props = obj.BIMAttributeProperties
|
||||
props.attributes.clear()
|
||||
|
||||
|
||||
@@ -18,14 +18,12 @@
|
||||
|
||||
import os
|
||||
import bpy
|
||||
import json
|
||||
import string
|
||||
import random
|
||||
import shutil
|
||||
import requests
|
||||
import tempfile
|
||||
import datetime
|
||||
import addon_utils
|
||||
import blenderbim.tool as tool
|
||||
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import numpy as np
|
||||
import tempfile
|
||||
import webbrowser
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.geolocation
|
||||
import ifcopenshell.util.unit
|
||||
import blenderbim.bim.module.bcf.prop as bcf_prop
|
||||
from . import bcfstore
|
||||
|
||||
@@ -60,10 +60,14 @@ class SearchBSDDClass(bpy.types.Operator):
|
||||
class GetBSDDClassificationProperties(bpy.types.Operator):
|
||||
bl_idname = "bim.get_bsdd_classification_properties"
|
||||
bl_label = "Search bSDD Class Properties"
|
||||
bl_description = "Search for bSDD class properties for the currently selected class"
|
||||
bl_description = (
|
||||
"Search for bSDD class properties for the currently selected class. All non-IFC properties are skipped"
|
||||
)
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
properties_found = core.get_class_properties(bsdd.Client(), tool.Bsdd)
|
||||
self.report({"INFO"}, f"{properties_found} properties found for the active classification.")
|
||||
pset_data = core.get_class_properties(bsdd.Client(), tool.Bsdd)
|
||||
psets_found = len(pset_data)
|
||||
props_found = sum(len(pset) for pset in pset_data.values())
|
||||
self.report({"INFO"}, f"{psets_found} psets found ({props_found} props) for the active classification.")
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -43,7 +43,6 @@ class BSDDDomain(PropertyGroup):
|
||||
class BSDDClassification(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
reference_code: StringProperty(name="Reference Code")
|
||||
description: StringProperty(name="Description")
|
||||
uri: StringProperty(name="Namespace URI")
|
||||
domain_name: StringProperty(name="Domain Name")
|
||||
domain_namespace_uri: StringProperty(name="Domain Namespace URI")
|
||||
|
||||
@@ -68,8 +68,7 @@ class BIM_PT_bsdd(Panel):
|
||||
row = box.row(align=True)
|
||||
row.label(text="Version")
|
||||
row.label(text=selected_domain.version)
|
||||
box.label(text="URI:")
|
||||
box.label(text=selected_domain.uri)
|
||||
box.operator("bim.open_uri", text="Open bSDD In Browser", icon="URL").uri = selected_domain.uri
|
||||
|
||||
else:
|
||||
row = self.layout.row()
|
||||
@@ -79,14 +78,18 @@ class BIM_PT_bsdd(Panel):
|
||||
class BIM_UL_bsdd_domains(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if item:
|
||||
props = context.scene.BIMBSDDProperties
|
||||
row = layout.row(align=True)
|
||||
if item.status != "Active":
|
||||
row.label(text=f"{item.name} ({item.organization_name_owner}) - {item.status}", icon="ERROR")
|
||||
else:
|
||||
row.label(text=f"{item.name} ({item.organization_name_owner})")
|
||||
op = row.operator("bim.set_active_bsdd_domain", text="", icon="RESTRICT_SELECT_OFF")
|
||||
op.name = item.name
|
||||
op.uri = item.uri
|
||||
if item.uri == props.active_uri:
|
||||
row.label(text="", icon="URL")
|
||||
else:
|
||||
op = row.operator("bim.set_active_bsdd_domain", text="", icon="RESTRICT_SELECT_OFF")
|
||||
op.name = item.name
|
||||
op.uri = item.uri
|
||||
|
||||
|
||||
class BIM_UL_bsdd_classifications(UIList):
|
||||
@@ -95,3 +98,4 @@ class BIM_UL_bsdd_classifications(UIList):
|
||||
row = layout.row(align=True)
|
||||
row.label(text=item.reference_code)
|
||||
row.label(text=item.name)
|
||||
row.operator("bim.open_uri", text="", icon="URL").uri = item.uri
|
||||
|
||||
@@ -22,7 +22,6 @@ import bpy
|
||||
import time
|
||||
import random
|
||||
import logging
|
||||
import platform
|
||||
import subprocess
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
@@ -30,6 +29,7 @@ import ifcopenshell.geom
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.placement
|
||||
import ifcopenshell.util.representation
|
||||
import ifcopenshell.util.unit
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.debug as core
|
||||
import blenderbim.bim.handler
|
||||
|
||||
@@ -21,6 +21,7 @@ import bpy
|
||||
import json
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.representation
|
||||
import ifcopenshell.util.unit
|
||||
import blenderbim.tool as tool
|
||||
from pathlib import Path
|
||||
from typing import Any, Union
|
||||
|
||||
@@ -25,6 +25,7 @@ import bmesh
|
||||
import shapely
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.unit
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.bim.module.drawing.helper as helper
|
||||
from math import pi, sin, cos, tan, acos, atan, degrees, radians, ceil
|
||||
|
||||
@@ -1472,12 +1472,9 @@ class ActivateModel(bpy.types.Operator):
|
||||
|
||||
CutDecorator.uninstall()
|
||||
|
||||
# save current visibility statuses for Views and Types collections
|
||||
# save current visibility statuses
|
||||
visibility_status: dict[bpy.types.Object, bool] = {}
|
||||
for col in bpy.data.collections["Views"].children:
|
||||
for obj in col.objects:
|
||||
visibility_status[obj] = obj.hide_get()
|
||||
for obj in bpy.data.collections["Types"].objects:
|
||||
for obj in bpy.data.objects:
|
||||
visibility_status[obj] = obj.hide_get()
|
||||
|
||||
if not bpy.app.background:
|
||||
@@ -1508,6 +1505,7 @@ class ActivateModel(bpy.types.Operator):
|
||||
obj.hide_set(hide_status)
|
||||
|
||||
tool.Blender.update_viewport()
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -1515,46 +1513,56 @@ class ActivateDrawing(bpy.types.Operator):
|
||||
bl_idname = "bim.activate_drawing"
|
||||
bl_label = "Activate Drawing"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Activates the selected drawing view.\n\n" + "ALT+CLICK to keep the viewport position"
|
||||
bl_description = "Activates the selected drawing view.\n\n" + "ALT+CLICK to keep the viewport position.\n\n" + "SHIFT+CLICK activiate drawing without turning objects on/off."
|
||||
|
||||
drawing: bpy.props.IntProperty()
|
||||
camera_view_point: bpy.props.BoolProperty(name="Camera View Point", default=True, options={"SKIP_SAVE"})
|
||||
switch_camera_only: bpy.props.BoolProperty(name="Only Changes Camera View", default=False, options={"SKIP_SAVE"})
|
||||
|
||||
def invoke(self, context, event):
|
||||
# keep the viewport position on alt+click
|
||||
# make sure to use SKIP_SAVE on property, otherwise it might get stuck
|
||||
if event.type == "LEFTMOUSE" and event.alt:
|
||||
self.camera_view_point = False
|
||||
# Only activates the camera view on shift+click. Does not turn on/off objects in scene
|
||||
if event.type == "LEFTMOUSE" and event.shift:
|
||||
self.switch_camera_only = True
|
||||
return self.execute(context)
|
||||
|
||||
def execute(self, context):
|
||||
if bpy.context.scene.DocProperties.is_editing_drawings == False:
|
||||
bpy.ops.bim.load_drawings()
|
||||
drawing = tool.Ifc.get().by_id(self.drawing)
|
||||
dprops = bpy.context.scene.DocProperties
|
||||
|
||||
if not self.camera_view_point:
|
||||
viewport_position = tool.Blender.get_viewport_position()
|
||||
if self.switch_camera_only:
|
||||
camera = tool.Drawing.import_drawing(drawing)
|
||||
tool.Blender.activate_camera(camera)
|
||||
else:
|
||||
if not self.camera_view_point:
|
||||
viewport_position = tool.Blender.get_viewport_position()
|
||||
|
||||
core.activate_drawing_view(tool.Ifc, tool.Blender, tool.Drawing, drawing=drawing)
|
||||
core.activate_drawing_view(tool.Ifc, tool.Blender, tool.Drawing, drawing=drawing)
|
||||
|
||||
if not self.camera_view_point:
|
||||
tool.Blender.set_viewport_position(viewport_position)
|
||||
if not self.camera_view_point:
|
||||
tool.Blender.set_viewport_position(viewport_position)
|
||||
|
||||
dprops.active_drawing_id = self.drawing
|
||||
# reset DrawingsData to reload_drawing_styles work correctly
|
||||
DrawingsData.is_loaded = False
|
||||
dprops.drawing_styles.clear()
|
||||
if ifcopenshell.util.element.get_pset(drawing, "EPset_Drawing", "HasUnderlay"):
|
||||
bpy.ops.bim.reload_drawing_styles()
|
||||
bpy.ops.bim.activate_drawing_style()
|
||||
core.sync_references(tool.Ifc, tool.Collector, tool.Drawing, drawing=tool.Ifc.get().by_id(self.drawing))
|
||||
CutDecorator.install(context)
|
||||
tool.Drawing.show_decorations()
|
||||
dprops.active_drawing_id = self.drawing
|
||||
# reset DrawingsData to reload_drawing_styles work correctly
|
||||
DrawingsData.is_loaded = False
|
||||
dprops.drawing_styles.clear()
|
||||
if ifcopenshell.util.element.get_pset(drawing, "EPset_Drawing", "HasUnderlay"):
|
||||
bpy.ops.bim.reload_drawing_styles()
|
||||
bpy.ops.bim.activate_drawing_style()
|
||||
core.sync_references(tool.Ifc, tool.Collector, tool.Drawing, drawing=tool.Ifc.get().by_id(self.drawing))
|
||||
CutDecorator.install(context)
|
||||
tool.Drawing.show_decorations()
|
||||
|
||||
# Save drawing bounds to the .ifc file
|
||||
camera = context.scene.camera
|
||||
camera_props = camera.data.BIMCameraProperties
|
||||
if camera_props.update_representation(camera):
|
||||
bpy.ops.bim.update_representation(obj=camera.name, ifc_representation_class="")
|
||||
# Save drawing bounds to the .ifc file
|
||||
camera = context.scene.camera
|
||||
camera_props = camera.data.BIMCameraProperties
|
||||
if camera_props.update_representation(camera):
|
||||
bpy.ops.bim.update_representation(obj=camera.name, ifc_representation_class="")
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@ import bpy
|
||||
import json
|
||||
import enum
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.element
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.drawing as core
|
||||
import blenderbim.bim.module.drawing.annotation as annotation
|
||||
|
||||
@@ -29,6 +29,7 @@ import ifcopenshell
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.representation
|
||||
import ifcopenshell.util.selector
|
||||
import ifcopenshell.util.unit
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.bim.module.drawing.helper as helper
|
||||
import blenderbim.bim.module.drawing.annotation as annotation
|
||||
|
||||
@@ -430,6 +430,19 @@ class BIM_PT_sheets(Panel):
|
||||
active_sheet = self.props.sheets[self.props.active_sheet_index]
|
||||
row = self.layout.row(align=True)
|
||||
row.alignment = "RIGHT"
|
||||
|
||||
if active_sheet.reference_type == "DRAWING":
|
||||
drawingnamesvg = active_sheet.name
|
||||
drawingname = drawingnamesvg.split('.')[0]
|
||||
ifc_file = tool.Ifc.get()
|
||||
ifc_annotations = ifc_file.by_type('IfcAnnotation')
|
||||
for annotation in ifc_annotations:
|
||||
if annotation.Name == drawingname:
|
||||
drawingid = annotation.id()
|
||||
drawing_button = row.row(align=True)
|
||||
op = drawing_button.operator("bim.activate_drawing", icon="OUTLINER_OB_CAMERA", text="")
|
||||
op.drawing = drawingid
|
||||
|
||||
row.operator("bim.edit_sheet", icon="GREASEPENCIL", text="")
|
||||
row.operator("bim.open_sheet", icon="URL", text="")
|
||||
row.operator("bim.add_drawing_to_sheet", icon="IMAGE_PLANE", text="")
|
||||
|
||||
@@ -22,7 +22,6 @@ from . import ui, prop, operator
|
||||
classes = (
|
||||
operator.ExecuteIfcFM,
|
||||
operator.ExecuteIfcFMFederate,
|
||||
operator.SelectFMIfcFile,
|
||||
operator.SelectFMSpreadsheetFiles,
|
||||
prop.BIMFMProperties,
|
||||
ui.BIM_PT_fm,
|
||||
|
||||
@@ -24,20 +24,6 @@ import logging
|
||||
import tempfile
|
||||
import ifcopenshell
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim.operator import MultipleFileSelector
|
||||
|
||||
|
||||
class SelectFMIfcFile(MultipleFileSelector):
|
||||
bl_idname = "bim.select_fm_ifc_file"
|
||||
bl_label = "Select FM IFC File"
|
||||
filename_ext = ".ifc"
|
||||
filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"})
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMFMProperties
|
||||
|
||||
self.update_props(props, "ifc_file", props.ifc_files)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ExecuteIfcFM(bpy.types.Operator):
|
||||
@@ -50,7 +36,7 @@ class ExecuteIfcFM(bpy.types.Operator):
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
props = context.scene.BIMFMProperties
|
||||
return props.should_load_from_memory or props.ifc_file
|
||||
return props.should_load_from_memory or props.ifc_files.single_file
|
||||
|
||||
def invoke(self, context, event):
|
||||
props = context.scene.BIMFMProperties
|
||||
@@ -63,14 +49,13 @@ class ExecuteIfcFM(bpy.types.Operator):
|
||||
props = context.scene.BIMFMProperties
|
||||
ifc_file = tool.Ifc.get()
|
||||
filepaths = []
|
||||
if not (ifc_file and props.should_load_from_memory):
|
||||
if len(props.ifc_files):
|
||||
ifc_files = [ifcopenshell.open(f.name) for f in props.ifc_files]
|
||||
filepaths = [f.name for f in props.ifc_files]
|
||||
else:
|
||||
ifc_files = [ifcopenshell.open(props.ifc_file)]
|
||||
else:
|
||||
if ifc_file and props.should_load_from_memory:
|
||||
ifc_files = [ifc_file]
|
||||
else:
|
||||
ifc_files = [ifcopenshell.open(f.name) for f in props.ifc_files.file_list]
|
||||
|
||||
if len(ifc_files) > 1:
|
||||
filepaths = [f.name for f in props.ifc_files.file_list]
|
||||
|
||||
for i, ifc_file in enumerate(ifc_files):
|
||||
if filepaths:
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
import bpy
|
||||
from blenderbim.bim.module.fm.data import FMData
|
||||
from blenderbim.bim.prop import StrProperty
|
||||
from blenderbim.bim.prop import MultipleFileSelect, StrProperty
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
@@ -39,11 +39,10 @@ def get_engine(self, context):
|
||||
|
||||
|
||||
class BIMFMProperties(PropertyGroup):
|
||||
ifc_file: StringProperty(default="", name="IFC File")
|
||||
ifc_files: CollectionProperty(name="IFC Files", type=StrProperty)
|
||||
ifc_files: PointerProperty(type=MultipleFileSelect)
|
||||
spreadsheet_files: CollectionProperty(name="Spreadsheets", type=StrProperty)
|
||||
should_load_from_memory: BoolProperty(default=False, name="Load from Memory")
|
||||
engine: EnumProperty(items=get_engine, name="Engine")
|
||||
engine: EnumProperty(items=get_engine, name="Engine", options=set())
|
||||
format: EnumProperty(
|
||||
items=[
|
||||
("csv", "csv", ""),
|
||||
@@ -52,4 +51,5 @@ class BIMFMProperties(PropertyGroup):
|
||||
],
|
||||
name="Format",
|
||||
default="ods",
|
||||
options=set(),
|
||||
)
|
||||
|
||||
@@ -45,12 +45,8 @@ class BIM_PT_fm(Panel):
|
||||
row.prop(props, "should_load_from_memory")
|
||||
|
||||
if not IfcStore.get_file() or not props.should_load_from_memory:
|
||||
row = layout.row(align=True)
|
||||
if len(props.ifc_files) > 1:
|
||||
row.label(text=f"{len(props.ifc_files)} Files Selected")
|
||||
else:
|
||||
row.prop(props, "ifc_file")
|
||||
row.operator("bim.select_fm_ifc_file", icon="FILE_FOLDER", text="")
|
||||
row = layout.row()
|
||||
props.ifc_files.layout_file_select(row, "*.ifc;*.ifczip;*.ifcxml", "IFC File(s)")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(props, "engine")
|
||||
|
||||
@@ -18,8 +18,11 @@
|
||||
|
||||
import bpy
|
||||
import ifcopenshell.util.element
|
||||
import blenderbim.tool as tool
|
||||
import ifcopenshell.util.geolocation
|
||||
import ifcopenshell.util.placement
|
||||
import ifcopenshell.util.schema
|
||||
import ifcopenshell.util.unit
|
||||
import blenderbim.tool as tool
|
||||
from mathutils import Vector
|
||||
|
||||
|
||||
|
||||
@@ -21,9 +21,6 @@ from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.AddGeoreferencing,
|
||||
operator.ConvertAngleToCoordinates,
|
||||
operator.ConvertGlobalToLocal,
|
||||
operator.ConvertLocalToGlobal,
|
||||
operator.DisableEditingGeoreferencing,
|
||||
operator.DisableEditingTrueNorth,
|
||||
operator.DisableEditingWCS,
|
||||
|
||||
@@ -20,7 +20,10 @@
|
||||
import bpy
|
||||
import numpy as np
|
||||
import blenderbim.tool as tool
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.geolocation
|
||||
import ifcopenshell.util.schema
|
||||
import ifcopenshell.util.unit
|
||||
from mathutils import Matrix, Vector
|
||||
from ifcopenshell.util.doc import get_entity_doc
|
||||
|
||||
@@ -44,6 +47,7 @@ class GeoreferenceData:
|
||||
cls.data["local_unit_symbol"] = cls.local_unit_symbol()
|
||||
cls.data["map_unit_symbol"] = cls.map_unit_symbol()
|
||||
cls.data["world_coordinate_system"] = cls.world_coordinate_system()
|
||||
cls.data["local_origin"] = cls.local_origin()
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
@@ -165,7 +169,7 @@ class GeoreferenceData:
|
||||
|
||||
props = bpy.context.scene.BIMGeoreferenceProperties
|
||||
if props.has_blender_offset:
|
||||
blender_xyz = ifcopenshell.util.geolocation.xyz2enh(
|
||||
blender_xyz = ifcopenshell.util.geolocation.enh2xyz(
|
||||
result["x"],
|
||||
result["y"],
|
||||
result["z"],
|
||||
@@ -186,3 +190,16 @@ class GeoreferenceData:
|
||||
wcs[2][3] *= unit_scale
|
||||
result["matrix"] = Matrix(wcs)
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def local_origin(cls):
|
||||
props = bpy.context.scene.BIMGeoreferenceProperties
|
||||
if not props.has_blender_offset:
|
||||
return
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
x = float(props.blender_offset_x) * -1
|
||||
y = float(props.blender_offset_y) * -1
|
||||
z = float(props.blender_offset_z) * -1
|
||||
enh = ifcopenshell.util.geolocation.auto_xyz2enh(tool.Ifc.get(), 0, 0, 0)
|
||||
xyz_si = Vector([x * unit_scale, y * unit_scale, z * unit_scale])
|
||||
return {"x": x, "y": y, "z": z, "enh": enh, "location": xyz_si}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import blf
|
||||
import gpu
|
||||
import bmesh
|
||||
@@ -61,6 +62,10 @@ class GeoreferenceDecorator:
|
||||
self.calculate_angles(context)
|
||||
props = context.scene.BIMGeoreferenceProperties
|
||||
e, n, h = [round(float(o), 7) for o in props.model_origin.split(",")]
|
||||
if (operation := GeoreferenceData.data["coordinate_operation"]) and (scale := operation.get("Scale", None)):
|
||||
e *= scale
|
||||
n *= scale
|
||||
h *= scale
|
||||
|
||||
self.addon_prefs = tool.Blender.get_addon_preferences()
|
||||
|
||||
@@ -71,10 +76,14 @@ class GeoreferenceDecorator:
|
||||
blf.enable(self.font_id, blf.SHADOW)
|
||||
blf.shadow(self.font_id, 6, 0, 0, 0, 1)
|
||||
|
||||
text = f"Blender Coordinates ({GeoreferenceData.data['local_unit_symbol']}) X: 0, Y: 0, Z: 0"
|
||||
if props.has_blender_offset:
|
||||
text = "Blender Origin"
|
||||
text += f"\nBlender Coordinates ({GeoreferenceData.data['local_unit_symbol']}) X: 0, Y: 0, Z: 0"
|
||||
else:
|
||||
text = "IFC Local Origin"
|
||||
text += f"\nLocal Coordinates ({GeoreferenceData.data['local_unit_symbol']}) X: {props.blender_offset_x}, Y: {props.blender_offset_y}, Z: {props.blender_offset_z}"
|
||||
if GeoreferenceData.data["projected_crs"]:
|
||||
text += f"\nMap Coordinates ({GeoreferenceData.data['local_unit_symbol']}) E: {e}, N: {n}, H: {h}"
|
||||
text += f"\nMap Coordinates ({GeoreferenceData.data['map_unit_symbol']}) E: {e}, N: {n}, H: {h}"
|
||||
self.draw_text_at_position(context, text, Vector((0, -0.1, 0)))
|
||||
|
||||
angle = Matrix.Rotation(radians(self.pn_angle), 4, "Z")
|
||||
@@ -107,9 +116,10 @@ class GeoreferenceDecorator:
|
||||
arc_mid = angle_half @ arc_start
|
||||
self.draw_text_at_position(context, f"{self.tn_angle}deg", arc_mid)
|
||||
|
||||
if (wcs := GeoreferenceData.data["world_coordinate_system"]) and ["has_transformation"]:
|
||||
if (wcs := GeoreferenceData.data["world_coordinate_system"]) and wcs["has_transformation"]:
|
||||
text = "WCS"
|
||||
text += f"\nBlender Coordinates ({GeoreferenceData.data['local_unit_symbol']}) X: {wcs['blender_x']}, Y: {wcs['blender_y']}, Z: {wcs['blender_z']}"
|
||||
if props.has_blender_offset:
|
||||
text += f"\nBlender Coordinates ({GeoreferenceData.data['local_unit_symbol']}) X: {wcs['blender_x']}, Y: {wcs['blender_y']}, Z: {wcs['blender_z']}"
|
||||
text += f"\nLocal Coordinates ({GeoreferenceData.data['local_unit_symbol']}) X: {wcs['x']}, Y: {wcs['y']}, Z: {wcs['z']}"
|
||||
if operation := GeoreferenceData.data["coordinate_operation"]:
|
||||
e = operation.get("Eastings")
|
||||
@@ -126,6 +136,22 @@ class GeoreferenceDecorator:
|
||||
|
||||
self.draw_text_at_position(context, text, position)
|
||||
|
||||
if props.has_blender_offset:
|
||||
text = "IFC Local Origin"
|
||||
x = GeoreferenceData.data["local_origin"]["x"]
|
||||
y = GeoreferenceData.data["local_origin"]["y"]
|
||||
z = GeoreferenceData.data["local_origin"]["z"]
|
||||
location = GeoreferenceData.data["local_origin"]["location"]
|
||||
text += f"\nBlender Coordinates ({GeoreferenceData.data['local_unit_symbol']}) X: {x}, Y: {y}, Z: {z}"
|
||||
text += f"\nLocal Coordinates ({GeoreferenceData.data['local_unit_symbol']}) X: 0, Y: 0, Z: 0"
|
||||
if GeoreferenceData.data["projected_crs"]:
|
||||
e, n, h = GeoreferenceData.data["local_origin"]["enh"]
|
||||
text += f"\nMap Coordinates ({GeoreferenceData.data['map_unit_symbol']}) E: {e}, N: {n}, H: {h}"
|
||||
if location.length > 1000:
|
||||
location = location.normalized() * 3
|
||||
text += "\n(Warning: Actual XYZ Not Shown)"
|
||||
self.draw_text_at_position(context, text, location)
|
||||
|
||||
def draw_text_at_position(self, context, text, position):
|
||||
coords_2d = location_3d_to_region_2d(context.region, context.region_data, position)
|
||||
if not coords_2d:
|
||||
@@ -255,25 +281,50 @@ class GeoreferenceDecorator:
|
||||
verts, edges = arc_segments
|
||||
self.draw_batch("LINES", verts, decorator_color_special, edges)
|
||||
|
||||
if (wcs := GeoreferenceData.data["world_coordinate_system"]) and ["has_transformation"]:
|
||||
|
||||
if (wcs := GeoreferenceData.data["world_coordinate_system"]) and wcs["has_transformation"]:
|
||||
if wcs["blender_location"].length < 1000:
|
||||
verts = [Vector((0, 0, 0)), wcs["blender_location"]]
|
||||
edges = [[0, 1]]
|
||||
self.draw_batch("LINES", verts, decorator_color_special, edges)
|
||||
self.draw_batch("POINTS", verts[1:], decorator_color_special)
|
||||
else:
|
||||
location = wcs["blender_location"].normalized()
|
||||
edges = [[0, 1]]
|
||||
verts = [Vector((0, 0, 0)), wcs["blender_location"].normalized() * 3]
|
||||
verts = [Vector((0, 0, 0)), location * 3]
|
||||
self.draw_batch("LINES", verts, decorator_color_special, edges)
|
||||
verts = [wcs["blender_location"].normalized() * 3, wcs["blender_location"].normalized() * 3.1]
|
||||
self.draw_batch("LINES", verts, decorator_color_error, edges)
|
||||
verts = [wcs["blender_location"].normalized() * 3.12, wcs["blender_location"].normalized() * 3.2]
|
||||
self.draw_batch("LINES", verts, decorator_color_error, edges)
|
||||
verts = [wcs["blender_location"].normalized() * 3.22, wcs["blender_location"].normalized() * 3.3]
|
||||
self.draw_batch("LINES", verts, decorator_color_error, edges)
|
||||
verts = [wcs["blender_location"].normalized() * 3.32, wcs["blender_location"].normalized() * 3.4]
|
||||
self.draw_batch("LINES", verts, decorator_color_error, edges)
|
||||
self.draw_dashed_line(location * 3, location * 6, decorator_color_error)
|
||||
|
||||
props = context.scene.BIMGeoreferenceProperties
|
||||
if props.has_blender_offset:
|
||||
location = GeoreferenceData.data["local_origin"]["location"]
|
||||
if location.length < 1000:
|
||||
verts = [Vector((0, 0, 0)), location]
|
||||
edges = [[0, 1]]
|
||||
self.draw_batch("LINES", verts, decorator_color_special, edges)
|
||||
self.draw_batch("POINTS", verts[1:], decorator_color_special)
|
||||
else:
|
||||
location = location.normalized()
|
||||
edges = [[0, 1]]
|
||||
verts = [Vector((0, 0, 0)), location * 3]
|
||||
self.draw_batch("LINES", verts, decorator_color_special, edges)
|
||||
self.draw_dashed_line(location * 3, location * 6, decorator_color_error)
|
||||
|
||||
def draw_dashed_line(self, start, end, colour):
|
||||
direction = (end - start).normalized()
|
||||
distance = (end - start).length
|
||||
current_distance = Vector((0, 0, 0))
|
||||
gap = 0.05 * direction
|
||||
dash = 0.1 * direction
|
||||
points = [start]
|
||||
while current_distance.length < distance:
|
||||
points.append(start + current_distance + dash)
|
||||
points.append(start + current_distance + dash + gap)
|
||||
current_distance += gap + dash
|
||||
points.pop()
|
||||
|
||||
edges = [[i, i + 1] for i in range(0, len(points), 2)]
|
||||
verts = points
|
||||
self.draw_batch("LINES", verts, colour, edges)
|
||||
|
||||
def calculate_angles(self, context):
|
||||
self.pn_angle = 0.0
|
||||
|
||||
@@ -87,55 +87,6 @@ class GetCursorLocation(bpy.types.Operator, tool.Ifc.Operator):
|
||||
core.get_cursor_location(tool.Georeference)
|
||||
|
||||
|
||||
class ConvertLocalToGlobal(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.convert_local_to_global"
|
||||
bl_label = "Convert Local To Global"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Convert local coordinate to global coordinate"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
file = tool.Ifc.get()
|
||||
props = context.scene.BIMGeoreferenceProperties
|
||||
return file and props.local_coordinates.count(",") == 2
|
||||
|
||||
def _execute(self, context):
|
||||
core.convert_local_to_global(tool.Georeference)
|
||||
|
||||
|
||||
class ConvertGlobalToLocal(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.convert_global_to_local"
|
||||
bl_label = "Convert Global To Local"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Convert global coordinate to local coordinate"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
file = tool.Ifc.get()
|
||||
props = context.scene.BIMGeoreferenceProperties
|
||||
return file and file.by_type("IfcUnitAssignment") and props.local_coordinates.count(",") == 2
|
||||
|
||||
def _execute(self, context):
|
||||
core.convert_global_to_local(tool.Georeference)
|
||||
|
||||
|
||||
class ConvertAngleToCoordinates(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.convert_angle_to_coord"
|
||||
bl_label = "Convert Angle To Y Axis"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Convert angle to Y axis"
|
||||
type: bpy.props.StringProperty()
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
file = tool.Ifc.get()
|
||||
props = context.scene.BIMGeoreferenceProperties
|
||||
return file and (props.angle_degree_input_x or props.angle_degree_input_y)
|
||||
|
||||
def _execute(self, context):
|
||||
core.convert_angle_to_coord(tool.Georeference, type=self.type)
|
||||
|
||||
|
||||
class ImportPlot(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.import_plot"
|
||||
bl_label = "Import Plot"
|
||||
@@ -170,7 +121,7 @@ class EditWCS(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_description = "Edit the WCS"
|
||||
|
||||
def _execute(self, context):
|
||||
core.edit_wcs(tool.Ifc, tool.Georeference)
|
||||
core.edit_wcs(tool.Georeference)
|
||||
|
||||
|
||||
class DisableEditingWCS(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
import bpy
|
||||
import ifcopenshell.util.geolocation
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim.prop import Attribute
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
@@ -101,6 +102,74 @@ def update_should_visualise(self, context):
|
||||
GeoreferenceDecorator.uninstall()
|
||||
|
||||
|
||||
def update_blender_coordinates(self, context):
|
||||
props = bpy.context.scene.BIMGeoreferenceProperties
|
||||
if props.is_updating_coordinates:
|
||||
return
|
||||
props.is_updating_coordinates = True
|
||||
blender_coordinates = tool.Georeference.get_coordinates("blender")
|
||||
local_coordinates = ifcopenshell.util.geolocation.xyz2enh(
|
||||
*blender_coordinates,
|
||||
float(props.blender_offset_x),
|
||||
float(props.blender_offset_y),
|
||||
float(props.blender_offset_z),
|
||||
float(props.blender_x_axis_abscissa),
|
||||
float(props.blender_x_axis_ordinate),
|
||||
)
|
||||
tool.Georeference.set_coordinates("local", local_coordinates)
|
||||
tool.Georeference.set_coordinates(
|
||||
"map", ifcopenshell.util.geolocation.auto_xyz2enh(tool.Ifc.get(), *local_coordinates)
|
||||
)
|
||||
props.is_updating_coordinates = False
|
||||
|
||||
|
||||
def update_local_coordinates(self, context):
|
||||
props = bpy.context.scene.BIMGeoreferenceProperties
|
||||
if props.is_updating_coordinates:
|
||||
return
|
||||
props.is_updating_coordinates = True
|
||||
local_coordinates = tool.Georeference.get_coordinates("local")
|
||||
tool.Georeference.set_coordinates(
|
||||
"map", ifcopenshell.util.geolocation.auto_xyz2enh(tool.Ifc.get(), *local_coordinates)
|
||||
)
|
||||
if props.has_blender_offset:
|
||||
tool.Georeference.set_coordinates(
|
||||
"blender",
|
||||
ifcopenshell.util.geolocation.enh2xyz(
|
||||
*local_coordinates,
|
||||
float(props.blender_offset_x),
|
||||
float(props.blender_offset_y),
|
||||
float(props.blender_offset_z),
|
||||
float(props.blender_x_axis_abscissa),
|
||||
float(props.blender_x_axis_ordinate),
|
||||
),
|
||||
)
|
||||
props.is_updating_coordinates = False
|
||||
|
||||
|
||||
def update_map_coordinates(self, context):
|
||||
props = bpy.context.scene.BIMGeoreferenceProperties
|
||||
if props.is_updating_coordinates:
|
||||
return
|
||||
props.is_updating_coordinates = True
|
||||
map_coordinates = tool.Georeference.get_coordinates("map")
|
||||
local_coordinates = ifcopenshell.util.geolocation.auto_enh2xyz(tool.Ifc.get(), *map_coordinates)
|
||||
tool.Georeference.set_coordinates("local", local_coordinates)
|
||||
if props.has_blender_offset:
|
||||
tool.Georeference.set_coordinates(
|
||||
"blender",
|
||||
ifcopenshell.util.geolocation.enh2xyz(
|
||||
*local_coordinates,
|
||||
float(props.blender_offset_x),
|
||||
float(props.blender_offset_y),
|
||||
float(props.blender_offset_z),
|
||||
float(props.blender_x_axis_abscissa),
|
||||
float(props.blender_x_axis_ordinate),
|
||||
),
|
||||
)
|
||||
props.is_updating_coordinates = False
|
||||
|
||||
|
||||
class BIMGeoreferenceProperties(PropertyGroup):
|
||||
coordinate_operation_class: bpy.props.EnumProperty(
|
||||
items=get_coordinate_operation_class, name="Coordinate Operation Class"
|
||||
@@ -111,11 +180,24 @@ class BIMGeoreferenceProperties(PropertyGroup):
|
||||
is_editing_true_north: BoolProperty(name="Is Editing True North")
|
||||
coordinate_operation: CollectionProperty(name="Coordinate Operation", type=Attribute)
|
||||
projected_crs: CollectionProperty(name="Projected CRS", type=Attribute)
|
||||
is_updating_coordinates: BoolProperty(name="Is Updating Coordinates", default=False)
|
||||
blender_coordinates: StringProperty(
|
||||
name="Blender Coordinates",
|
||||
description='Formatted "x,y,z" (without quotes)',
|
||||
default="0,0,0",
|
||||
update=update_blender_coordinates,
|
||||
)
|
||||
local_coordinates: StringProperty(
|
||||
name="Local Coordinates", description='Formatted "x,y,z" (without quotes)', default="0,0,0"
|
||||
name="Local Coordinates",
|
||||
description='Formatted "x,y,z" (without quotes)',
|
||||
default="0,0,0",
|
||||
update=update_local_coordinates,
|
||||
)
|
||||
map_coordinates: StringProperty(
|
||||
name="Map Coordinates", description='Formatted "x,y,z" (without quotes)', default="0,0,0"
|
||||
name="Map Coordinates",
|
||||
description='Formatted "x,y,z" (without quotes)',
|
||||
default="0,0,0",
|
||||
update=update_map_coordinates,
|
||||
)
|
||||
should_visualise: BoolProperty(
|
||||
name="Should Visualise",
|
||||
|
||||
@@ -288,12 +288,15 @@ class BIM_PT_gis_calculator(Panel):
|
||||
|
||||
props = context.scene.BIMGeoreferenceProperties
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "local_coordinates", text=f"Local ({GeoreferenceData.data['local_unit_symbol']})")
|
||||
row.operator("bim.get_cursor_location", text="", icon="TRACKER")
|
||||
row = self.layout.row()
|
||||
row.prop(props, "map_coordinates", text=f"Map ({GeoreferenceData.data['map_unit_symbol']})")
|
||||
if props.has_blender_offset:
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "blender_coordinates", text=f"Blender ({GeoreferenceData.data['local_unit_symbol']})")
|
||||
row.operator("bim.get_cursor_location", text="", icon="TRACKER")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.convert_local_to_global", text="Local to Global")
|
||||
row.operator("bim.convert_global_to_local", text="Global to Local")
|
||||
row.prop(props, "local_coordinates", text=f"Local ({GeoreferenceData.data['local_unit_symbol']})")
|
||||
if not props.has_blender_offset:
|
||||
row.operator("bim.get_cursor_location", text="", icon="TRACKER")
|
||||
|
||||
row = self.layout.row()
|
||||
row.prop(props, "map_coordinates", text=f"Map ({GeoreferenceData.data['map_unit_symbol']})")
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,37 @@
|
||||
{
|
||||
skymap.cal - Map a pre swewd sky picture onto a hemisphere
|
||||
|
||||
###########################################
|
||||
#
|
||||
# SKY PICTURE MAP
|
||||
#
|
||||
# Copyright (C) 1992 by Curtin University
|
||||
# School of Architecture
|
||||
#
|
||||
# Curtin University of Technology
|
||||
# Department of Architecture
|
||||
# GPO Box U 1987
|
||||
# Perth, Western Australia
|
||||
#
|
||||
# Version 1.1 October 1993.
|
||||
# Fixed NaN errors.
|
||||
#
|
||||
# Simon Crone
|
||||
# email: crones@puffin.curtin.edu.au
|
||||
#
|
||||
###########################################
|
||||
|
||||
A1 = aspect ratio ( height / width )
|
||||
}
|
||||
|
||||
u_scale = if( A1 -1, 1, A1);
|
||||
v_scale = if( A1 -1, A1, 1);
|
||||
|
||||
hang = if( abs( Dx) - FTINY,
|
||||
atan2( Dy, Dx),
|
||||
if( Dy, PI/2, -PI/2 )
|
||||
);
|
||||
|
||||
map_u = ( (hang + PI ) / (PI*2) ) / u_scale;
|
||||
map_v = ( (asin( Dz) + (PI/2) ) / PI ) / v_scale;
|
||||
|
||||
@@ -19,6 +19,16 @@
|
||||
import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
import stat
|
||||
from pathlib import Path
|
||||
|
||||
import importlib
|
||||
import pyradiance
|
||||
|
||||
|
||||
def get_pyradiance_path():
|
||||
return importlib.util.find_spec("pyradiance").submodule_search_locations[0]
|
||||
|
||||
|
||||
classes = (
|
||||
operator.ExportOBJ,
|
||||
@@ -38,6 +48,12 @@ classes = (
|
||||
def register():
|
||||
bpy.types.Scene.radiance_exporter_properties = bpy.props.PointerProperty(type=prop.RadianceExporterProperties)
|
||||
bpy.types.Scene.BIMSolarProperties = bpy.props.PointerProperty(type=prop.BIMSolarProperties)
|
||||
pyradiance_path = Path(get_pyradiance_path())
|
||||
bin_path = pyradiance_path / "bin"
|
||||
if bin_path.exists():
|
||||
for file in bin_path.iterdir():
|
||||
if file.is_file():
|
||||
file.chmod(file.stat().st_mode | stat.S_IEXEC)
|
||||
|
||||
|
||||
def unregister():
|
||||
|
||||
@@ -34,10 +34,15 @@ class SolarData:
|
||||
def load(cls):
|
||||
cls.is_loaded = True
|
||||
cls.data = {
|
||||
"sun_position": cls.sun_position(),
|
||||
"sites": cls.sites(),
|
||||
"true_north": cls.true_north(),
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def sun_position(cls):
|
||||
return tool.Blender.get_sun_position_addon()
|
||||
|
||||
@classmethod
|
||||
def sites(cls):
|
||||
results = []
|
||||
|
||||
@@ -28,8 +28,6 @@ from gpu_extras.batch import batch_for_shader
|
||||
from bpy_extras.view3d_utils import location_3d_to_region_2d
|
||||
from blenderbim.bim.module.light.data import SolarData
|
||||
|
||||
sun_position = tool.Blender.get_sun_position_addon()
|
||||
|
||||
|
||||
class SolarDecorator:
|
||||
is_installed = False
|
||||
@@ -149,7 +147,7 @@ class SolarDecorator:
|
||||
indices = []
|
||||
coord_offset = 0
|
||||
for h in range(24):
|
||||
analemma_verts = sun_position.sun_calc.calc_analemma(context, h)
|
||||
analemma_verts = SolarData.data["sun_position"].sun_calc.calc_analemma(context, h)
|
||||
coords.extend([origin @ v for v in analemma_verts])
|
||||
for i in range(len(analemma_verts) - 1):
|
||||
indices.append((coord_offset + i, coord_offset + i + 1))
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
[
|
||||
{
|
||||
"ceiling": {
|
||||
"Black Ceiling 1": "void plastic black_ceiling\n0\n0\n5 0.0460 0.0456 0.0463 0.0052 0.2000",
|
||||
"Black Ceiling 2": "void plastic black_ceiling\n0\n0\n5 0.1116 0.1085 0.0938 0.0000 0.0000",
|
||||
"Plastic Ceiling Vent E14 526": "void plastic plastic_ceiling_vent_e14_526\n0\n0\n5 0.7384 0.7195 0.6540 0.0110 0.0500",
|
||||
"Specular Reflective Ceiling Panels": "void plastic specular_reflective_ceiling_panels\n0\n0\n5 0.7708 0.7673 0.6819 0.0741 0.0500"
|
||||
},
|
||||
"Door": {
|
||||
"Wooden Door": "void plastic wooden_door\n0\n0\n5 0.6164 0.4257 0.2156 0.0185 0.2000",
|
||||
"Wooden Door 2": "void plastic wooden_door_2\n0\n0\n5 0.0403 0.0388 0.0406 0.0121 0.2000",
|
||||
"White Wooden Door": "void plastic white_wooden_door\n0\n0\n5 0.7893 0.7502 0.6413 0.0080 0.2000",
|
||||
"Off White Door": "void plastic off_white_door\n0\n0\n5 0.8607 0.8611 0.7852 0.0258 0.2000",
|
||||
"Bedroom Door": "void plastic bedroom_door\n0\n0\n5 0.2508 0.0782 0.0111 0.0279 0.2000"
|
||||
},
|
||||
"Floor": {
|
||||
"Light Blue Ceramic": "void plastic light_blue_ceramic\n0\n0\n5 0.7477 0.7776 0.7807 0.0323 0.1000",
|
||||
"Light Grey Ceramic": "void plastic light_grey_ceramic\n0\n0\n5 0.6386 0.5941 0.4982 0.0106 0.2000",
|
||||
"Purple Carpet": "void plastic purple_carpet\n0\n0\n5 0.0425 0.0179 0.0371 0.0001 0.3000"
|
||||
},
|
||||
"Wall": {
|
||||
"Dirty Dark Pink Painted Wall": "void plastic dirty_dark_pink_painted_wall\n0\n0\n5 0.3533 0.1751 0.1690 0.0000 0.2000",
|
||||
"Blue Green Painted Walls": "void plastic blue_green_painted_walls\n0\n0\n5 -0.0013 0.2092 0.2601 0.0140 0.2000",
|
||||
"Red Painted Wall": "void plastic red_painted_wall\n0\n0\n5 0.4621 0.0156 0.0091 0.0396 0.2000"
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -24,6 +24,7 @@ except ImportError:
|
||||
print("PyRadiance is not available. Rendering functionality will be disabled.")
|
||||
pr = None
|
||||
|
||||
from datetime import datetime
|
||||
import bpy
|
||||
import blenderbim.tool as tool
|
||||
from pathlib import Path
|
||||
@@ -70,9 +71,6 @@ class ExportOBJ(bpy.types.Operator):
|
||||
ifc_file_path = context.scene.radiance_exporter_properties.ifc_file
|
||||
ifc_file = ifcopenshell.open(ifc_file_path)
|
||||
|
||||
# for material in ifc_file.by_type("IfcMaterial"):
|
||||
# self.report({'INFO'}, f"Material: {material.Name}, ID: {material.id()}")
|
||||
|
||||
obj_file_path = os.path.join(output_dir, "model.obj")
|
||||
mtl_file_path = os.path.join(output_dir, "model.mtl")
|
||||
|
||||
@@ -86,6 +84,7 @@ class ExportOBJ(bpy.types.Operator):
|
||||
else:
|
||||
elements = ifc_file.by_type("IfcElement")
|
||||
|
||||
elements += ifc_file.by_type("IfcSite")
|
||||
elements = [e for e in elements if not e.is_a("IfcFeatureElement") or e.is_a("IfcSurfaceFeature")]
|
||||
|
||||
iterator = ifcopenshell.geom.iterator(settings, ifc_file, multiprocessing.cpu_count(), include=elements)
|
||||
@@ -128,6 +127,7 @@ class RadianceRender(bpy.types.Operator):
|
||||
return {"CANCELLED"}
|
||||
|
||||
# Get the resolution from the user input
|
||||
|
||||
props = context.scene.radiance_exporter_properties
|
||||
resolution_x, resolution_y = props.radiance_resolution_x, props.radiance_resolution_y
|
||||
quality = props.radiance_quality.upper()
|
||||
@@ -136,9 +136,29 @@ class RadianceRender(bpy.types.Operator):
|
||||
output_dir = props.output_dir
|
||||
output_file_name = props.output_file_name
|
||||
output_file_format = props.output_file_format
|
||||
use_hdr = props.use_hdr
|
||||
choose_hdr_image = props.choose_hdr_image
|
||||
if use_hdr:
|
||||
hdr_image = "noon_grass_2k.hdr"
|
||||
hdr_mask = "noon_grass_2k_mask.hdr"
|
||||
sky_map_cal = "skymap.cal"
|
||||
hdr_image_path = os.path.join(os.path.dirname(__file__), "HDRs", hdr_image)
|
||||
hdr_mask_path = os.path.join(os.path.dirname(__file__), "HDRs", hdr_mask)
|
||||
sky_map_cal_path = os.path.join(os.path.dirname(__file__), "HDRs", sky_map_cal)
|
||||
# os.chdir(output_dir)
|
||||
|
||||
obj_file_path = os.path.join(output_dir, "model.obj")
|
||||
|
||||
sun_props = context.scene.BIMSolarProperties
|
||||
sky_file_path = os.path.join(output_dir, "sky.rad")
|
||||
latitude = sun_props.latitude
|
||||
longitude = sun_props.longitude
|
||||
timezone = sun_props.timezone
|
||||
month = sun_props.month
|
||||
day = sun_props.day
|
||||
hour = sun_props.hour
|
||||
minute = sun_props.minute
|
||||
|
||||
camera = self.get_active_camera(context)
|
||||
if camera is None:
|
||||
self.report({"ERROR"}, "No active camera found in the scene. Please add a camera and set it as active.")
|
||||
@@ -147,29 +167,124 @@ class RadianceRender(bpy.types.Operator):
|
||||
# Get camera position and direction
|
||||
camera_position, camera_direction = self.get_camera_data(camera)
|
||||
|
||||
# Material processing
|
||||
# style = []
|
||||
dt = datetime(2024, month, day, hour, minute)
|
||||
|
||||
# with open(obj_file_path, "r") as obj_file:
|
||||
# for line in obj_file:
|
||||
# if line.startswith("usemtl"):
|
||||
# l = line.strip().split(" ")
|
||||
# style.append(l[1])
|
||||
sky_description = pr.gensky(
|
||||
dt=dt,
|
||||
latitude=latitude,
|
||||
longitude=longitude,
|
||||
timezone=timezone,
|
||||
sunny_with_sun=True,
|
||||
sunny_without_sun=False,
|
||||
cloudy=True,
|
||||
ground_reflectance=0.2,
|
||||
turbidity=3.0,
|
||||
)
|
||||
|
||||
sky_description_str = sky_description.decode("utf-8")
|
||||
|
||||
# Write all this to file
|
||||
# skyfunc glow sky_glow
|
||||
# 0
|
||||
# 0
|
||||
# 4 .9 .9 1.15 0
|
||||
|
||||
# sky_glow source sky
|
||||
# 0
|
||||
# 0
|
||||
# 4 0 0 1 180
|
||||
|
||||
# skyfunc glow ground_glow
|
||||
# 0
|
||||
# 0
|
||||
# 4 1.4 .9 .6 0
|
||||
|
||||
# ground_glow source ground
|
||||
# 0
|
||||
# 0
|
||||
# 4 0 0 -1 180
|
||||
|
||||
if choose_hdr_image == "Noon":
|
||||
|
||||
with open(sky_file_path, "w") as f:
|
||||
f.write(sky_description_str)
|
||||
f.write("\n")
|
||||
# f.write("skyfunc glow sky_glow\n0\n0\n4 .9 .9 1.15 0\n")
|
||||
# f.write("sky_glow source sky\n0\n0\n4 0 0 1 180\n")
|
||||
# f.write("skyfunc glow ground_glow\n0\n0\n4 1.4 .9 .6 0\n")
|
||||
# f.write("ground_glow source ground\n0\n0\n4 0 0 -1 180\n")
|
||||
|
||||
f.write(
|
||||
'''void colorpict env_map
|
||||
7 red green blue "'''
|
||||
+ hdr_image_path
|
||||
+ '''" "'''
|
||||
+ sky_map_cal_path
|
||||
+ '''" map_u map_v
|
||||
0
|
||||
1 0.5
|
||||
|
||||
# This is a multiplier to colour balance the env map
|
||||
# In this case, it provides a rough ground luminance from 3k-5k
|
||||
env_map colorfunc env_colour
|
||||
4 100 100 100 .
|
||||
0
|
||||
0
|
||||
|
||||
# .37 .57 1.5 is measured from a HDRI image
|
||||
# It is multiplied by a factor such that grey(r,g,b) = 1
|
||||
skyfunc colorfunc sky_colour
|
||||
4 .64 .99 2.6 .
|
||||
0
|
||||
0
|
||||
|
||||
void mixpict composite
|
||||
7 env_colour sky_colour grey "'''
|
||||
+ hdr_mask_path
|
||||
+ '''" "'''
|
||||
+ sky_map_cal_path
|
||||
+ """" map_u map_v
|
||||
0
|
||||
2 0.5 1
|
||||
|
||||
composite glow env_map_glow
|
||||
0
|
||||
0
|
||||
4 1 1 1 0
|
||||
|
||||
env_map_glow source sky
|
||||
0
|
||||
0
|
||||
4 0 0 1 180
|
||||
|
||||
env_colour glow ground_glow
|
||||
0
|
||||
0
|
||||
4 1 1 1 0
|
||||
|
||||
ground_glow source ground
|
||||
0
|
||||
0
|
||||
4 0 0 -1 180"""
|
||||
)
|
||||
|
||||
else:
|
||||
with open(sky_file_path, "w") as f:
|
||||
f.write(sky_description_str)
|
||||
f.write("\n")
|
||||
f.write("skyfunc glow sky_glow\n0\n0\n4 .9 .9 1.15 0\n")
|
||||
f.write("sky_glow source sky\n0\n0\n4 0 0 1 180\n")
|
||||
f.write("skyfunc glow ground_glow\n0\n0\n4 1.4 .9 .6 0\n")
|
||||
f.write("ground_glow source ground\n0\n0\n4 0 0 -1 180\n")
|
||||
|
||||
# Check if json file is empty or not
|
||||
props = context.scene.radiance_exporter_properties
|
||||
|
||||
if props.use_json_file:
|
||||
# Load data from JSON file
|
||||
with open(props.json_file, "r") as file:
|
||||
data = json.load(file)
|
||||
else:
|
||||
# Use in-UI material mappings
|
||||
data = props.get_mappings_dict()
|
||||
|
||||
print(data)
|
||||
|
||||
# Create materials.rad file
|
||||
materials_file = os.path.join(output_dir, "materials.rad")
|
||||
with open(materials_file, "w") as file:
|
||||
file.write("void plastic white\n0\n0\n5 0.8 0.8 0.8 0 0\n")
|
||||
@@ -181,12 +296,8 @@ class RadianceRender(bpy.types.Operator):
|
||||
file.write("void trans olive_trans\n0\n0\n7 0.6 0.7 0.4 0.05 0.05 0.7 0.2\n")
|
||||
|
||||
for style_id, radiance_material in data.items():
|
||||
print(style_id, radiance_material)
|
||||
# print(style_id, radiance_material)
|
||||
file.write("inherit alias " + style_id + " " + data.get(style_id, "white") + "\n")
|
||||
# file.write(f"inherit alias {style_id} {radiance_material}\n")
|
||||
# for i in set(ifc_materials):
|
||||
# print(i)
|
||||
# file.write("inherit alias " + i + " " + data.get(i, "white") + "\n")
|
||||
|
||||
self.report({"INFO"}, "Exported Materials Rad file to: {}".format(materials_file))
|
||||
|
||||
@@ -197,7 +308,6 @@ class RadianceRender(bpy.types.Operator):
|
||||
self.report({"INFO"}, "obj2mesh output: {}".format(mesh_file_path))
|
||||
scene_file = os.path.join(output_dir, "scene.rad")
|
||||
with open(scene_file, "w") as file:
|
||||
# file.write("void mesh model\n1 " + rtm_file_path + "\n0\n0\n")
|
||||
file.write('void mesh model\n1 "' + rtm_file_path + '"\n0\n0\n')
|
||||
|
||||
self.report({"INFO"}, "Exported Scene file to: {}".format(scene_file))
|
||||
@@ -210,6 +320,7 @@ class RadianceRender(bpy.types.Operator):
|
||||
|
||||
scene.add_material(material_path)
|
||||
scene.add_surface(scene_path)
|
||||
scene.add_source(sky_file_path)
|
||||
|
||||
aview = pr.View(position=camera_position, direction=camera_direction)
|
||||
scene.add_view(aview)
|
||||
@@ -223,15 +334,21 @@ class RadianceRender(bpy.types.Operator):
|
||||
variability=variability,
|
||||
)
|
||||
|
||||
output_path = os.path.join(output_dir, f"{output_file_name}.{output_file_format.lower()}")
|
||||
output_hdr_path = os.path.join(output_dir, f"render123.{output_file_format.lower()}")
|
||||
|
||||
if output_file_format == "HDR":
|
||||
with open(output_path, "wb") as wtr:
|
||||
with open(output_hdr_path, "wb") as wtr:
|
||||
wtr.write(image)
|
||||
else:
|
||||
pass
|
||||
|
||||
self.report({"INFO"}, "Radiance rendering completed. Output: {}".format(output_path))
|
||||
pcond_image = pr.pcond(hdr=output_hdr_path, human=True)
|
||||
|
||||
tiff_path = os.path.join(output_dir, f"{output_file_name}.tiff")
|
||||
|
||||
pr.ra_tiff(inp=pcond_image, out=tiff_path, lzw=True)
|
||||
|
||||
self.report({"INFO"}, "Radiance rendering completed. Output: {}".format(tiff_path))
|
||||
return {"FINISHED"}
|
||||
|
||||
def get_active_camera(self, context):
|
||||
|
||||
@@ -23,13 +23,19 @@ import datetime
|
||||
import blenderbim.tool as tool
|
||||
from math import radians, pi
|
||||
from mathutils import Euler, Vector, Matrix, Quaternion
|
||||
from bpy.props import IntProperty, StringProperty, EnumProperty, FloatProperty, FloatVectorProperty, BoolProperty
|
||||
from bpy.props import (
|
||||
IntProperty,
|
||||
StringProperty,
|
||||
EnumProperty,
|
||||
FloatProperty,
|
||||
FloatVectorProperty,
|
||||
BoolProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
from bpy.types import PropertyGroup
|
||||
from blenderbim.bim.module.light.data import SolarData
|
||||
from blenderbim.bim.module.light.decorator import SolarDecorator
|
||||
|
||||
sun_position = tool.Blender.get_sun_position_addon()
|
||||
|
||||
|
||||
def get_sites(self, context):
|
||||
if not SolarData.is_loaded:
|
||||
@@ -95,6 +101,12 @@ def update_display_sun_path(self, context):
|
||||
|
||||
|
||||
def update_sun_path():
|
||||
if not SolarData.is_loaded:
|
||||
SolarData.load()
|
||||
|
||||
if (sun_position := SolarData.data["sun_position"]) is None:
|
||||
return
|
||||
|
||||
props = bpy.context.scene.BIMSolarProperties
|
||||
sun_props = bpy.context.scene.sun_pos_properties
|
||||
props.timezone = tzfpy.get_tz(props.longitude, props.latitude)
|
||||
@@ -182,6 +194,7 @@ class RadianceExporterProperties(PropertyGroup):
|
||||
)
|
||||
|
||||
material_mappings: bpy.props.CollectionProperty(type=bpy.types.PropertyGroup, name="Material Mappings")
|
||||
# material_mappings: CollectionProperty(type=MaterialMapping)
|
||||
|
||||
should_load_from_memory: BoolProperty(
|
||||
name="Load from Memory",
|
||||
@@ -246,11 +259,26 @@ class RadianceExporterProperties(PropertyGroup):
|
||||
name="Output File Format",
|
||||
description="Format of the output image file",
|
||||
items=[
|
||||
("HDR", "HDR", "High Dynamic Range"),
|
||||
("HDR", "HDR + Tiff", "High Dynamic Range"),
|
||||
],
|
||||
default="HDR",
|
||||
)
|
||||
|
||||
use_hdr: BoolProperty(
|
||||
name="Use HDR",
|
||||
description="Use HDR image format",
|
||||
default=True,
|
||||
)
|
||||
|
||||
choose_hdr_image: EnumProperty(
|
||||
name="HDR Image",
|
||||
description="Choose an HDR image to use",
|
||||
items=[
|
||||
("Noon", "Noon", "Noon"),
|
||||
],
|
||||
default="Noon",
|
||||
)
|
||||
|
||||
|
||||
class BIMSolarProperties(PropertyGroup):
|
||||
sites: EnumProperty(items=get_sites, name="Sites")
|
||||
|
||||
@@ -21,9 +21,6 @@ import blenderbim.tool as tool
|
||||
from blenderbim.bim.module.light.data import SolarData
|
||||
|
||||
|
||||
sun_position = tool.Blender.get_sun_position_addon()
|
||||
|
||||
|
||||
class BIM_PT_radiance_exporter(bpy.types.Panel):
|
||||
"""Creates a Panel in the render properties window"""
|
||||
|
||||
@@ -48,7 +45,6 @@ class BIM_PT_radiance_exporter(bpy.types.Panel):
|
||||
if not tool.Ifc.get() or not props.should_load_from_memory:
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "ifc_file")
|
||||
# row.operator("bim.select_ifctester_ifc_file", icon="FILE_FOLDER", text="")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(props, "output_dir")
|
||||
@@ -96,6 +92,12 @@ class BIM_PT_radiance_exporter(bpy.types.Panel):
|
||||
row.prop(props, "output_file_format")
|
||||
layout.separator()
|
||||
|
||||
row = layout.row()
|
||||
row.prop(props, "use_hdr")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(props, "choose_hdr_image")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("export_scene.radiance", text="Export Geometry for Simulation")
|
||||
|
||||
@@ -116,13 +118,13 @@ class BIM_PT_solar(bpy.types.Panel):
|
||||
bl_options = {"HIDE_HEADER"}
|
||||
|
||||
def draw(self, context):
|
||||
if sun_position is None:
|
||||
self.layout.label(text="Enable 'Sun Position' addon to continue.")
|
||||
return
|
||||
|
||||
if not SolarData.is_loaded:
|
||||
SolarData.load()
|
||||
|
||||
if (sun_position := SolarData.data["sun_position"]) is None:
|
||||
self.layout.label(text="Enable 'Sun Position' Add-on To Continue", icon="ERROR")
|
||||
return
|
||||
|
||||
props = context.scene.BIMSolarProperties
|
||||
|
||||
if SolarData.data["sites"]:
|
||||
|
||||
@@ -22,6 +22,7 @@ import ifcopenshell.api
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.attribute
|
||||
import ifcopenshell.util.representation
|
||||
import ifcopenshell.util.unit
|
||||
import blenderbim.bim.helper
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.style
|
||||
|
||||
@@ -120,6 +120,11 @@ def get_contexts(self, context):
|
||||
|
||||
|
||||
def update_material_name(self: "Material", context: bpy.types.Context) -> None:
|
||||
# No need to update anything if it's either category
|
||||
# or ifc_definition_id is not yet set
|
||||
# (which occurs when Material still in the process of creation).
|
||||
if self.is_category or not self.ifc_definition_id:
|
||||
return
|
||||
ifc_file = tool.Ifc.get()
|
||||
name = self.name
|
||||
material = ifc_file.by_id(self.ifc_definition_id)
|
||||
|
||||
@@ -143,7 +143,6 @@ classes = (
|
||||
stair.EnableEditingStair,
|
||||
stair.RemoveStair,
|
||||
pie.OpenPieClass,
|
||||
pie.PieUpdateContainer,
|
||||
pie.PieAddOpening,
|
||||
pie.VIEW3D_MT_PIE_bim,
|
||||
pie.VIEW3D_MT_PIE_bim_class,
|
||||
|
||||
@@ -21,6 +21,7 @@ import json
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.unit
|
||||
import blenderbim.tool as tool
|
||||
from mathutils import Vector, Matrix
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.element
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.covering as core
|
||||
|
||||
@@ -31,9 +32,7 @@ class AddInstanceFlooringCoveringFromCursor(bpy.types.Operator, tool.Ifc.Operato
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
relating_type_id = int(bpy.data.scenes["Scene"].BIMModelProperties.relating_type_id)
|
||||
relating_type = ifcopenshell.util.element.get_predefined_type(tool.Ifc.get().by_id(relating_type_id))
|
||||
return relating_type == "FLOORING"
|
||||
return tool.Covering.covering_poll_relating_type_check(cls, context, "FLOORING")
|
||||
|
||||
def _execute(self, context):
|
||||
try:
|
||||
@@ -50,9 +49,7 @@ class AddInstanceCeilingCoveringFromCursor(bpy.types.Operator, tool.Ifc.Operator
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
relating_type_id = int(bpy.data.scenes["Scene"].BIMModelProperties.relating_type_id)
|
||||
relating_type = ifcopenshell.util.element.get_predefined_type(tool.Ifc.get().by_id(relating_type_id))
|
||||
return relating_type == "CEILING"
|
||||
return tool.Covering.covering_poll_relating_type_check(cls, context, "CEILING")
|
||||
|
||||
def _execute(self, context):
|
||||
try:
|
||||
@@ -69,8 +66,10 @@ class RegenSelectedCoveringObject(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
return element and element.is_a("IfcCovering")
|
||||
if (obj := context.active_object) and (element := tool.Ifc.get_entity(obj)) and element.is_a("IfcCovering"):
|
||||
return True
|
||||
cls.poll_message_set("IfcCovering must be selected.")
|
||||
return False
|
||||
|
||||
def _execute(self, context):
|
||||
try:
|
||||
@@ -89,11 +88,7 @@ class AddInstanceFlooringCoveringsFromWalls(bpy.types.Operator, tool.Ifc.Operato
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
relating_type_id = int(bpy.data.scenes["Scene"].BIMModelProperties.relating_type_id)
|
||||
relating_type = ifcopenshell.util.element.get_predefined_type(tool.Ifc.get().by_id(relating_type_id))
|
||||
if element and element.is_a("IfcWall") and tool.Model.get_usage_type(element) == "LAYER2":
|
||||
return context.selected_objects and relating_type == "FLOORING"
|
||||
return tool.Covering.covering_poll_wall_selected(cls, context, "FLOORING")
|
||||
|
||||
def _execute(self, context):
|
||||
# This only works based on a 2D plan only considering the standard
|
||||
@@ -117,11 +112,7 @@ class AddInstanceCeilingCoveringsFromWalls(bpy.types.Operator, tool.Ifc.Operator
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
relating_type_id = int(bpy.data.scenes["Scene"].BIMModelProperties.relating_type_id)
|
||||
relating_type = ifcopenshell.util.element.get_predefined_type(tool.Ifc.get().by_id(relating_type_id))
|
||||
if element and element.is_a("IfcWall") and tool.Model.get_usage_type(element) == "LAYER2":
|
||||
return context.selected_objects and relating_type == "CEILING"
|
||||
return tool.Covering.covering_poll_wall_selected(cls, context, "CEILING")
|
||||
|
||||
def _execute(self, context):
|
||||
# This only works based on a 2D plan only considering the standard
|
||||
|
||||
@@ -21,6 +21,7 @@ import math
|
||||
import json
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.schema
|
||||
from ifcopenshell.util.doc import get_entity_doc, get_predefined_type_doc
|
||||
import blenderbim.tool as tool
|
||||
from math import degrees
|
||||
@@ -50,6 +51,7 @@ class AuthoringData:
|
||||
cls.props = bpy.context.scene.BIMModelProperties
|
||||
if ifc_element_type:
|
||||
cls.ifc_element_type = None if ifc_element_type == "all" else ifc_element_type
|
||||
cls.data["default_container"] = cls.default_container()
|
||||
cls.data["ifc_element_type"] = cls.ifc_element_type
|
||||
cls.data["ifc_classes"] = cls.ifc_classes()
|
||||
cls.data["relating_type_id"] = cls.relating_type_id() # only after .ifc_classes()
|
||||
@@ -74,6 +76,15 @@ class AuthoringData:
|
||||
cls.data["boundary_class"] = cls.boundary_class()
|
||||
cls.data["selected_material_usages"] = cls.selected_material_usages()
|
||||
|
||||
@classmethod
|
||||
def default_container(cls) -> str | None:
|
||||
props = bpy.context.scene.BIMSpatialDecompositionProperties
|
||||
if props.default_container:
|
||||
try:
|
||||
return tool.Ifc.get().by_id(props.default_container).Name
|
||||
except:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def boundary_class(cls):
|
||||
declaration = tool.Ifc.schema().declaration_by_name("IfcRelSpaceBoundary")
|
||||
|
||||
@@ -25,6 +25,8 @@ import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.representation
|
||||
import ifcopenshell.util.schema
|
||||
import ifcopenshell.util.unit
|
||||
from ifcopenshell.util.shape_builder import V
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.geometry
|
||||
|
||||
@@ -63,28 +63,6 @@ class PieAddOpening(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class PieUpdateContainer(bpy.types.Operator):
|
||||
bl_idname = "bim.pie_update_container"
|
||||
bl_label = "Update Spatial Container"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
for obj in context.selected_objects:
|
||||
if not obj.BIMObjectProperties.ifc_definition_id:
|
||||
continue
|
||||
for collection in obj.users_collection:
|
||||
spatial_obj = collection.BIMCollectionProperties.obj
|
||||
if spatial_obj and spatial_obj.BIMObjectProperties.ifc_definition_id:
|
||||
blenderbim.core.spatial.assign_container(
|
||||
tool.Ifc, tool.Collector, tool.Spatial, structure_obj=spatial_obj, element_obj=obj
|
||||
)
|
||||
break
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class VIEW3D_MT_PIE_bim(bpy.types.Menu):
|
||||
bl_label = "Geometry"
|
||||
|
||||
@@ -93,7 +71,6 @@ class VIEW3D_MT_PIE_bim(bpy.types.Menu):
|
||||
pie.operator("bim.edit_object_placement")
|
||||
pie.operator("bim.update_representation").ifc_representation_class = ""
|
||||
pie.operator("bim.pie_add_opening")
|
||||
pie.operator("bim.pie_update_container")
|
||||
pie.operator("bim.open_pie_class", text="Assign IFC Class")
|
||||
pie.operator("bim.aggregate_assign_object", text="Assign Aggregation")
|
||||
pie.operator("bim.aggregate_unassign_object", text="Unassign Aggregation")
|
||||
|
||||
@@ -225,9 +225,8 @@ class AddConstrTypeInstance(bpy.types.Operator):
|
||||
else:
|
||||
parent = ifcopenshell.util.element.get_container(building_element)
|
||||
if parent:
|
||||
parent_obj = tool.Ifc.get_object(parent)
|
||||
blenderbim.core.spatial.assign_container(
|
||||
tool.Ifc, tool.Collector, tool.Spatial, structure_obj=parent_obj, element_obj=obj
|
||||
tool.Ifc, tool.Collector, tool.Spatial, container=parent, element_obj=obj
|
||||
)
|
||||
|
||||
# set occurrences properties for the types defined with modifiers
|
||||
|
||||
@@ -21,6 +21,7 @@ import json
|
||||
import bmesh
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.representation
|
||||
import ifcopenshell.util.unit
|
||||
import blenderbim.core.root
|
||||
|
||||
@@ -503,6 +503,7 @@ class BimToolUI:
|
||||
def draw_type_selection_interface(cls):
|
||||
# shared by both sidebar and header
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text=f"Container: {AuthoringData.data['default_container']}", icon="OUTLINER_COLLECTION")
|
||||
if AuthoringData.data["ifc_classes"]:
|
||||
if not AuthoringData.data["ifc_element_type"]:
|
||||
row.label(text="", icon="FILE_VOLUME")
|
||||
|
||||
@@ -67,7 +67,6 @@ class ExecuteIfcPatch(bpy.types.Operator):
|
||||
bl_idname = "bim.execute_ifc_patch"
|
||||
bl_label = "Execute IFCPatch"
|
||||
file_format: bpy.props.StringProperty()
|
||||
use_json_for_args: bpy.props.BoolProperty()
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -76,9 +75,8 @@ class ExecuteIfcPatch(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMPatchProperties
|
||||
if self.use_json_for_args or not props.ifc_patch_args_attr:
|
||||
arguments = json.loads(props.ifc_patch_args or "[]")
|
||||
else:
|
||||
arguments = []
|
||||
if props.ifc_patch_args_attr:
|
||||
arguments = [arg.get_value() for arg in props.ifc_patch_args_attr]
|
||||
|
||||
if props.should_load_from_memory and tool.Ifc.get():
|
||||
@@ -124,23 +122,48 @@ class UpdateIfcPatchArguments(bpy.types.Operator):
|
||||
new_attr = patch_args.add()
|
||||
data_type = arg_info.get("type", "str")
|
||||
if isinstance(data_type, list):
|
||||
if "file" in data_type:
|
||||
data_type = ["file"]
|
||||
|
||||
data_type = [dt for dt in data_type if dt != "NoneType"][0]
|
||||
|
||||
new_attr.data_type = {
|
||||
"Literal": "enum",
|
||||
"file": "file",
|
||||
"str": "string",
|
||||
"float": "float",
|
||||
"int": "integer",
|
||||
"bool": "boolean",
|
||||
}[data_type]
|
||||
new_attr.name = arg_name
|
||||
new_attr.name = self.pretty_arg_name(arg_name)
|
||||
if new_attr.data_type == "enum":
|
||||
new_attr.enum_items = json.dumps(arg_info.get("enum_items", []))
|
||||
new_attr.enum_value = arg_info.get("default", new_attr.get_value_default())
|
||||
continue
|
||||
|
||||
if new_attr.data_type == "file":
|
||||
new_attr.filepath_value.single_file = arg_info.get("default", new_attr.get_value_default())
|
||||
new_attr.filter_glob = arg_info.get("filter_glob", "*.ifc;*.ifczip;*.ifcxml")
|
||||
continue
|
||||
|
||||
new_attr.set_value(arg_info.get("default", new_attr.get_value_default()))
|
||||
return {"FINISHED"}
|
||||
|
||||
def pretty_arg_name(self, arg_name: str):
|
||||
words = []
|
||||
|
||||
for word in arg_name.split("_"):
|
||||
word = word.strip().capitalize()
|
||||
|
||||
replacements = [("Dir", "Directory"), ("Sql", "SQL")]
|
||||
|
||||
for prev, new in replacements:
|
||||
word = word.replace(prev, new)
|
||||
|
||||
words.append(word)
|
||||
|
||||
return " ".join(words)
|
||||
|
||||
|
||||
class RunMigratePatch(bpy.types.Operator):
|
||||
bl_idname = "bim.run_migrate_patch"
|
||||
|
||||
@@ -65,6 +65,5 @@ class BIMPatchProperties(PropertyGroup):
|
||||
ifc_patch_recipes: EnumProperty(items=get_ifcpatch_recipes, name="Recipes", update=update_ifc_patch_recipe)
|
||||
ifc_patch_input: StringProperty(default="", name="IFC Patch Input IFC")
|
||||
ifc_patch_output: StringProperty(default="", name="IFC Patch Output IFC")
|
||||
ifc_patch_args: StringProperty(default="", name="Arguments")
|
||||
ifc_patch_args_attr: CollectionProperty(type=Attribute, name="Arguments")
|
||||
should_load_from_memory: BoolProperty(default=False, name="Load from Memory")
|
||||
|
||||
@@ -56,7 +56,4 @@ class BIM_PT_patch(bpy.types.Panel):
|
||||
|
||||
if props.ifc_patch_args_attr:
|
||||
draw_attributes(props.ifc_patch_args_attr, layout)
|
||||
else:
|
||||
layout.row().prop(props, "ifc_patch_args")
|
||||
op = layout.operator("bim.execute_ifc_patch")
|
||||
op.use_json_for_args = len(props.ifc_patch_args_attr) == 0
|
||||
|
||||
@@ -28,16 +28,15 @@ classes = (
|
||||
operator.BIM_OT_load_clipping_planes,
|
||||
operator.BIM_OT_save_clipping_planes,
|
||||
operator.ChangeLibraryElement,
|
||||
operator.ClearRecentIFCProjects,
|
||||
operator.CreateClippingPlane,
|
||||
operator.CreateProject,
|
||||
operator.ClearRecentIFCProjects,
|
||||
operator.DisableCulling,
|
||||
operator.DisableEditingHeader,
|
||||
operator.EditHeader,
|
||||
operator.EnableCulling,
|
||||
operator.EnableEditingHeader,
|
||||
operator.ExportIFC,
|
||||
operator.ExportIFCDeprecated,
|
||||
operator.FlipClippingPlane,
|
||||
operator.LinkIfc,
|
||||
operator.LoadLink,
|
||||
@@ -92,6 +91,7 @@ def register():
|
||||
bpy.utils.register_tool(workspace.ExploreTool, after={"builtin.transform"}, separator=True, group=False)
|
||||
bpy.types.Scene.BIMProjectProperties = bpy.props.PointerProperty(type=prop.BIMProjectProperties)
|
||||
bpy.app.handlers.load_post.append(decorator.toggle_decorations_on_load)
|
||||
bpy.types.TOPBAR_MT_file_import.append(ui.file_import_menu)
|
||||
bpy.types.TOPBAR_MT_file.prepend(ui.file_menu)
|
||||
bpy.types.TOPBAR_MT_file_context_menu.prepend(ui.file_menu)
|
||||
wm = bpy.context.window_manager
|
||||
|
||||
@@ -730,12 +730,12 @@ class LoadProject(bpy.types.Operator, IFCFileSelector):
|
||||
if not self.is_existing_ifc_file():
|
||||
return {"FINISHED"}
|
||||
|
||||
if tool.Blender.is_default_scene():
|
||||
if self.should_start_fresh_session and tool.Blender.is_default_scene():
|
||||
for obj in bpy.data.objects:
|
||||
bpy.data.objects.remove(obj)
|
||||
|
||||
filepath = Path(self.get_filepath())
|
||||
context.scene.BIMProperties.ifc_file = str(filepath)
|
||||
context.scene.BIMProperties.ifc_file = filepath.as_posix()
|
||||
context.scene.BIMProjectProperties.is_loading = True
|
||||
context.scene.BIMProjectProperties.total_elements = len(tool.Ifc.get().by_type("IfcElement"))
|
||||
tool.Blender.register_toolbar()
|
||||
@@ -743,6 +743,8 @@ class LoadProject(bpy.types.Operator, IFCFileSelector):
|
||||
|
||||
if not self.is_advanced:
|
||||
bpy.ops.bim.load_project_elements()
|
||||
if not self.should_start_fresh_session:
|
||||
bpy.ops.bim.convert_to_blender()
|
||||
except:
|
||||
blenderbim.last_error = traceback.format_exc()
|
||||
raise
|
||||
@@ -1235,6 +1237,10 @@ class ExportIFCBase:
|
||||
if bpy.data.is_saved:
|
||||
layout.prop(self, "use_relative_path")
|
||||
|
||||
layout.separator()
|
||||
layout.label(text="Supported formats for export:")
|
||||
layout.label(text=".ifc, .ifczip, .ifcjson")
|
||||
|
||||
def invoke(self, context, event):
|
||||
if not tool.Ifc.get():
|
||||
bpy.ops.wm.save_mainfile("INVOKE_DEFAULT")
|
||||
@@ -1333,18 +1339,6 @@ class ExportIFC(ExportIFCBase, bpy.types.Operator):
|
||||
pass
|
||||
|
||||
|
||||
# TODO: remove as deprecated, better wait couple releases since
|
||||
# this operator is used for saving IFC files in user scripts.
|
||||
class ExportIFCDeprecated(ExportIFCBase, bpy.types.Operator):
|
||||
bl_idname = "export_ifc.bim"
|
||||
|
||||
def execute(self, context):
|
||||
msg = f"'{ExportIFCDeprecated.bl_idname}' operator name is deprecated, use '{ExportIFC.bl_idname}'."
|
||||
self.report({"WARNING"}, msg)
|
||||
print(msg)
|
||||
return super().execute(context)
|
||||
|
||||
|
||||
class LoadLinkedProject(bpy.types.Operator):
|
||||
bl_idname = "bim.load_linked_project"
|
||||
bl_label = "Load a project for viewing only."
|
||||
@@ -1880,8 +1874,7 @@ class AppendInspectedLinkedElement(AppendLibraryElement):
|
||||
)
|
||||
self.import_product_from_ifc(element, context)
|
||||
element_type = ifcopenshell.util.element.get_type(element)
|
||||
obj = tool.Ifc.get_object(element_type)
|
||||
if obj is None:
|
||||
if element_type and tool.Ifc.get_object(element_type) is None:
|
||||
self.import_type_from_ifc(element_type, context)
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
import os
|
||||
import bpy
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.placement
|
||||
from blenderbim.bim.module.project.data import ProjectData
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
@@ -25,6 +25,11 @@ from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.project.data import ProjectData, LinksData
|
||||
|
||||
|
||||
def file_import_menu(self, context):
|
||||
op = self.layout.operator("bim.load_project", text="IFC (Geometry Only) (.ifc/.ifczip/.ifcxml)")
|
||||
op.should_start_fresh_session = False
|
||||
|
||||
|
||||
class BIM_MT_project(Menu):
|
||||
bl_idname = "BIM_MT_project"
|
||||
bl_label = "New IFC Project"
|
||||
@@ -123,6 +128,15 @@ class BIM_PT_project(Panel):
|
||||
if pprops.is_loading:
|
||||
self.draw_advanced_loading_ui(context)
|
||||
elif self.file or props.ifc_file:
|
||||
if props.has_blend_warning:
|
||||
box = self.layout.box()
|
||||
box.alert = True
|
||||
row = box.row(align=True)
|
||||
row.label(text="Your Model May Be Outdated", icon="ERROR")
|
||||
op = row.operator("bim.open_uri", text="", icon="QUESTION")
|
||||
op.uri = "https://docs.blenderbim.org/users/troubleshooting.html#saving-and-loading-blend-files"
|
||||
row.operator("bim.close_blend_warning", text="", icon="CANCEL")
|
||||
|
||||
if props.ifc_file:
|
||||
self.draw_loaded_project_ui(context)
|
||||
else:
|
||||
@@ -294,9 +308,8 @@ class BIM_PT_new_project_wizard(Panel):
|
||||
row.prop(props, "volume_unit", text="Volume Unit")
|
||||
prop_with_search(self.layout, pprops, "template_file", text="Template")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row = self.layout.row()
|
||||
row.operator("bim.create_project")
|
||||
row.operator("bim.load_project").should_start_fresh_session = False
|
||||
|
||||
|
||||
class BIM_PT_project_library(Panel):
|
||||
|
||||
@@ -51,13 +51,13 @@ class Data:
|
||||
psetqtos = ifcopenshell.util.element.get_psets(
|
||||
element, psets_only=psets_only, qtos_only=qtos_only, should_inherit=False
|
||||
)
|
||||
for name, data in psetqtos.items():
|
||||
for name, data in sorted(psetqtos.items()):
|
||||
results.append(
|
||||
{
|
||||
"id": data["id"],
|
||||
"Name": name,
|
||||
"is_expanded": is_expanded.get(data["id"], True),
|
||||
"Properties": [{"Name": k, "NominalValue": v} for k, v in data.items() if k != "id"],
|
||||
"Properties": [{"Name": k, "NominalValue": v} for k, v in sorted(data.items()) if k != "id"],
|
||||
}
|
||||
)
|
||||
return sorted(results, key=lambda v: v["Name"])
|
||||
|
||||
@@ -19,9 +19,10 @@
|
||||
import bpy
|
||||
import json
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.unit
|
||||
import ifcopenshell.util.pset
|
||||
import ifcopenshell.util.attribute
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.pset
|
||||
import ifcopenshell.util.unit
|
||||
import blenderbim.bim.schema
|
||||
import blenderbim.bim.helper
|
||||
import blenderbim.bim.handler
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import blenderbim.tool as tool
|
||||
from bpy.types import Panel
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
@@ -34,16 +35,21 @@ from blenderbim.bim.module.pset.data import (
|
||||
ProfilePsetsData,
|
||||
WorkSchedulePsetsData,
|
||||
)
|
||||
from typing import Any, Optional
|
||||
|
||||
|
||||
def draw_property(prop, layout, copy_operator=None):
|
||||
def draw_property(
|
||||
prop: bpy.types.PropertyGroup, layout: bpy.types.UILayout, copy_operator: Optional[str] = None
|
||||
) -> None:
|
||||
if prop.value_type == "IfcPropertySingleValue":
|
||||
draw_single_property(prop, layout, copy_operator)
|
||||
elif prop.value_type == "IfcPropertyEnumeratedValue":
|
||||
draw_enumerated_property(prop, layout, copy_operator)
|
||||
|
||||
|
||||
def draw_single_property(prop, layout, copy_operator=None):
|
||||
def draw_single_property(
|
||||
prop: bpy.types.PropertyGroup, layout: bpy.types.UILayout, copy_operator: Optional[str] = None
|
||||
) -> None:
|
||||
value_name = prop.metadata.get_value_name(display_only=True)
|
||||
if not value_name:
|
||||
layout.label(text=prop["Name"])
|
||||
@@ -63,7 +69,9 @@ def draw_single_property(prop, layout, copy_operator=None):
|
||||
op.name = prop.metadata.name
|
||||
|
||||
|
||||
def draw_enumerated_property(prop, layout, copy_operator=None):
|
||||
def draw_enumerated_property(
|
||||
prop: bpy.types.PropertyGroup, layout: bpy.types.UILayout, copy_operator: Optional[str] = None
|
||||
) -> None:
|
||||
value_name = prop.metadata.get_value_name()
|
||||
if not value_name:
|
||||
layout.label(text=prop.metadata.name)
|
||||
@@ -78,13 +86,21 @@ def draw_enumerated_property(prop, layout, copy_operator=None):
|
||||
op.name = prop.metadata.name
|
||||
|
||||
|
||||
def get_active_pset_obj_name(context, obj_type):
|
||||
def get_active_pset_obj_name(context: bpy.types.Context, obj_type: tool.Ifc.OBJECT_TYPE) -> str:
|
||||
if obj_type in ("Object", "MaterialSet", "MaterialSetItem"):
|
||||
return context.active_object.name
|
||||
return ""
|
||||
|
||||
|
||||
def draw_psetqto_ui(context, pset_id, pset, props, layout, obj_type, allow_removing=True):
|
||||
def draw_psetqto_ui(
|
||||
context: bpy.types.Context,
|
||||
pset_id: int,
|
||||
pset: dict[str, Any],
|
||||
props: bpy.types.PropertyGroup,
|
||||
layout: bpy.types.UILayout,
|
||||
obj_type: tool.Ifc.OBJECT_TYPE,
|
||||
allow_removing: bool = True,
|
||||
) -> None:
|
||||
box = layout.box()
|
||||
row = box.row(align=True)
|
||||
if "is_expanded" not in pset:
|
||||
@@ -155,7 +171,9 @@ def draw_psetqto_ui(context, pset_id, pset, props, layout, obj_type, allow_remov
|
||||
row.label(text="No Properties Set")
|
||||
|
||||
|
||||
def draw_psetqto_editable_ui(box, props, prop):
|
||||
def draw_psetqto_editable_ui(
|
||||
box: bpy.types.UILayout, props: bpy.types.PropertyGroup, prop: bpy.types.PropertyGroup
|
||||
) -> None:
|
||||
row = box.row(align=True)
|
||||
draw_property(prop, row, copy_operator="bim.copy_property_to_selection")
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ classes = (
|
||||
operator.CalculateFaceAreas,
|
||||
operator.CalculateFormworkArea,
|
||||
operator.CalculateObjectVolumes,
|
||||
operator.CalculateSideFormworkArea,
|
||||
operator.CalculateSingleQuantity,
|
||||
operator.PerformQuantityTakeOff,
|
||||
prop.BIMQtoProperties,
|
||||
|
||||
@@ -18,25 +18,28 @@
|
||||
|
||||
import bpy
|
||||
import bmesh
|
||||
from typing import Callable
|
||||
|
||||
|
||||
def calculate_height(obj):
|
||||
def calculate_height(obj: bpy.types.Object) -> float:
|
||||
return obj.dimensions[2]
|
||||
|
||||
|
||||
def calculate_edges_lengths(objs, context):
|
||||
def calculate_edges_lengths(objs: list[bpy.types.Object], context: bpy.types.Context):
|
||||
return calculate_mesh_quantity(objs, context, lambda bm: sum((e.calc_length() for e in bm.edges if e.select)))
|
||||
|
||||
|
||||
def calculate_faces_areas(objs, context):
|
||||
def calculate_faces_areas(objs: list[bpy.types.Object], context: bpy.types.Context) -> float:
|
||||
return calculate_mesh_quantity(objs, context, lambda bm: sum((f.calc_area() for f in bm.faces if f.select)))
|
||||
|
||||
|
||||
def calculate_volumes(objs, context):
|
||||
def calculate_volumes(objs: list[bpy.types.Object], context: bpy.types.Context) -> float:
|
||||
return calculate_mesh_quantity(objs, context, lambda bm: bm.calc_volume())
|
||||
|
||||
|
||||
def calculate_mesh_quantity(objs: bpy.types.Object, context, operation):
|
||||
def calculate_mesh_quantity(
|
||||
objs: list[bpy.types.Object], context: bpy.types.Context, operation: Callable[[bmesh.types.BMesh], float]
|
||||
) -> float:
|
||||
"""Get the sum of the target quantity on all passed mesh objects
|
||||
|
||||
:param objs: iterable of mesh object
|
||||
@@ -58,7 +61,7 @@ def calculate_mesh_quantity(objs: bpy.types.Object, context, operation):
|
||||
return result
|
||||
|
||||
|
||||
def calculate_formwork_area(objs, context):
|
||||
def calculate_formwork_area(objs: list[bpy.types.Object], context: bpy.types.Context) -> float:
|
||||
"""
|
||||
Formwork is defined as the surface area required to cover all exposed
|
||||
surfaces of one or more objects, excluding top surfaces (i.e. that have a
|
||||
@@ -85,6 +88,7 @@ def calculate_formwork_area(objs, context):
|
||||
copied_obj.name = "Formwork"
|
||||
copied_obj.BIMObjectProperties.ifc_definition_id = 0
|
||||
modifier = copied_obj.modifiers.new("Formwork", "REMESH")
|
||||
assert isinstance(modifier, bpy.types.RemeshModifier)
|
||||
modifier.mode = "SHARP"
|
||||
# This hardcoded value may be optimised through a better understanding of the octree division.
|
||||
# These values are based off some trial and error heuristics I've learned through experience.
|
||||
@@ -108,7 +112,7 @@ def calculate_formwork_area(objs, context):
|
||||
return result
|
||||
|
||||
|
||||
def calculate_side_formwork_area(objs, context):
|
||||
def calculate_side_formwork_area(objs: list[bpy.types.Object], context: bpy.types.Context) -> float:
|
||||
"""
|
||||
Side formwork is defined as the surface area required to cover all exposed
|
||||
surfaces of one or more objects, excluding top and bottom surfaces (i.e.
|
||||
|
||||
@@ -99,6 +99,21 @@ class CalculateFormworkArea(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class CalculateSideFormworkArea(bpy.types.Operator):
|
||||
bl_idname = "bim.calculate_side_formwork_area"
|
||||
bl_label = "Calculate Side Formwork Area"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.selected_objects and context.active_object
|
||||
|
||||
def execute(self, context):
|
||||
result = helper.calculate_side_formwork_area([o for o in context.selected_objects if o.type == "MESH"], context)
|
||||
context.scene.BIMQtoProperties.qto_result = str(round(result, 3))
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class CalculateSingleQuantity(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.calculate_single_quantity"
|
||||
bl_label = "Calculate Single Quantity"
|
||||
@@ -137,7 +152,10 @@ class PerformQuantityTakeOff(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.perform_quantity_take_off"
|
||||
bl_label = "Perform Quantity Take-off"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Perform a quantity take off based of a QTO rule configuration"
|
||||
bl_description = (
|
||||
"Perform a quantity take off on selected objects based of a QTO rule configuration."
|
||||
"If no objects are selected, quantities calculated for all available IfcElements."
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -162,4 +180,6 @@ class PerformQuantityTakeOff(bpy.types.Operator, tool.Ifc.Operator):
|
||||
ifc_file = tool.Ifc.get()
|
||||
results = ifc5d.qto.quantify(ifc_file, elements, rules)
|
||||
ifc5d.qto.edit_qtos(ifc_file, results)
|
||||
|
||||
self.report({"INFO"}, f"Quantities are calculated for {len(elements)} elements.")
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
import bpy
|
||||
import blenderbim.tool as tool
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.constraint
|
||||
import ifcopenshell.util.cost
|
||||
import ifcopenshell.util.date
|
||||
import ifcopenshell.util.resource
|
||||
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.schema
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim.module.root.data import IfcClassData
|
||||
|
||||
@@ -175,6 +175,9 @@ class Search(Operator):
|
||||
props = context.scene.CsvProperties
|
||||
elif self.property_group == "BIMSearchProperties":
|
||||
props = context.scene.BIMSearchProperties
|
||||
else:
|
||||
raise Exception(f"bim.search - unexpected property group name '{self.property_group}'.")
|
||||
|
||||
results = ifcopenshell.util.selector.filter_elements(
|
||||
tool.Ifc.get(), tool.Search.export_filter_query(props.filter_groups)
|
||||
)
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
import bpy
|
||||
import blenderbim.tool as tool
|
||||
from ifcopenshell import util
|
||||
import ifcopenshell.util.schema
|
||||
from blenderbim.bim.prop import ObjProperty, BIMFilterGroup
|
||||
from blenderbim.bim.module.search.data import SearchData, ColourByPropertyData, SelectSimilarData
|
||||
from bpy.types import PropertyGroup
|
||||
@@ -134,6 +134,6 @@ class BIMSearchProperties(PropertyGroup):
|
||||
|
||||
def get_classes(self, ifc_product):
|
||||
declaration = tool.Ifc.schema().declaration_by_name(ifc_product)
|
||||
declarations = util.schema.get_subtypes(declaration)
|
||||
declarations = ifcopenshell.util.schema.get_subtypes(declaration)
|
||||
names = [d.name() for d in declarations]
|
||||
return [(c, c, "") for c in sorted(names)]
|
||||
|
||||
@@ -20,6 +20,7 @@ import bpy
|
||||
import isodate
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.attribute
|
||||
import ifcopenshell.util.date
|
||||
from ifcopenshell.util.doc import get_predefined_type_doc
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.sequence as core
|
||||
|
||||
@@ -21,7 +21,6 @@ from . import ui, prop, operator, workspace
|
||||
|
||||
classes = (
|
||||
operator.AssignContainer,
|
||||
operator.ChangeSpatialLevel,
|
||||
operator.ContractContainer,
|
||||
operator.CopyToContainer,
|
||||
operator.DeleteContainer,
|
||||
@@ -39,14 +38,11 @@ classes = (
|
||||
operator.SelectSimilarContainer,
|
||||
operator.SetContainerVisibility,
|
||||
operator.SetDefaultContainer,
|
||||
prop.SpatialElement,
|
||||
prop.Element,
|
||||
prop.BIMSpatialProperties,
|
||||
prop.BIMObjectSpatialProperties,
|
||||
prop.BIMContainer,
|
||||
prop.BIMSpatialDecompositionProperties,
|
||||
ui.BIM_PT_spatial,
|
||||
ui.BIM_UL_containers,
|
||||
ui.BIM_UL_containers_manager,
|
||||
ui.BIM_UL_elements,
|
||||
ui.BIM_PT_spatial_decomposition,
|
||||
@@ -57,7 +53,6 @@ classes = (
|
||||
def register():
|
||||
if not bpy.app.background:
|
||||
bpy.utils.register_tool(workspace.SpatialTool, after={"bim.annotation_tool"}, separator=False, group=False)
|
||||
bpy.types.Scene.BIMSpatialProperties = bpy.props.PointerProperty(type=prop.BIMSpatialProperties)
|
||||
bpy.types.Object.BIMObjectSpatialProperties = bpy.props.PointerProperty(type=prop.BIMObjectSpatialProperties)
|
||||
bpy.types.Scene.BIMSpatialDecompositionProperties = bpy.props.PointerProperty(
|
||||
type=prop.BIMSpatialDecompositionProperties
|
||||
@@ -67,6 +62,5 @@ def register():
|
||||
def unregister():
|
||||
if not bpy.app.background:
|
||||
bpy.utils.unregister_tool(workspace.SpatialTool)
|
||||
del bpy.types.Scene.BIMSpatialProperties
|
||||
del bpy.types.Object.BIMObjectSpatialProperties
|
||||
del bpy.types.Scene.BIMSpatialDecompositionProperties
|
||||
|
||||
@@ -37,7 +37,8 @@ class SpatialData:
|
||||
if cls.data["poll"]:
|
||||
cls.data.update(
|
||||
{
|
||||
"parent_container_id": cls.parent_container_id(),
|
||||
"default_container": cls.default_container(),
|
||||
"selected_containers": cls.selected_containers(),
|
||||
"is_directly_contained": cls.is_directly_contained(),
|
||||
"label": cls.label(),
|
||||
"references": cls.references(),
|
||||
@@ -56,14 +57,17 @@ class SpatialData:
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def parent_container_id(cls):
|
||||
container_id = bpy.context.scene.BIMSpatialProperties.active_container_id
|
||||
if not container_id:
|
||||
return
|
||||
container = tool.Ifc.get_entity_by_id(container_id)
|
||||
if not container or container.is_a("IfcProject"):
|
||||
return
|
||||
return container.Decomposes[0].RelatingObject.id()
|
||||
def default_container(cls) -> str | None:
|
||||
props = bpy.context.scene.BIMSpatialDecompositionProperties
|
||||
if props.default_container:
|
||||
try:
|
||||
return tool.Ifc.get().by_id(props.default_container).Name
|
||||
except:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def selected_containers(cls):
|
||||
return sorted([e.Name or "Unnamed" for e in tool.Spatial.get_selected_containers()])
|
||||
|
||||
@classmethod
|
||||
def label(cls):
|
||||
@@ -97,7 +101,7 @@ class SpatialDecompositionData:
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def default_container(cls) -> str:
|
||||
def default_container(cls) -> str | None:
|
||||
props = bpy.context.scene.BIMSpatialDecompositionProperties
|
||||
if props.default_container:
|
||||
try:
|
||||
|
||||
@@ -29,13 +29,21 @@ import blenderbim.bim.handler
|
||||
class ReferenceStructure(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.reference_structure"
|
||||
bl_label = "Reference Structure"
|
||||
bl_description = (
|
||||
"Reference selected objects from all selected structures.\n\n"
|
||||
"Currently we do not support referencing structures in other structures "
|
||||
"though it is allowed in IFC4X3"
|
||||
)
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
structure: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
sprops = context.scene.BIMSpatialProperties
|
||||
containers = [tool.Ifc.get().by_id(c.ifc_definition_id) for c in sprops.containers if c.is_selected]
|
||||
for obj in context.selected_objects:
|
||||
objs = tool.Spatial.get_selected_objects_without_containers()
|
||||
if not objs:
|
||||
self.report({"INFO"}, "No non-spatial objects are selected.")
|
||||
return
|
||||
|
||||
containers = tool.Spatial.get_selected_containers()
|
||||
for obj in objs:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
continue
|
||||
@@ -46,13 +54,21 @@ class ReferenceStructure(bpy.types.Operator, tool.Ifc.Operator):
|
||||
class DereferenceStructure(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.dereference_structure"
|
||||
bl_label = "Dereference Structure"
|
||||
bl_description = (
|
||||
"Dereference selected objects from all selected structures.\n\n"
|
||||
"Currently we do not support referencing structures in other structures "
|
||||
"though it is allowed in IFC4X3"
|
||||
)
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
structure: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
sprops = context.scene.BIMSpatialProperties
|
||||
containers = [tool.Ifc.get().by_id(c.ifc_definition_id) for c in sprops.containers if c.is_selected]
|
||||
for obj in context.selected_objects:
|
||||
objs = tool.Spatial.get_selected_objects_without_containers()
|
||||
if not objs:
|
||||
self.report({"INFO"}, "No non-spatial objects are selected.")
|
||||
return
|
||||
|
||||
containers = tool.Spatial.get_selected_containers()
|
||||
for obj in objs:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
continue
|
||||
@@ -63,15 +79,16 @@ class DereferenceStructure(bpy.types.Operator, tool.Ifc.Operator):
|
||||
class AssignContainer(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.assign_container"
|
||||
bl_label = "Assign Container"
|
||||
bl_description = "Assign current default container to the selected objects"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
structure: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
structure_obj = tool.Ifc.get_object(tool.Ifc.get().by_id(self.structure))
|
||||
for element_obj in context.selected_objects:
|
||||
core.assign_container(
|
||||
tool.Ifc, tool.Collector, tool.Spatial, structure_obj=structure_obj, element_obj=element_obj
|
||||
)
|
||||
props = context.active_object.BIMObjectSpatialProperties
|
||||
if (container_obj := props.container_obj) and (container := tool.Ifc.get_entity(container_obj)):
|
||||
for element_obj in context.selected_objects:
|
||||
core.assign_container(
|
||||
tool.Ifc, tool.Collector, tool.Spatial, container=container, element_obj=element_obj
|
||||
)
|
||||
|
||||
|
||||
class EnableEditingContainer(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -83,16 +100,6 @@ class EnableEditingContainer(bpy.types.Operator, tool.Ifc.Operator):
|
||||
core.enable_editing_container(tool.Spatial, obj=context.active_object)
|
||||
|
||||
|
||||
class ChangeSpatialLevel(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.change_spatial_level"
|
||||
bl_label = "Change Spatial Level"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
parent: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.change_spatial_level(tool.Spatial, parent=tool.Ifc.get().by_id(self.parent))
|
||||
|
||||
|
||||
class DisableEditingContainer(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.disable_editing_container"
|
||||
bl_label = "Disable Editing Container"
|
||||
@@ -114,30 +121,36 @@ class RemoveContainer(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
class CopyToContainer(bpy.types.Operator, tool.Ifc.Operator):
|
||||
"""
|
||||
Copies selected 3D elements in the viewport to checkmarked spatial containers
|
||||
Copies selected 3D elements in the viewport to the selected spatial containers
|
||||
|
||||
Example: bulk copy a wall to multiple storeys
|
||||
|
||||
1. Select one or more 3D elements in the viewport
|
||||
2. Enable the checkmark next to one or more containers in the container list below to select it
|
||||
2. Select one or more spatial containers in the viewport
|
||||
3. Press this button
|
||||
4. The copied elements will have a new position relative to the destination containers
|
||||
"""
|
||||
|
||||
Copying containers to other containers currently is not supported."""
|
||||
|
||||
bl_idname = "bim.copy_to_container"
|
||||
bl_label = "Copy To Container"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
sprops = context.scene.BIMSpatialProperties
|
||||
objs = tool.Spatial.get_selected_objects_without_containers()
|
||||
if not objs:
|
||||
self.report({"INFO"}, "No non-spatial objects are selected.")
|
||||
return
|
||||
|
||||
containers = tool.Spatial.get_selected_containers()
|
||||
# Track decompositions so they can be recreated after the operation
|
||||
relationships = tool.Root.get_decomposition_relationships(context.selected_objects)
|
||||
relationships = tool.Root.get_decomposition_relationships(objs)
|
||||
old_to_new = {}
|
||||
containers = [tool.Ifc.get().by_id(c.ifc_definition_id) for c in sprops.containers if c.is_selected]
|
||||
for obj in context.selected_objects:
|
||||
for obj in objs:
|
||||
result_objs = core.copy_to_container(tool.Ifc, tool.Collector, tool.Spatial, obj=obj, containers=containers)
|
||||
if result_objs:
|
||||
old_to_new[tool.Ifc.get_entity(obj)] = result_objs
|
||||
|
||||
# Recreate decompositions
|
||||
tool.Root.recreate_decompositions(relationships, old_to_new)
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
@@ -237,15 +250,36 @@ class SelectDecomposedElements(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.select_decomposed_elements"
|
||||
bl_label = "Select Children"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
should_filter: bpy.props.BoolProperty(name="Should Filter", default=True, options={"SKIP_SAVE"})
|
||||
container: bpy.props.IntProperty()
|
||||
ifc_class: bpy.props.StringProperty()
|
||||
relating_type: bpy.props.IntProperty()
|
||||
|
||||
@classmethod
|
||||
def description(cls, context, operator):
|
||||
return f"Select all elements contained in this {operator.ifc_class}"
|
||||
return "Select all contained elements filtered by this type" + "\nALT+CLICK to select all contained elements"
|
||||
|
||||
def invoke(self, context, event):
|
||||
if event.type == "LEFTMOUSE" and event.alt:
|
||||
self.should_filter = False
|
||||
return self.execute(context)
|
||||
|
||||
def _execute(self, context):
|
||||
core.select_decomposed_elements(tool.Spatial, container=tool.Ifc.get().by_id(self.container))
|
||||
if self.should_filter:
|
||||
ifc_class = self.ifc_class
|
||||
relating_type = tool.Ifc.get().by_id(self.relating_type) if self.relating_type else None
|
||||
is_untyped = self.relating_type == 0
|
||||
else:
|
||||
ifc_class = ""
|
||||
relating_type = None
|
||||
is_untyped = False
|
||||
core.select_decomposed_elements(
|
||||
tool.Spatial,
|
||||
container=tool.Ifc.get().by_id(self.container),
|
||||
ifc_class=ifc_class,
|
||||
relating_type=relating_type,
|
||||
is_untyped=is_untyped,
|
||||
)
|
||||
|
||||
|
||||
class SetDefaultContainer(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
@@ -32,6 +32,7 @@ from bpy.props import (
|
||||
)
|
||||
import blenderbim.tool as tool
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
def get_subelement_class(self, context):
|
||||
@@ -63,47 +64,32 @@ def update_should_include_children(self, context):
|
||||
tool.Spatial.load_contained_elements()
|
||||
|
||||
|
||||
def update_relating_container_from_object(self, context):
|
||||
if self.relating_container_object is None or context.active_object is None:
|
||||
def update_container_obj(self, context):
|
||||
if self.container_obj is None or not (obj := context.active_object):
|
||||
return
|
||||
element = tool.Ifc.get_entity(self.relating_container_object)
|
||||
if not element:
|
||||
if not (element := tool.Ifc.get_entity(self.container_obj)):
|
||||
self.container_obj = None
|
||||
return
|
||||
container = ifcopenshell.util.element.get_container(element)
|
||||
if container:
|
||||
bpy.ops.bim.assign_container(structure=container.id())
|
||||
else:
|
||||
bpy.ops.bim.disable_editing_container()
|
||||
bpy.ops.bim.remove_container()
|
||||
if tool.Spatial.can_contain(element, obj):
|
||||
return
|
||||
if (
|
||||
(container := ifcopenshell.util.element.get_container(element))
|
||||
and (container_obj := tool.Ifc.get_object(container))
|
||||
and tool.Spatial.can_contain(container, obj)
|
||||
):
|
||||
self.container_obj = container_obj
|
||||
return
|
||||
self.container_obj = None
|
||||
|
||||
|
||||
def is_object_applicable(self, obj):
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
return bool(element)
|
||||
|
||||
|
||||
class SpatialElement(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
long_name: StringProperty(name="Long Name")
|
||||
has_decomposition: BoolProperty(name="Has Decomposition")
|
||||
ifc_definition_id: IntProperty(name="IFC Definition ID")
|
||||
is_selected: BoolProperty(name="Is Selected")
|
||||
|
||||
|
||||
class BIMSpatialProperties(PropertyGroup):
|
||||
containers: CollectionProperty(name="Containers", type=SpatialElement)
|
||||
active_container_index: IntProperty(name="Active Container Index")
|
||||
active_container_id: IntProperty(name="Active Container Id")
|
||||
def poll_container_obj(self, obj):
|
||||
return obj is None or tool.Ifc.get_entity(obj)
|
||||
|
||||
|
||||
class BIMObjectSpatialProperties(PropertyGroup):
|
||||
is_editing: BoolProperty(name="Is Editing")
|
||||
relating_container_object: PointerProperty(
|
||||
type=bpy.types.Object,
|
||||
name="Copy Container",
|
||||
update=update_relating_container_from_object,
|
||||
poll=is_object_applicable,
|
||||
description="Copy the target object's container to the active object",
|
||||
container_obj: PointerProperty(
|
||||
type=bpy.types.Object, name="Container", update=update_container_obj, poll=poll_container_obj
|
||||
)
|
||||
|
||||
|
||||
@@ -123,6 +109,7 @@ class Element(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
is_class: BoolProperty(name="Is Class", default=False)
|
||||
is_type: BoolProperty(name="Is Type", default=False)
|
||||
ifc_definition_id: IntProperty(name="IFC Definition ID")
|
||||
total: IntProperty(name="Total")
|
||||
|
||||
|
||||
@@ -144,3 +131,8 @@ class BIMSpatialDecompositionProperties(PropertyGroup):
|
||||
def active_container(self):
|
||||
if self.containers and self.active_container_index < len(self.containers):
|
||||
return self.containers[self.active_container_index]
|
||||
|
||||
@property
|
||||
def active_element(self):
|
||||
if self.elements and self.active_element_index < len(self.elements):
|
||||
return self.elements[self.active_element_index]
|
||||
|
||||
@@ -39,24 +39,32 @@ class BIM_PT_spatial(Panel):
|
||||
if not SpatialData.is_loaded:
|
||||
SpatialData.load()
|
||||
|
||||
props = context.scene.BIMSpatialProperties
|
||||
osprops = context.active_object.BIMObjectSpatialProperties
|
||||
|
||||
if osprops.is_editing:
|
||||
row = self.layout.row(align=True)
|
||||
if SpatialData.data["parent_container_id"]:
|
||||
op = row.operator("bim.change_spatial_level", text="", icon="FRAME_PREV")
|
||||
op.parent = SpatialData.data["parent_container_id"]
|
||||
if props.containers and props.active_container_index < len(props.containers):
|
||||
op = row.operator("bim.assign_container", icon="CHECKMARK")
|
||||
op.structure = props.containers[props.active_container_index].ifc_definition_id
|
||||
row.operator("bim.reference_structure", icon="LINKED", text="")
|
||||
row.operator("bim.dereference_structure", icon="UNLINKED", text="")
|
||||
row.operator("bim.copy_to_container", icon="COPYDOWN", text="")
|
||||
row.operator("bim.disable_editing_container", icon="CANCEL", text="")
|
||||
if SpatialData.data["default_container"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(osprops, "container_obj", text="", icon="OUTLINER_COLLECTION")
|
||||
row.operator("bim.assign_container", icon="CHECKMARK", text="")
|
||||
row.operator("bim.disable_editing_container", icon="CANCEL", text="")
|
||||
|
||||
self.layout.template_list("BIM_UL_containers", "", props, "containers", props, "active_container_index")
|
||||
self.layout.prop(osprops, "relating_container_object")
|
||||
if SpatialData.data["selected_containers"]:
|
||||
row = self.layout.row()
|
||||
row.label(text=f"{len(SpatialData.data['selected_containers'])} Selected Containers")
|
||||
for name in SpatialData.data["selected_containers"][:3]:
|
||||
row = self.layout.row()
|
||||
row.label(text=name, icon="OUTLINER_COLLECTION")
|
||||
if len(SpatialData.data["selected_containers"]) > 3:
|
||||
row = self.layout.row()
|
||||
row.label(text=f"... {len(SpatialData.data['selected_containers']) - 3} More")
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.reference_structure", icon="LINKED", text="Reference Selected")
|
||||
row.operator("bim.dereference_structure", icon="UNLINKED", text="")
|
||||
row = self.layout.row()
|
||||
row.operator("bim.copy_to_container", icon="COPYDOWN", text="Copy Object To Selected")
|
||||
else:
|
||||
row = self.layout.row()
|
||||
row.label(text="No Selected Containers")
|
||||
else:
|
||||
row = self.layout.row(align=True)
|
||||
if SpatialData.data["label"]:
|
||||
@@ -69,28 +77,17 @@ class BIM_PT_spatial(Panel):
|
||||
else:
|
||||
row.label(text="No Spatial Container")
|
||||
row.operator("bim.enable_editing_container", icon="GREASEPENCIL", text="")
|
||||
for reference in SpatialData.data["references"]:
|
||||
|
||||
if references := SpatialData.data["references"]:
|
||||
self.layout.label(text=f"{len(references)} References:")
|
||||
else:
|
||||
self.layout.label(text="No References Found")
|
||||
|
||||
for reference in references:
|
||||
row = self.layout.row()
|
||||
row.label(text=reference, icon="LINKED")
|
||||
|
||||
|
||||
class BIM_UL_containers(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if item:
|
||||
if item.has_decomposition:
|
||||
op = layout.operator("bim.change_spatial_level", text="", icon="DISCLOSURE_TRI_RIGHT", emboss=False)
|
||||
op.parent = item.ifc_definition_id
|
||||
layout.label(text=item.name)
|
||||
layout.label(text=item.long_name)
|
||||
layout.prop(
|
||||
item,
|
||||
"is_selected",
|
||||
icon="CHECKBOX_HLT" if item.is_selected else "CHECKBOX_DEHLT",
|
||||
text="",
|
||||
emboss=False,
|
||||
)
|
||||
|
||||
|
||||
class BIM_PT_spatial_decomposition(Panel):
|
||||
bl_label = "Spatial Decomposition"
|
||||
bl_idname = "BIM_PT_spatial_decomposition"
|
||||
@@ -161,15 +158,23 @@ class BIM_PT_spatial_decomposition(Panel):
|
||||
|
||||
if not self.props.total_elements:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="No Contained Elements", icon="FILE_3D")
|
||||
row.label(text=f"{self.props.active_container.ifc_class} > No Contained Elements", icon="FILE_3D")
|
||||
row.prop(self.props, "should_include_children", text="", icon="OUTLINER")
|
||||
return
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=f"{self.props.total_elements} Contained Elements", icon="FILE_3D")
|
||||
row.label(
|
||||
text=f"{self.props.active_container.ifc_class} > {self.props.total_elements} Contained Elements",
|
||||
icon="FILE_3D",
|
||||
)
|
||||
row.prop(self.props, "should_include_children", text="", icon="OUTLINER")
|
||||
op = row.operator("bim.select_decomposed_elements", icon="RESTRICT_SELECT_OFF", text="")
|
||||
op.ifc_class = self.props.active_container.ifc_class
|
||||
if self.props.active_element.is_class:
|
||||
op.ifc_class = self.props.active_element.name
|
||||
op.relating_type = 0
|
||||
elif self.props.active_element.is_type:
|
||||
op.ifc_class = ""
|
||||
op.relating_type = self.props.active_element.ifc_definition_id
|
||||
op.container = ifc_definition_id
|
||||
|
||||
self.layout.template_list(
|
||||
@@ -187,7 +192,20 @@ class BIM_UL_containers_manager(UIList):
|
||||
if item:
|
||||
row = layout.row(align=True)
|
||||
self.draw_hierarchy(row, item)
|
||||
row.prop(item, "name", emboss=False, text="")
|
||||
icon = {
|
||||
"IfcProject": "FILE",
|
||||
"IfcSite": "WORLD",
|
||||
"IfcBuilding": "HOME",
|
||||
"IfcBuildingStorey": "LINENUMBERS_OFF",
|
||||
"IfcSpace": "ANTIALIASED",
|
||||
"IfcFacilityPart": "MOD_FLUID",
|
||||
"IfcBridgePart": "MOD_FLUID",
|
||||
"IfcFacilityPartCommon": "MOD_FLUID",
|
||||
"IfcMarinePart": "MOD_FLUID",
|
||||
"IfcRailwayPart": "MOD_FLUID",
|
||||
"IfcRoadPart": "MOD_FLUID",
|
||||
}.get(item.ifc_class, "META_PLANE")
|
||||
row.prop(item, "name", emboss=False, text="", icon=icon)
|
||||
if item.long_name:
|
||||
row.prop(item, "long_name", emboss=False, text="")
|
||||
col = row.column()
|
||||
@@ -208,24 +226,6 @@ class BIM_UL_containers_manager(UIList):
|
||||
)
|
||||
else:
|
||||
row.label(text="", icon="BLANK1")
|
||||
if item.ifc_class == "IfcProject":
|
||||
row.label(text="", icon="FILE")
|
||||
else:
|
||||
icon = {
|
||||
"IfcSite": "WORLD",
|
||||
"IfcBuilding": "HOME",
|
||||
"IfcBuildingStorey": "LINENUMBERS_OFF",
|
||||
"IfcSpace": "ANTIALIASED",
|
||||
"IfcFacilityPart": "MOD_FLUID",
|
||||
"IfcBridgePart": "MOD_FLUID",
|
||||
"IfcFacilityPartCommon": "MOD_FLUID",
|
||||
"IfcMarinePart": "MOD_FLUID",
|
||||
"IfcRailwayPart": "MOD_FLUID",
|
||||
"IfcRoadPart": "MOD_FLUID",
|
||||
}.get(item.ifc_class, "META_PLANE")
|
||||
op = row.operator("bim.select_decomposed_elements", icon=icon, text="", emboss=False)
|
||||
op.ifc_class = item.ifc_class
|
||||
op.container = item.ifc_definition_id
|
||||
|
||||
|
||||
class BIM_UL_elements(UIList):
|
||||
@@ -240,7 +240,6 @@ class BIM_UL_elements(UIList):
|
||||
col.label(text=str(item.total))
|
||||
elif item.is_type:
|
||||
row.label(text="", icon="BLANK1")
|
||||
row.label(text="", icon="DISCLOSURE_TRI_DOWN")
|
||||
row.label(text=item.name)
|
||||
col = row.column()
|
||||
col.alignment = "RIGHT"
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.schema
|
||||
import blenderbim.tool as tool
|
||||
from ifcopenshell.util.doc import get_entity_doc
|
||||
from typing import Union
|
||||
|
||||
@@ -25,6 +25,7 @@ import blenderbim.core.style as core
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.api.style
|
||||
import ifcopenshell.util.representation
|
||||
import ifcopenshell.util.unit
|
||||
from pathlib import Path
|
||||
from mathutils import Vector
|
||||
|
||||
@@ -535,22 +536,22 @@ class RemoveTextureMap(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EnableAddingPresentationStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
class EnableAddingPresentationStyle(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_adding_presentation_style"
|
||||
bl_label = "Enable Add Presentation Style"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
style_type = bpy.context.scene.BIMStylesProperties.style_type
|
||||
style_type = tool.Style.get_active_style_type()
|
||||
if style_type != "IfcSurfaceStyle":
|
||||
cls.poll_message_set(f"Adding {style_type} is not currently supported.")
|
||||
return False
|
||||
return True
|
||||
|
||||
def _execute(self, context):
|
||||
props = bpy.context.scene.BIMStylesProperties
|
||||
props.is_adding = True
|
||||
def execute(self, context):
|
||||
tool.Style.enable_adding_presentation_style()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DuplicateStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -569,14 +570,14 @@ class DuplicateStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bpy.ops.bim.load_styles(style_type=style_type)
|
||||
|
||||
|
||||
class DisableAddingPresentationStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
class DisableAddingPresentationStyle(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_adding_presentation_style"
|
||||
bl_label = "Disable Add Presentation Style"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
props = bpy.context.scene.BIMStylesProperties
|
||||
props.is_adding = False
|
||||
def execute(self, context):
|
||||
tool.Style.disable_adding_presentation_style()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddPresentationStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -629,7 +630,7 @@ class AddPresentationStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
else:
|
||||
self.report({"INFO"}, f"Adding style of type {props.style_type} is not yet supported.")
|
||||
|
||||
props.is_adding = False
|
||||
tool.Style.disable_adding_presentation_style()
|
||||
core.load_styles(tool.Style, style_type=props.style_type)
|
||||
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user