mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-21 04:32:23 +00:00
Compare commits
192 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 54951d2102 | |||
| 8403c32e76 | |||
| 316934de97 | |||
| dd48fbcb0f | |||
| 54978a9868 | |||
| 02fd78df14 | |||
| ed1202e1f2 | |||
| d9de967197 | |||
| e57076c84f | |||
| 75e6495999 | |||
| fcf07bebfb | |||
| f190ddcd92 | |||
| 39a841fb4e | |||
| bdfd0c4aa7 | |||
| 37b14f2a3d | |||
| 70a4af2605 | |||
| 7142f3856c | |||
| f2ca030fab | |||
| 37cacfcd06 | |||
| 3e31309384 | |||
| a19b7614fa | |||
| 763ee9198d | |||
| 9ec8035a8d | |||
| 687bb32e37 | |||
| 4279b769aa | |||
| 9a59b8cdae | |||
| b9b0e81d25 | |||
| 80d31b4e1a | |||
| dac9bfe3d4 | |||
| 58a7ef62d7 | |||
| d2198568b2 | |||
| b087aa09d9 | |||
| fd62ba63a5 | |||
| 16c5fd56ca | |||
| d85f9af790 | |||
| 2f071bd809 | |||
| 7a44f9e8a7 | |||
| 59a19a1a70 | |||
| 6a8b59f2cf | |||
| ec28d3d0af | |||
| f576aadee1 | |||
| fffd7360cb | |||
| 64ea455c01 | |||
| cf635986a6 | |||
| ac8040008a | |||
| df15a37127 | |||
| 6d887d593e | |||
| fa87092fe0 | |||
| 1a4e4678d0 | |||
| 7f2c7eb467 | |||
| c70745826a | |||
| 291bffca95 | |||
| e5a316e509 | |||
| e4aa97091e | |||
| b4f0a346c1 | |||
| 073e471305 | |||
| 4d9f47b3a7 | |||
| 96088c1d08 | |||
| c405ff1adf | |||
| 02cae9c7c2 | |||
| 3c42962635 | |||
| 0336dbabf7 | |||
| a8729933af | |||
| 993ce46fcc | |||
| edfd446833 | |||
| 87c184ef8a | |||
| e9b9113df8 | |||
| f772a53dd5 | |||
| 85c6b6c4da | |||
| 1aaccba803 | |||
| f2eb08a066 | |||
| f460676e54 | |||
| 382e028b90 | |||
| 53d02446bf | |||
| 5a82d7a9d4 | |||
| 69c197c1a4 | |||
| 4027f28c85 | |||
| 3a1154c4ba | |||
| aeb1e4bf49 | |||
| 4fb34c0816 | |||
| 9b562669d2 | |||
| 295d0a677e | |||
| 9aacafd575 | |||
| ce6ce1b975 | |||
| d064ef1237 | |||
| 2db9bd9c39 | |||
| 7622504d68 | |||
| 221dd9a7a2 | |||
| 1b74a99667 | |||
| 9122f006c7 | |||
| 678dbaa66a | |||
| fb88499197 | |||
| f84adf67cd | |||
| cacb38a251 | |||
| 7c6c01e6b9 | |||
| 4e462a3cf7 | |||
| e7f4c6c9cc | |||
| 7bf8c0bbfb | |||
| c4157673f5 | |||
| 16285e0625 | |||
| a07d3b9669 | |||
| 2d0b356203 | |||
| f666053b63 | |||
| 3bdc969efb | |||
| 932817877c | |||
| cb255a18b9 | |||
| 183028570e | |||
| 341cdd4ef7 | |||
| fe68e16ca3 | |||
| a8f0c90d47 | |||
| 39f550529b | |||
| 25c1a14e64 | |||
| 9f7d710119 | |||
| ea592775e4 | |||
| 42be4eb064 | |||
| 8662e20c58 | |||
| da4ce3773f | |||
| 87c9bbe2a7 | |||
| a66ee06e3d | |||
| 9969de58cb | |||
| 6995cafe63 | |||
| eaa80ad08a | |||
| d001180082 | |||
| 3884403231 | |||
| 8d4b5d83e1 | |||
| e1250f2177 | |||
| a2ee920a5f | |||
| fdbe74a432 | |||
| 4e39fb3edd | |||
| c2049246cd | |||
| 335e2b7a78 | |||
| bb8e84e5ec | |||
| 2fa30be0d0 | |||
| c50149ad87 | |||
| da1bdc802d | |||
| a9cba30da6 | |||
| 3665dda87c | |||
| f2696d5352 | |||
| 5e394e1576 | |||
| ebd03e9290 | |||
| c3bfd7354f | |||
| 1d046eaadf | |||
| 2f554db54b | |||
| 54b4cef25e | |||
| c6106e6636 | |||
| 865dbab3ec | |||
| 76e6c63a01 | |||
| 3320accc7a | |||
| ac686db298 | |||
| aca26c7597 | |||
| b7737cb6d3 | |||
| 55026ca996 | |||
| 3fa573e3c1 | |||
| 00ae6809a9 | |||
| c3fbb11093 | |||
| a0d27c3f1c | |||
| 811a908cc3 | |||
| 94e5baf48a | |||
| cd2570cdbb | |||
| 6df6553935 | |||
| 8e6c372a4f | |||
| 371e11de9b | |||
| d2a708fcc4 | |||
| 845d772a08 | |||
| 1e630ede08 | |||
| c50d1ea2fe | |||
| e7eb00eaa3 | |||
| cd6eac3c8f | |||
| 69e21da60c | |||
| 57b905862a | |||
| 51d3a2b77b | |||
| b2920996be | |||
| bdc5a7a01b | |||
| 1c6a9e2c49 | |||
| 8ffb77551f | |||
| 2d35869c41 | |||
| 359dab573b | |||
| 6e2edbf101 | |||
| 4c8f93d737 | |||
| 2789bc565a | |||
| a1e0b8858e | |||
| f075a6178b | |||
| 5fa385e1e0 | |||
| b57141a80c | |||
| d453adb7cf | |||
| c5a7a8680e | |||
| d4ccadb832 | |||
| cbf04546b8 | |||
| 37c0084874 | |||
| 875afd69d5 | |||
| 67f4ed00d0 | |||
| 10d30b0de7 |
@@ -46,7 +46,7 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install xmlschema xsdata numpy lxml pytest isodate lark networkx tabulate python-dateutil
|
||||
pip install xmlschema xsdata numpy lxml pytest isodate lark networkx tabulate python-dateutil shapely
|
||||
pip install src/bcf --no-deps
|
||||
pip install https://github.com/Andrej730/aud/archive/refs/heads/master-reduced-size.zip
|
||||
|
||||
@@ -90,7 +90,7 @@ jobs:
|
||||
-DPYTHON_INCLUDE_DIR:PATH=${{ env.pythonLocation }}/include/python3.11 \
|
||||
-DPYTHON_LIBRARY:FILEPATH=${{ env.pythonLocation }}/lib/libpython3.11.so \
|
||||
-DCOLLADA_SUPPORT=Off \
|
||||
"-DSCHEMA_VERSIONS=2x3;4;4x3_add1" \
|
||||
"-DSCHEMA_VERSIONS=2x3;4;4x3;4x3_add1;4x3_add2" \
|
||||
-DGLTF_SUPPORT=On \
|
||||
-DJSON_INCLUDE_DIR=/usr/include \
|
||||
-DCGAL_INCLUDE_DIR=/usr/include \
|
||||
|
||||
@@ -36,7 +36,9 @@ if(NOT CMAKE_BUILD_TYPE)
|
||||
endif()
|
||||
|
||||
# use extra version to make pre-release using eg semver
|
||||
set(EXTRA_VERSION "-alpha.3")
|
||||
if(NOT DEFINED EXTRA_VERSION)
|
||||
set(EXTRA_VERSION "-alpha.3")
|
||||
endif()
|
||||
|
||||
option(MINIMAL_BUILD "The build is to make a minimal version of IFC converter from OCCT into IFC." OFF)
|
||||
option(WASM_BUILD "Build a WebAssembly binary." OFF)
|
||||
@@ -1147,9 +1149,10 @@ endif()
|
||||
|
||||
# Packaging
|
||||
list(APPEND CPACK_SOURCE_IGNORE_FILES
|
||||
.git
|
||||
.gitignore
|
||||
"/\\\\.git"
|
||||
"/build/"
|
||||
)
|
||||
set(CPACK_SOURCE_INSTALLED_DIRECTORIES "${CMAKE_SOURCE_DIR}/..;/")
|
||||
set(CPACK_PACKAGE_NAME "${PROJECT_NAME}-${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}${EXTRA_VERSION}")
|
||||
set(CPACK_SOURCE_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION}${EXTRA_VERSION}")
|
||||
SET(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}${EXTRA_VERSION}-${CMAKE_SYSTEM_NAME}")
|
||||
|
||||
@@ -13,7 +13,7 @@ readme = "README.md"
|
||||
requires-python = ">=3.8"
|
||||
keywords = ["IFC", "BCF", "BIM"]
|
||||
dependencies = [
|
||||
"xsdata",
|
||||
"xsdata>=24.4",
|
||||
"numpy",
|
||||
"ifcopenshell",
|
||||
]
|
||||
|
||||
@@ -10,14 +10,14 @@ from xsdata.formats.dataclass.serializers.config import SerializerConfig
|
||||
def build_xml_parser(context: Optional[XmlContext] = None) -> XmlParser:
|
||||
"""Return a parser for an XML file."""
|
||||
parser = XmlParser(context=context or XmlContext())
|
||||
parser.register_namespace("xs", "http://www.w3.org/2001/XMLSchema")
|
||||
parser.register_namespace(ns_map=parser.ns_map, prefix="xs", uri="http://www.w3.org/2001/XMLSchema")
|
||||
return parser
|
||||
|
||||
|
||||
def build_serializer(context: Optional[XmlContext] = None) -> XmlSerializer:
|
||||
"""Return a serializer for an XML file."""
|
||||
return XmlSerializer(
|
||||
config=SerializerConfig(pretty_print=True),
|
||||
config=SerializerConfig(indent=" "),
|
||||
context=context or XmlContext(),
|
||||
)
|
||||
|
||||
@@ -36,8 +36,9 @@ class AbstractXmlParserSerializer(Protocol):
|
||||
xml: The XML file as bytes.
|
||||
clazz: The class to parse to.
|
||||
"""
|
||||
...
|
||||
|
||||
def serialize(self, obj: T, ns_map: Optional[dict[str, str]] = None) -> str:
|
||||
def serialize(self, obj: object, ns_map: Optional[dict[str, str]] = None) -> str:
|
||||
"""
|
||||
Serialize an object to XML.
|
||||
|
||||
@@ -48,6 +49,7 @@ class AbstractXmlParserSerializer(Protocol):
|
||||
Returns:
|
||||
The XML as string.
|
||||
"""
|
||||
...
|
||||
|
||||
|
||||
class XmlParserSerializer:
|
||||
@@ -68,7 +70,7 @@ class XmlParserSerializer:
|
||||
"""
|
||||
return self.parser.from_bytes(xml, clazz)
|
||||
|
||||
def serialize(self, obj: T, ns_map: Optional[dict[str, str]] = None) -> str:
|
||||
def serialize(self, obj: object, ns_map: Optional[dict[Optional[str], str]] = None) -> str:
|
||||
"""
|
||||
Serialize an object to XML.
|
||||
|
||||
@@ -79,5 +81,5 @@ class XmlParserSerializer:
|
||||
Returns:
|
||||
The XML as string.
|
||||
"""
|
||||
ns_map = ns_map or {"xs": "http://www.w3.org/2001/XMLSchema"}
|
||||
ns_map = ns_map or self.parser.ns_map or {"xs": "http://www.w3.org/2001/XMLSchema"}
|
||||
return self.serializer.render(obj, ns_map)
|
||||
|
||||
+10
-31
@@ -151,8 +151,10 @@ endif
|
||||
cd dist/working && wget https://github.com/IfcOpenShell/IfcOpenShell/archive/v0.7.0.zip
|
||||
cd dist/working && unzip v0.7.0.zip
|
||||
# IfcOpenBot sometimes lags behind, so we hotfix the Python utilities
|
||||
cp -r dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/util/* dist/blenderbim/libs/site/packages/ifcopenshell/util/
|
||||
cp -r dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/api/* dist/blenderbim/libs/site/packages/ifcopenshell/api/
|
||||
rm -rf dist/blenderbim/libs/site/packages/ifcopenshell/util/
|
||||
rm -rf dist/blenderbim/libs/site/packages/ifcopenshell/api/
|
||||
cp -r dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/util dist/blenderbim/libs/site/packages/ifcopenshell/
|
||||
cp -r dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/api dist/blenderbim/libs/site/packages/ifcopenshell/
|
||||
cp dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/*.py dist/blenderbim/libs/site/packages/ifcopenshell/
|
||||
cp dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/express/rule_executor.py dist/blenderbim/libs/site/packages/ifcopenshell/express/
|
||||
# Provides bcf functionality
|
||||
@@ -275,34 +277,6 @@ endif
|
||||
cp -r dist/working/pyparsing-2.4.5/pyparsing.py dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by bcf
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/5b/ef/f97c3e1a7efa00e989a793fe15297214fc95ad7d9e3810586bd08ce9f0f3/xmlschema-2.0.2.tar.gz
|
||||
cd dist/working && tar -xzvf xmlschema*
|
||||
cp -r dist/working/xmlschema-2.0.2/xmlschema dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by bcf
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/ad/c7/17c9d16320d8e2cfdb27d2fd298b5e8f7a3f211025b0c1bc7a39a85bd690/xsdata-22.11.tar.gz
|
||||
cd dist/working && tar -xzvf xsdata*
|
||||
cp -r dist/working/xsdata-22.11/xsdata dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by bcf
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/11/bc/5afb61dd5d863e5cf77cd952445c50c17e65953405986f19e97e4389692a/elementpath-3.0.2.tar.gz
|
||||
cd dist/working && tar -xzvf elementpath*
|
||||
cp -r dist/working/elementpath-3.0.2/elementpath dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by bcf
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/21/9f/b251f7f8a76dec1d6651be194dfba8fb8d7781d10ab3987190de8391d08e/six-1.14.0.tar.gz
|
||||
cd dist/working && tar -xzvf six*
|
||||
cp -r dist/working/six-1.14.0/six.py dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by IFCCSV and ifcopenshell.util.selector
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/18/4d/8d522136c37d9e1ea74062b41b8d5e1318ebf45063ae46ce72ed60af223b/lark-parser-0.8.5.tar.gz
|
||||
@@ -364,7 +338,12 @@ endif
|
||||
# Provides Brickschema functionality
|
||||
cd dist/working && . env/bin/activate && $(PIP) install "brickschema[persistence]==0.7.6a2" --target=./site-packages
|
||||
# Required for SVG to DXF conversion
|
||||
cd dist/working && . env/bin/activate && $(PIP) install "ezdxf" --target=./site-packages
|
||||
cd dist/working && . env/bin/activate && $(PIP) install ezdxf --target=./site-packages
|
||||
# Required by bcf
|
||||
cd dist/working && . env/bin/activate && $(PIP) install xsdata --target=./site-packages
|
||||
cd dist/working && . env/bin/activate && $(PIP) install xmlschema --target=./site-packages
|
||||
cd dist/working && . env/bin/activate && $(PIP) install elementpath --target=./site-packages
|
||||
cd dist/working && . env/bin/activate && $(PIP) install six --target=./site-packages
|
||||
cp -r dist/working/site-packages/* dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
|
||||
@@ -18,7 +18,21 @@
|
||||
|
||||
import os
|
||||
import sys
|
||||
import site
|
||||
|
||||
# Ensure we don't try to import bpy or blenderbim.bim
|
||||
# to support running core tests.
|
||||
# We assume if bpy was never loaded in current python session
|
||||
# then we're not in Blender. It's still possible to use
|
||||
# bpy in core and core tests for annotations using TYPE_CHECKING.
|
||||
IN_BLENDER = sys.modules.get("bpy", None)
|
||||
if IN_BLENDER:
|
||||
import bpy
|
||||
import platform
|
||||
import traceback
|
||||
import subprocess
|
||||
import webbrowser
|
||||
import addon_utils
|
||||
from collections import deque
|
||||
|
||||
bl_info = {
|
||||
"name": "BlenderBIM",
|
||||
@@ -32,16 +46,163 @@ bl_info = {
|
||||
"category": "System",
|
||||
}
|
||||
|
||||
if sys.modules.get("bpy", None):
|
||||
last_error = None
|
||||
last_actions: deque = deque(maxlen=10)
|
||||
|
||||
|
||||
def get_debug_info():
|
||||
version = ".".join(
|
||||
[
|
||||
str(x)
|
||||
for x in [
|
||||
addon.bl_info.get("version", (-1, -1, -1))
|
||||
for addon in addon_utils.modules()
|
||||
if addon.bl_info["name"] == "BlenderBIM"
|
||||
][0]
|
||||
]
|
||||
)
|
||||
return {
|
||||
"os": platform.system(),
|
||||
"os_version": platform.version(),
|
||||
"python_version": platform.python_version(),
|
||||
"architecture": platform.architecture(),
|
||||
"machine": platform.machine(),
|
||||
"processor": platform.processor(),
|
||||
"blender_version": bpy.app.version_string,
|
||||
"blenderbim_version": version,
|
||||
"last_actions": last_actions,
|
||||
"last_error": last_error,
|
||||
}
|
||||
|
||||
|
||||
def format_debug_info(info: dict):
|
||||
last_actions = ""
|
||||
for action in info["last_actions"]:
|
||||
last_actions += f"\n# {action['type']}: {action['name']}"
|
||||
if settings := action.get("settings"):
|
||||
last_actions += f"\n>>> {settings}"
|
||||
info["last_actions"] = last_actions
|
||||
text = "\n".join(f"{k}: {v}" for k, v in info.items())
|
||||
return text.strip()
|
||||
|
||||
|
||||
if IN_BLENDER:
|
||||
# Process *.pth in /libs/site/packages to setup globally importable modules
|
||||
# This is 3 levels deep as required by the static RPATH of ../../ from dependencies taken from Anaconda
|
||||
# site.addsitedir(os.path.join(os.path.dirname(os.path.realpath(__file__)), "libs", "site", "packages"))
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)), "libs", "site", "packages"))
|
||||
|
||||
import blenderbim.bim
|
||||
try:
|
||||
import blenderbim.bim
|
||||
import ifcopenshell.api
|
||||
|
||||
def register():
|
||||
blenderbim.bim.register()
|
||||
def log_api(usecase_path, ifc_file, settings):
|
||||
last_actions.append(
|
||||
{
|
||||
"type": "ifcopenshell.api",
|
||||
"name": usecase_path,
|
||||
"settings": ifcopenshell.api.serialise_settings(settings),
|
||||
}
|
||||
)
|
||||
|
||||
def unregister():
|
||||
blenderbim.bim.unregister()
|
||||
ifcopenshell.api.add_pre_listener("*", "action_logger", log_api)
|
||||
|
||||
def register():
|
||||
blenderbim.bim.register()
|
||||
|
||||
def unregister():
|
||||
blenderbim.bim.unregister()
|
||||
|
||||
except:
|
||||
last_error = traceback.format_exc()
|
||||
|
||||
print(last_error)
|
||||
print(format_debug_info(get_debug_info()))
|
||||
print("\nFATAL ERROR: Unable to load the BlenderBIM Add-on")
|
||||
|
||||
class BIM_PT_fatal_error(bpy.types.Panel):
|
||||
bl_label = "BlenderBIM Fatal Error"
|
||||
bl_idname = "SCENE_PT_error_message"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
|
||||
def draw(self, context):
|
||||
info = get_debug_info()
|
||||
|
||||
layout = self.layout
|
||||
layout.alert = True
|
||||
layout.label(text="BlenderBIM could not load.", icon="ERROR")
|
||||
if info["os"] == "Windows":
|
||||
layout.operator("wm.console_toggle", text="View the console for full logs.", icon="CONSOLE")
|
||||
else:
|
||||
layout.label(text="View the console for full logs.", icon="CONSOLE")
|
||||
box = layout.box()
|
||||
py = ".".join(info["python_version"].split(".")[0:2])
|
||||
b3d = ".".join(info["blender_version"].split(".")[0:2])
|
||||
box.label(text=f"Blender {b3d} {info['os']} {info['machine']}", icon="BLENDER")
|
||||
box.label(text=f"Python {py} BBIM {info['blenderbim_version']}", icon="SCRIPTPLUGINS")
|
||||
layout.operator("bim.copy_debug_information", text="Copy Error Message To Clipboard")
|
||||
op = layout.operator("bim.open_uri", text="How Can I Fix This?")
|
||||
op.uri = "https://docs.blenderbim.org/users/troubleshooting.html#installation-issues"
|
||||
|
||||
layout.label(text="Try Reinstalling:", icon="IMPORT")
|
||||
op = layout.operator("bim.open_uri", text="Re-download Add-on")
|
||||
bbim_date = info["blenderbim_version"].split(".")[-1]
|
||||
py_tag = py.replace(".", "")
|
||||
if "Linux" in info["os"]:
|
||||
os = "linux"
|
||||
elif "Darwin" in info["os"]:
|
||||
if "arm64" in info["machine"]:
|
||||
os = "macosm1"
|
||||
else:
|
||||
os = "macos"
|
||||
else:
|
||||
os = "win"
|
||||
op.uri = f"https://github.com/IfcOpenShell/IfcOpenShell/releases/download/blenderbim-{bbim_date}/blenderbim-{bbim_date}-py{py_tag}-{os}.zip"
|
||||
|
||||
class OpenUri(bpy.types.Operator):
|
||||
bl_idname = "bim.open_uri"
|
||||
bl_label = "Open URI"
|
||||
uri: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
webbrowser.open(self.uri)
|
||||
return {"FINISHED"}
|
||||
|
||||
class CopyDebugInformation(bpy.types.Operator):
|
||||
bl_idname = "bim.copy_debug_information"
|
||||
bl_label = "Copy Debug Information"
|
||||
bl_description = "Copies debugging information to your clipboard for use in bugreports"
|
||||
|
||||
def execute(self, context):
|
||||
info = format_debug_info(get_debug_info())
|
||||
context.window_manager.clipboard = info
|
||||
return {"FINISHED"}
|
||||
|
||||
class HiddenPanel:
|
||||
@classmethod
|
||||
def false_poll(cls, context):
|
||||
return False
|
||||
|
||||
def register():
|
||||
# Only show our error panel and nothing else in the scene tab
|
||||
for item_name in dir(bpy.types):
|
||||
item = getattr(bpy.types, item_name)
|
||||
if not hasattr(item, "bl_rna") or not isinstance(item.bl_rna, bpy.types.Panel):
|
||||
continue
|
||||
if getattr(item, "bl_context", None) != "scene":
|
||||
continue
|
||||
|
||||
# Reregister scene panel with a new poll to hide it
|
||||
item.poll = HiddenPanel.false_poll
|
||||
bpy.utils.unregister_class(item)
|
||||
bpy.utils.register_class(item)
|
||||
bpy.utils.register_class(BIM_PT_fatal_error)
|
||||
bpy.utils.register_class(CopyDebugInformation)
|
||||
bpy.utils.register_class(OpenUri)
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(OpenUri)
|
||||
bpy.utils.unregister_class(CopyDebugInformation)
|
||||
bpy.utils.unregister_class(BIM_PT_fatal_error)
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
import bpy
|
||||
import bpy.utils.previews
|
||||
import blenderbim
|
||||
import importlib
|
||||
from pathlib import Path
|
||||
from . import handler, ui, prop, operator, helper
|
||||
from typing import Callable, Union
|
||||
|
||||
@@ -103,6 +103,7 @@ classes = [
|
||||
operator.BIM_OT_select_object,
|
||||
operator.BIM_OT_show_description,
|
||||
operator.ClippingPlaneCutWithCappings,
|
||||
operator.CloseError,
|
||||
operator.EditBlenderCollection,
|
||||
operator.FileAssociate,
|
||||
operator.FileUnassociate,
|
||||
@@ -153,9 +154,10 @@ classes = [
|
||||
ui.BIM_PT_tab_representations,
|
||||
ui.BIM_PT_tab_geometric_relationships,
|
||||
ui.BIM_PT_tab_parametric_geometry,
|
||||
ui.BIM_PT_tab_profiles,
|
||||
ui.BIM_PT_tab_object_materials,
|
||||
ui.BIM_PT_tab_materials,
|
||||
ui.BIM_PT_tab_styles,
|
||||
ui.BIM_PT_tab_profiles,
|
||||
# Drawings and documents
|
||||
ui.BIM_PT_tab_sheets,
|
||||
ui.BIM_PT_tab_drawings,
|
||||
@@ -168,6 +170,7 @@ classes = [
|
||||
ui.BIM_PT_tab_structural,
|
||||
# Construction scheduling
|
||||
ui.BIM_PT_tab_status,
|
||||
ui.BIM_PT_tab_qto,
|
||||
ui.BIM_PT_tab_resources,
|
||||
ui.BIM_PT_tab_cost,
|
||||
ui.BIM_PT_tab_sequence,
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
* Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
|
||||
*
|
||||
* This file is part of BlenderBIM Add-on.
|
||||
*
|
||||
* BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY, without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* You may copy this `schedule.css` template alongside your input schedule
|
||||
* document with the same filename. For example if you have a schedule called
|
||||
* `door_types.ods`, you can create a `door_types.css` in the same folder to
|
||||
* style that schedule.
|
||||
*/
|
||||
|
||||
/**
|
||||
* If you specify a font size in CSS, such as text { font-size: 5; }, all fonts
|
||||
* will be overriden to match that size.
|
||||
*
|
||||
* If your CSS, ODS, XLSX does not specify a font size, the variables below
|
||||
* will specify the default font size.
|
||||
*
|
||||
* If your ODS, XLSX does specify a font size, they will scale linearly based
|
||||
* on the variables below.
|
||||
*/
|
||||
|
||||
:root {
|
||||
--font-size-pt: 12;
|
||||
--font-size-px: 4.13;
|
||||
--font-width: 0.45;
|
||||
}
|
||||
|
||||
text, tspan { /* 2.5mm */ fill: black; stroke: none; font-family: 'OpenGost Type B TT', 'DejaVu Sans Condensed', 'Liberation Sans', 'Arial Narrow', 'Arial'; }
|
||||
@@ -5,7 +5,7 @@ FILE_NAME('EPset_Drawing.ifc','2020-01-01T00:00:00',(),(),'EPset_Drawing','EPset
|
||||
FILE_SCHEMA(('IFC4'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#1=IFCPROPERTYSETTEMPLATE('2JhNIvqZrFnAgxfhK0XVQX',$,'EPset_Drawing','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation',(#2,#3,#4,#5,#6,#7,#8,#9,#10,#11,#12,#13,#14,#15,#16,#17,#18,#19,#20,#21,#22,#23));
|
||||
#1=IFCPROPERTYSETTEMPLATE('2JhNIvqZrFnAgxfhK0XVQX',$,'EPset_Drawing','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation/DRAWING',(#2,#3,#4,#5,#6,#7,#8,#9,#10,#11,#12,#13,#14,#15,#16,#17,#18,#19,#20,#21,#22,#23));
|
||||
#2=IFCSIMPLEPROPERTYTEMPLATE('23JavTMk98ZxXhrUEnjAcf',$,'TargetView','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#3=IFCSIMPLEPROPERTYTEMPLATE('1yVWUt5H9DAOuu0OaMMLpe',$,'Scale','The scale of this drawing represented as a numerator and denominator, such as 1/100',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#4=IFCSIMPLEPROPERTYTEMPLATE('3gsuPBtU93b8f0gg1pjkq6',$,'HumanScale','The scale of this drawing in human readable format, such as 1:100',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
|
||||
@@ -5,20 +5,20 @@ FILE_NAME('Psets_BBIM_Annotation.ifc','2020-01-01T00:00:00',(),(),'Psets_BBIM_An
|
||||
FILE_SCHEMA(('IFC4'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#1=IFCPROPERTYSETTEMPLATE('3VuPUwdCD2Qx3XDDRs0R1N',$,'EPset_Annotation','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation,IfcTypeProduct',(#2,#3,#4));
|
||||
#1=IFCPROPERTYSETTEMPLATE('3VuPUwdCD2Qx3XDDRs0R1N',$,'EPset_Annotation','',.PSET_TYPEDRIVENOVERRIDE.,'IfcAnnotation,IfcTypeProduct',(#2,#3,#4));
|
||||
#2=IFCSIMPLEPROPERTYTEMPLATE('2P7JN79n96Q9pElZ83LKe4',$,'ZIndex','',.P_SINGLEVALUE.,'IfcInteger',$,$,$,$,$,.READWRITE.);
|
||||
#3=IFCSIMPLEPROPERTYTEMPLATE('1Wpx_r2xj1_9w5JpI0QRJy',$,'Symbol','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#4=IFCSIMPLEPROPERTYTEMPLATE('3q0oxMUKP47vZ4jnyG$dDb',$,'Classes','Classes separarated by spaces that end up in classes for this element in svg. Can be used to specify the text font size: small - 1.8mm; regular - 2.5mm; large - 3.5mm; header - 5mm; title - 7mm. By default regular size is used.',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#5=IFCPROPERTYSETTEMPLATE('0iKwujnQL9IevVQato8f7Z',$,'BBIM_Batting','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation,IfcTypeProduct',(#6,#7));
|
||||
#5=IFCPROPERTYSETTEMPLATE('0iKwujnQL9IevVQato8f7Z',$,'BBIM_Batting','',.PSET_TYPEDRIVENOVERRIDE.,'IfcAnnotation/BATTING,IfcTypeProduct',(#6,#7));
|
||||
#6=IFCSIMPLEPROPERTYTEMPLATE('0t2LEesGT1QRQtrIZUAR8L',$,'Thickness','Batting thickness',.P_SINGLEVALUE.,'IfcPositiveLengthMeasure',$,$,$,$,$,.READWRITE.);
|
||||
#7=IFCSIMPLEPROPERTYTEMPLATE('082PndS6v2kBOiJoSboMnh',$,'Reverse pattern direction','Reverse batting pattern (swap starting and ending points)',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#8=IFCPROPERTYSETTEMPLATE('1Dx2EiZnP67xotInXpwz90',$,'BBIM_Section','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation,IfcTypeProduct',(#9,#10,#11,#12,#13));
|
||||
#8=IFCPROPERTYSETTEMPLATE('1Dx2EiZnP67xotInXpwz90',$,'BBIM_Section','',.PSET_TYPEDRIVENOVERRIDE.,'IfcAnnotation/SECTION,IfcTypeProduct',(#9,#10,#11,#12,#13));
|
||||
#9=IFCSIMPLEPROPERTYTEMPLATE('2a_9s8spHDc9dZHtgg71XL',$,'ShowStartArrow','Display start arrow.',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#10=IFCSIMPLEPROPERTYTEMPLATE('3i6SH_GbT7zhKea$wVE56A',$,'StartArrowSymbol','Custom symbol for the start of the section marker arrow. Need to make sure it''s present in "symbols.svg".',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#11=IFCSIMPLEPROPERTYTEMPLATE('1DtsPn5a9FG8$zXHDDMavY',$,'ShowEndArrow','Display end arrow.',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#12=IFCSIMPLEPROPERTYTEMPLATE('2$6U0mLI9AiPRWeBabdY3u',$,'EndArrowSymbol','Custom symbol for the end of the section marker arrow. Need to make sure it''s present in "symbols.svg".',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#13=IFCSIMPLEPROPERTYTEMPLATE('1naFqntIL7igCY7hCaE7kq',$,'HasConnectedSectionLine','Connect or disconnect section markers with line (by default = True).',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#14=IFCPROPERTYSETTEMPLATE('3V8oZ8YRD3_O7uR5vcUleS',$,'BBIM_Documentation','',.PSET_OCCURRENCEDRIVEN.,'IfcProject',(#15,#16,#17,#18,#19,#20,#21,#22,#23));
|
||||
#14=IFCPROPERTYSETTEMPLATE('3V8oZ8YRD3_O7uR5vcUleS',$,'BBIM_Documentation','',.PSET_TYPEDRIVENOVERRIDE.,'IfcProject',(#15,#16,#17,#18,#19,#20,#21,#22,#23));
|
||||
#15=IFCSIMPLEPROPERTYTEMPLATE('0ulAhgk3v9qfGlDILauR6J',$,'SheetsDir','Default sheets directory',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#16=IFCSIMPLEPROPERTYTEMPLATE('2yvlVKiQXASucfH40deCvu',$,'LayoutsDir','Default layouts directory',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#17=IFCSIMPLEPROPERTYTEMPLATE('2kXZqXicL3jRwsOnLM_0ho',$,'TitleblocksDir','Default titleblocks directory',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
@@ -28,7 +28,7 @@ DATA;
|
||||
#21=IFCSIMPLEPROPERTYTEMPLATE('1UDakJ5_f7kBhggNSW4$h5',$,'SymbolsPath','Default symbols SVG',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#22=IFCSIMPLEPROPERTYTEMPLATE('0d53LEtgLDQxnv__NfgH7i',$,'PatternsPath','Default patterns SVG',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#23=IFCSIMPLEPROPERTYTEMPLATE('26qFNMv7nCHgU6Jd7Anga5',$,'ShadingStylesPath','Default shading styles',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#24=IFCPROPERTYSETTEMPLATE('0I9merLinF5Ap$aZwaclgm',$,'BBIM_Dimension','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation,IfcTypeProduct',(#25,#26,#27,#28));
|
||||
#24=IFCPROPERTYSETTEMPLATE('0I9merLinF5Ap$aZwaclgm',$,'BBIM_Dimension','',.PSET_TYPEDRIVENOVERRIDE.,'IfcAnnotation/DIMENSION,IfcTypeProduct',(#25,#26,#27,#28));
|
||||
#25=IFCSIMPLEPROPERTYTEMPLATE('1rL2AbQsXD8RbpoWH5pYOV',$,'ShowDescriptionOnly','Hide the measurement values and show only annotation description',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#26=IFCSIMPLEPROPERTYTEMPLATE('0SVyOfB0rC2xNfdRYf3XvY',$,'SuppressZeroInches','Suppress 0 inch values in dimension annotation text (for example: 12'' - 0" -> 12'')',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#27=IFCSIMPLEPROPERTYTEMPLATE('2bUmj458PBqPAtUoI3MXsb',$,'TextPrefix','Text to add before annotation measurement value',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
|
||||
@@ -301,7 +301,8 @@ def load_post(scene):
|
||||
|
||||
if bpy.context.preferences.addons["blenderbim"].preferences.should_setup_workspace:
|
||||
if "BIM" in bpy.data.workspaces:
|
||||
bpy.context.window.workspace = bpy.data.workspaces["BIM"]
|
||||
if bpy.context.preferences.addons["blenderbim"].preferences.activate_workspace:
|
||||
bpy.context.window.workspace = bpy.data.workspaces["BIM"]
|
||||
else:
|
||||
bpy.ops.workspace.append_activate(idname="BIM", filepath=os.path.join(cwd, "data", "workspace.blend"))
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ from mathutils import geometry
|
||||
from mathutils import Vector
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from typing import Optional, Callable, Any
|
||||
from typing import Optional, Callable, Any, Union
|
||||
|
||||
|
||||
def draw_attributes(props, layout, copy_operator=None, popup_active_attribute=None):
|
||||
@@ -91,7 +91,11 @@ def import_attributes(ifc_class, props, data, callback=None):
|
||||
|
||||
|
||||
# A more elegant attribute importer signature, intended to supersede import_attributes
|
||||
def import_attributes2(element, props, callback=None):
|
||||
def import_attributes2(
|
||||
element: Union[str, ifcopenshell.entity_instance],
|
||||
props: bpy.types.PropertyGroup,
|
||||
callback: Optional[Callable] = None,
|
||||
) -> None:
|
||||
if isinstance(element, str):
|
||||
attributes = tool.Ifc.schema().declaration_by_name(element).as_entity().all_attributes()
|
||||
info = {a.name(): None for a in attributes}
|
||||
|
||||
@@ -19,12 +19,15 @@
|
||||
import os
|
||||
import bpy
|
||||
import uuid
|
||||
import shutil
|
||||
import hashlib
|
||||
import zipfile
|
||||
import tempfile
|
||||
import traceback
|
||||
import ifcopenshell
|
||||
import ifcopenshell.geom
|
||||
import ifcopenshell.ifcopenshell_wrapper
|
||||
import blenderbim
|
||||
import blenderbim.bim.handler
|
||||
import blenderbim.tool as tool
|
||||
from pathlib import Path
|
||||
@@ -37,19 +40,19 @@ IFC_CONNECTED_TYPE = Union[bpy.types.Material, bpy.types.Object]
|
||||
|
||||
class IfcStore:
|
||||
path: str = ""
|
||||
file: ifcopenshell.file = None
|
||||
schema: ifcopenshell.ifcopenshell_wrapper.schema_definition = None
|
||||
cache: ifcopenshell.ifcopenshell_wrapper.HdfSerializer = None
|
||||
cache_path: str = None
|
||||
file: Optional[ifcopenshell.file] = None
|
||||
schema: Optional[ifcopenshell.ifcopenshell_wrapper.schema_definition] = None
|
||||
cache: Optional[ifcopenshell.ifcopenshell_wrapper.HdfSerializer] = None
|
||||
cache_path: Optional[str] = None
|
||||
id_map: dict[int, IFC_CONNECTED_TYPE] = {}
|
||||
guid_map: dict[str, IFC_CONNECTED_TYPE] = {}
|
||||
edited_objs: Set[bpy.types.Object] = set()
|
||||
pset_template_path: str = ""
|
||||
pset_template_file: ifcopenshell.file = None
|
||||
pset_template_file: Optional[ifcopenshell.file] = None
|
||||
classification_path: str = ""
|
||||
classification_file: ifcopenshell.file = None
|
||||
classification_file: Optional[ifcopenshell.file] = None
|
||||
library_path: str = ""
|
||||
library_file: ifcopenshell.file = None
|
||||
library_file: Optional[ifcopenshell.file] = None
|
||||
current_transaction = ""
|
||||
last_transaction = ""
|
||||
history = []
|
||||
@@ -119,7 +122,13 @@ class IfcStore:
|
||||
ifc_hash = hashlib.md5(ifc_key.encode("utf-8")).hexdigest()
|
||||
new_cache_path = os.path.join(bpy.context.scene.BIMProperties.data_dir, "cache", f"{ifc_hash}.h5")
|
||||
IfcStore.cache = None
|
||||
os.replace(IfcStore.cache_path, new_cache_path)
|
||||
try:
|
||||
shutil.move(IfcStore.cache_path, new_cache_path)
|
||||
except PermissionError:
|
||||
try:
|
||||
shutil.copy2(IfcStore.cache_path, new_cache_path)
|
||||
except PermissionError:
|
||||
pass # Well we tried. No cache for you!
|
||||
IfcStore.get_cache()
|
||||
|
||||
@staticmethod
|
||||
@@ -329,6 +338,7 @@ class IfcStore:
|
||||
|
||||
@staticmethod
|
||||
def execute_ifc_operator(operator: bpy.types.Operator, context: bpy.types.Context, is_invoke=False):
|
||||
blenderbim.last_actions.append({"type": "operator", "name": operator.bl_idname})
|
||||
bpy.context.scene.BIMProperties.is_dirty = True
|
||||
is_top_level_operator = not bool(IfcStore.current_transaction)
|
||||
|
||||
@@ -343,10 +353,14 @@ class IfcStore:
|
||||
else:
|
||||
operator.transaction_key = IfcStore.current_transaction
|
||||
|
||||
if is_invoke:
|
||||
result = getattr(operator, "_invoke")(context, None)
|
||||
else:
|
||||
result = getattr(operator, "_execute")(context)
|
||||
try:
|
||||
if is_invoke:
|
||||
result = getattr(operator, "_invoke")(context, None)
|
||||
else:
|
||||
result = getattr(operator, "_execute")(context)
|
||||
except:
|
||||
blenderbim.last_error = traceback.format_exc()
|
||||
raise
|
||||
|
||||
if is_top_level_operator:
|
||||
if tool.Ifc.get():
|
||||
|
||||
@@ -26,6 +26,7 @@ import bmesh
|
||||
import logging
|
||||
import mathutils
|
||||
import numpy as np
|
||||
import numpy.typing as npt
|
||||
import multiprocessing
|
||||
import ifcopenshell
|
||||
import ifcopenshell.geom
|
||||
@@ -303,22 +304,22 @@ class IfcImporter:
|
||||
self.update_progress(100)
|
||||
bpy.context.window_manager.progress_end()
|
||||
|
||||
def is_element_far_away(self, element):
|
||||
def is_element_far_away(self, element: ifcopenshell.entity_instance) -> bool:
|
||||
try:
|
||||
placement = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
|
||||
point = placement[:, 3][0:3]
|
||||
return self.is_point_far_away(point, is_meters=False)
|
||||
except:
|
||||
pass
|
||||
return False
|
||||
|
||||
def is_point_far_away(self, point, is_meters=True):
|
||||
def is_point_far_away(
|
||||
self, point: Union[ifcopenshell.entity_instance, npt.NDArray[np.float64]], is_meters: bool = True
|
||||
) -> bool:
|
||||
# Locations greater than 1km are not considered "small sites" according to the georeferencing guide
|
||||
# Users can configure this if they have to handle larger sites but beware of surveying precision
|
||||
limit = self.ifc_import_settings.distance_limit
|
||||
limit = limit if is_meters else (limit / self.unit_scale)
|
||||
coords = point
|
||||
if hasattr(point, "Coordinates"):
|
||||
coords = point.Coordinates
|
||||
coords = getattr(point, "Coordinates", point)
|
||||
return abs(coords[0]) > limit or abs(coords[1]) > limit or abs(coords[2]) > limit
|
||||
|
||||
def process_context_filter(self):
|
||||
@@ -608,7 +609,9 @@ class IfcImporter:
|
||||
|
||||
threshold = 10000 # Just from experience.
|
||||
|
||||
faces = [len(e.CfsFaces) for e in self.file.by_type("IfcClosedShell")]
|
||||
# The check for CfsFaces/Faces/CoordIndex accommodates invalid data from Cadwork
|
||||
# 0 IfcClosedShell.CfsFaces
|
||||
faces = [len(faces) for e in self.file.by_type("IfcClosedShell") if (faces := e[0])]
|
||||
if faces and max(faces) > threshold:
|
||||
self.ifc_import_settings.should_use_native_meshes = True
|
||||
return
|
||||
@@ -616,12 +619,14 @@ class IfcImporter:
|
||||
if self.file.schema == "IFC2X3":
|
||||
return
|
||||
|
||||
faces = [len(e.Faces) for e in self.file.by_type("IfcPolygonalFaceSet")]
|
||||
# 2 IfcPolygonalFaceSet.Faces
|
||||
faces = [len(faces) for e in self.file.by_type("IfcPolygonalFaceSet") if (faces := e[2])]
|
||||
if faces and max(faces) > threshold:
|
||||
self.ifc_import_settings.should_use_native_meshes = True
|
||||
return
|
||||
|
||||
faces = [len(e.CoordIndex) for e in self.file.by_type("IfcTriangulatedFaceSet")]
|
||||
# 3 IfcTriangulatedFaceSet.CoordIndex
|
||||
faces = [len(index) for e in self.file.by_type("IfcTriangulatedFaceSet") if (index := e[3])]
|
||||
if faces and max(faces) > threshold:
|
||||
self.ifc_import_settings.should_use_native_meshes = True
|
||||
|
||||
@@ -679,7 +684,7 @@ class IfcImporter:
|
||||
props.blender_orthogonal_height = str(offset_point[2])
|
||||
props.has_blender_offset = True
|
||||
|
||||
def get_offset_point(self):
|
||||
def get_offset_point(self) -> Union[npt.NDArray[np.float64], None]:
|
||||
elements_checked = 0
|
||||
# If more than these elements aren't far away, the file probably isn't absolutely positioned
|
||||
element_checking_threshold = 10
|
||||
@@ -702,17 +707,19 @@ class IfcImporter:
|
||||
mat = np.array(
|
||||
([m[0], m[3], m[6], m[9]], [m[1], m[4], m[7], m[10]], [m[2], m[5], m[8], m[11]], [0, 0, 0, 1])
|
||||
)
|
||||
point = np.array(
|
||||
point = mat @ np.array(
|
||||
(
|
||||
shape.geometry.verts[0] / self.unit_scale,
|
||||
shape.geometry.verts[1] / self.unit_scale,
|
||||
shape.geometry.verts[2] / self.unit_scale,
|
||||
shape.geometry.verts[0],
|
||||
shape.geometry.verts[1],
|
||||
shape.geometry.verts[2],
|
||||
0.0,
|
||||
)
|
||||
)
|
||||
return mat @ point
|
||||
point = point / self.unit_scale
|
||||
if self.is_point_far_away(point, is_meters=False):
|
||||
return point
|
||||
|
||||
def does_element_likely_have_geometry_far_away(self, element):
|
||||
def does_element_likely_have_geometry_far_away(self, element: ifcopenshell.entity_instance) -> bool:
|
||||
for representation in element.Representation.Representations:
|
||||
items = []
|
||||
for item in representation.Items:
|
||||
@@ -729,13 +736,14 @@ class IfcImporter:
|
||||
if subelement.is_a("IfcCartesianPoint"):
|
||||
if len(subelement.Coordinates) == 3 and self.is_point_far_away(subelement, is_meters=False):
|
||||
return True
|
||||
return False
|
||||
|
||||
def apply_blender_offset_to_matrix_world(self, obj: bpy.types.Object, matrix: np.ndarray) -> mathutils.Matrix:
|
||||
props = bpy.context.scene.BIMGeoreferenceProperties
|
||||
if props.has_blender_offset:
|
||||
if obj.data and obj.data.get("has_cartesian_point_offset", None):
|
||||
obj.BIMObjectProperties.blender_offset_type = "CARTESIAN_POINT"
|
||||
elif self.is_point_far_away((matrix[0, 3], matrix[1, 3], matrix[2, 3])):
|
||||
elif self.is_point_far_away((matrix[:3, 3])):
|
||||
obj.BIMObjectProperties.blender_offset_type = "OBJECT_PLACEMENT"
|
||||
matrix = ifcopenshell.util.geolocation.global2local(
|
||||
matrix,
|
||||
@@ -748,7 +756,9 @@ class IfcImporter:
|
||||
|
||||
return mathutils.Matrix(matrix.tolist())
|
||||
|
||||
def find_decomposed_ifc_class(self, element, ifc_class):
|
||||
def find_decomposed_ifc_class(
|
||||
self, element: ifcopenshell.entity_instance, ifc_class: str
|
||||
) -> Union[ifcopenshell.entity_instance, None]:
|
||||
if element.is_a(ifc_class):
|
||||
return element
|
||||
rel_aggregates = element.IsDecomposedBy
|
||||
@@ -1500,10 +1510,9 @@ class IfcImporter:
|
||||
project_collection = bpy.context.view_layer.layer_collection.children[self.project["blender"].name]
|
||||
types_collection = project_collection.children[self.type_collection.name]
|
||||
types_collection.hide_viewport = False
|
||||
for obj in types_collection.collection.objects: #turn off all objects inside Types collection.
|
||||
for obj in types_collection.collection.objects: # turn off all objects inside Types collection.
|
||||
obj.hide_set(True)
|
||||
|
||||
|
||||
def clean_mesh(self):
|
||||
obj = None
|
||||
last_obj = None
|
||||
@@ -1912,11 +1921,7 @@ class IfcImporter:
|
||||
and geometry.verts
|
||||
and self.is_point_far_away((geometry.verts[0], geometry.verts[1], geometry.verts[2]))
|
||||
):
|
||||
m = shape.transformation.matrix.data
|
||||
mat = np.array(
|
||||
([m[0], m[3], m[6], m[9]], [m[1], m[4], m[7], m[10]], [m[2], m[5], m[8], m[11]], [0, 0, 0, 1])
|
||||
)
|
||||
offset_point = np.linalg.inv(mat) @ np.array(
|
||||
offset_point = np.array(
|
||||
(
|
||||
float(props.blender_eastings),
|
||||
float(props.blender_northings),
|
||||
@@ -1924,8 +1929,15 @@ class IfcImporter:
|
||||
0.0,
|
||||
)
|
||||
)
|
||||
if geometry != shape:
|
||||
m = shape.transformation.matrix.data
|
||||
mat = np.array(
|
||||
([m[0], m[3], m[6], m[9]], [m[1], m[4], m[7], m[10]], [m[2], m[5], m[8], m[11]], [0, 0, 0, 1])
|
||||
)
|
||||
offset_point = np.linalg.inv(mat) @ offset_point
|
||||
verts = [None] * len(geometry.verts)
|
||||
for i in range(0, len(geometry.verts), 3):
|
||||
# Note: this enh2xyz call is crazy slow.
|
||||
verts[i], verts[i + 1], verts[i + 2] = ifcopenshell.util.geolocation.enh2xyz(
|
||||
geometry.verts[i],
|
||||
geometry.verts[i + 1],
|
||||
|
||||
@@ -100,7 +100,7 @@ class BIM_OT_aggregate_unassign_object(bpy.types.Operator, Operator):
|
||||
pset = ifcopenshell.util.element.get_pset(element, 'BBIM_Linked_Aggregate')
|
||||
if pset:
|
||||
pset = tool.Ifc.get().by_id(pset["id"])
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=element, pset=pset)
|
||||
|
||||
|
||||
class BIM_OT_enable_editing_aggregate(bpy.types.Operator, Operator):
|
||||
|
||||
@@ -73,7 +73,7 @@ class BIM_PT_aggregate(Panel):
|
||||
row.operator("bim.add_aggregate", icon="ADD", text="")
|
||||
op = row.operator("bim.aggregate_unassign_object", icon="X", text="")
|
||||
else:
|
||||
row.label(text="No Aggregate", icon="TRIA_UP")
|
||||
row.label(text="No Whole relation defined", icon="TRIA_UP")
|
||||
row.operator("bim.enable_editing_aggregate", icon="GREASEPENCIL", text="")
|
||||
row.operator("bim.add_aggregate", icon="ADD", text="")
|
||||
|
||||
|
||||
@@ -27,15 +27,12 @@ classes = (
|
||||
operator.CopyAttributeToSelection,
|
||||
prop.BIMAttributeProperties,
|
||||
ui.BIM_PT_object_attributes,
|
||||
ui.BIM_PT_material_attributes,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
bpy.types.Object.BIMAttributeProperties = bpy.props.PointerProperty(type=prop.BIMAttributeProperties)
|
||||
bpy.types.Material.BIMAttributeProperties = bpy.props.PointerProperty(type=prop.BIMAttributeProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
del bpy.types.Object.BIMAttributeProperties
|
||||
del bpy.types.Material.BIMAttributeProperties
|
||||
|
||||
@@ -23,7 +23,6 @@ import blenderbim.tool as tool
|
||||
|
||||
def refresh():
|
||||
AttributesData.is_loaded = False
|
||||
MaterialAttributesData.is_loaded = False
|
||||
|
||||
|
||||
class AttributesData:
|
||||
@@ -51,28 +50,3 @@ class AttributesData:
|
||||
key = "STEP ID"
|
||||
results.append({"name": key, "value": str(value)})
|
||||
return results
|
||||
|
||||
|
||||
class MaterialAttributesData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {"ifc_definition_id": cls.ifc_definition_id(), "attributes": cls.attributes()}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def ifc_definition_id(cls):
|
||||
return bpy.context.active_object.active_material.BIMObjectProperties.ifc_definition_id
|
||||
|
||||
@classmethod
|
||||
def attributes(cls):
|
||||
results = []
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object.active_material)
|
||||
data = element.get_info()
|
||||
for key, value in data.items():
|
||||
if value is None or isinstance(value, ifcopenshell.entity_instance) or key in ["id", "type"]:
|
||||
continue
|
||||
results.append({"name": key, "value": str(value)})
|
||||
return results
|
||||
|
||||
@@ -20,6 +20,7 @@ import bpy
|
||||
import json
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.guid
|
||||
import blenderbim.bim.helper
|
||||
import blenderbim.bim.handler
|
||||
import blenderbim.tool as tool
|
||||
@@ -39,14 +40,10 @@ class EnableEditingAttributes(bpy.types.Operator):
|
||||
bl_label = "Enable Editing Attributes"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
obj_type: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
if self.obj_type == "Object":
|
||||
obj = bpy.data.objects.get(self.obj)
|
||||
elif self.obj_type == "Material":
|
||||
obj = bpy.data.materials.get(self.obj)
|
||||
obj = bpy.data.objects.get(self.obj)
|
||||
props = obj.BIMAttributeProperties
|
||||
props.attributes.clear()
|
||||
|
||||
@@ -84,13 +81,9 @@ class DisableEditingAttributes(bpy.types.Operator):
|
||||
bl_label = "Disable Editing Attributes"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
obj_type: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
if self.obj_type == "Object":
|
||||
obj = bpy.data.objects.get(self.obj)
|
||||
elif self.obj_type == "Material":
|
||||
obj = bpy.data.materials.get(self.obj)
|
||||
obj = bpy.data.objects.get(self.obj)
|
||||
props = obj.BIMAttributeProperties
|
||||
props.is_editing_attributes = False
|
||||
return {"FINISHED"}
|
||||
@@ -101,14 +94,10 @@ class EditAttributes(bpy.types.Operator, Operator):
|
||||
bl_label = "Edit Attributes"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
obj_type: bpy.props.StringProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
if self.obj_type == "Object":
|
||||
obj = bpy.data.objects.get(self.obj)
|
||||
elif self.obj_type == "Material":
|
||||
obj = bpy.data.materials.get(self.obj)
|
||||
obj = bpy.data.objects.get(self.obj)
|
||||
props = obj.BIMAttributeProperties
|
||||
product = tool.Ifc.get_entity(obj)
|
||||
|
||||
@@ -125,7 +114,7 @@ class EditAttributes(bpy.types.Operator, Operator):
|
||||
|
||||
attributes = blenderbim.bim.helper.export_attributes(props.attributes, callback=callback)
|
||||
ifcopenshell.api.run("attribute.edit_attributes", self.file, product=product, attributes=attributes)
|
||||
bpy.ops.bim.disable_editing_attributes(obj=obj.name, obj_type=self.obj_type)
|
||||
bpy.ops.bim.disable_editing_attributes(obj=obj.name)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
|
||||
@@ -19,34 +19,34 @@
|
||||
import blenderbim.bim.helper
|
||||
from bpy.types import Panel
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.attribute.data import AttributesData, MaterialAttributesData
|
||||
from blenderbim.bim.module.attribute.data import AttributesData
|
||||
|
||||
|
||||
def draw_ui(context, layout, obj_type, attributes):
|
||||
obj = context.active_object if obj_type == "Object" else context.active_object.active_material
|
||||
def draw_ui(context, layout, attributes):
|
||||
obj = context.active_object
|
||||
oprops = obj.BIMObjectProperties
|
||||
props = obj.BIMAttributeProperties
|
||||
|
||||
if props.is_editing_attributes:
|
||||
row = layout.row(align=True)
|
||||
op = row.operator("bim.edit_attributes", icon="CHECKMARK", text="Save Attributes")
|
||||
op.obj_type = obj_type
|
||||
op.obj = obj.name
|
||||
op = row.operator("bim.disable_editing_attributes", icon="CANCEL", text="")
|
||||
op.obj_type = obj_type
|
||||
op.obj = obj.name
|
||||
|
||||
blenderbim.bim.helper.draw_attributes(props.attributes, layout, copy_operator="bim.copy_attribute_to_selection")
|
||||
else:
|
||||
row = layout.row()
|
||||
op = row.operator("bim.enable_editing_attributes", icon="GREASEPENCIL", text="Edit")
|
||||
op.obj_type = obj_type
|
||||
op.obj = obj.name
|
||||
|
||||
for attribute in attributes:
|
||||
row = layout.row(align=True)
|
||||
row.label(text=attribute["name"])
|
||||
row.label(text=attribute["value"])
|
||||
# row.label(text=attribute["value"])
|
||||
op = row.operator("bim.select_similar", text=attribute["value"], icon="NONE", emboss=False)
|
||||
op.key = attribute['name']
|
||||
|
||||
|
||||
# TODO: reimplement, see #1222
|
||||
# if "IfcSite/" in context.active_object.name or "IfcBuilding/" in context.active_object.name:
|
||||
@@ -72,32 +72,4 @@ class BIM_PT_object_attributes(Panel):
|
||||
def draw(self, context):
|
||||
if not AttributesData.is_loaded:
|
||||
AttributesData.load()
|
||||
draw_ui(context, self.layout, "Object", AttributesData.data["attributes"])
|
||||
|
||||
|
||||
class BIM_PT_material_attributes(Panel):
|
||||
bl_label = "Material Attributes"
|
||||
bl_idname = "BIM_PT_material_attributes"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "material"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if not IfcStore.get_file():
|
||||
return False
|
||||
try:
|
||||
return bool(context.active_object.active_material.BIMObjectProperties.ifc_definition_id)
|
||||
except:
|
||||
return False
|
||||
|
||||
def draw(self, context):
|
||||
if not MaterialAttributesData.is_loaded:
|
||||
MaterialAttributesData.load()
|
||||
elif (
|
||||
context.active_object.active_material.BIMObjectProperties.ifc_definition_id
|
||||
!= MaterialAttributesData.data["ifc_definition_id"]
|
||||
):
|
||||
MaterialAttributesData.load()
|
||||
|
||||
draw_ui(context, self.layout, "Material", MaterialAttributesData.data["attributes"])
|
||||
draw_ui(context, self.layout, AttributesData.data["attributes"])
|
||||
|
||||
@@ -938,7 +938,7 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
# Operators with context overrides are used because they are
|
||||
# significantly faster than looping through all objects
|
||||
|
||||
exception_global_ids = {v.ifc_guid for v in viewpoint.visualization_info.components.visibility.exceptions or []}
|
||||
exception_global_ids = {v.ifc_guid for v in viewpoint.visualization_info.components.visibility.exceptions.component or []}
|
||||
|
||||
if viewpoint.visualization_info.components.visibility.default_visibility:
|
||||
old = context.area.type
|
||||
@@ -958,11 +958,13 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
if objs:
|
||||
old = context.area.type
|
||||
context.area.type = "VIEW_3D"
|
||||
bpy.ops.object.hide_view_clear()
|
||||
context_override = {}
|
||||
context_override["object"] = context_override["active_object"] = objs[0]
|
||||
context_override["selected_objects"] = context_override["selected_editable_objects"] = objs
|
||||
with context.temp_override(**context_override):
|
||||
bpy.ops.object.hide_view_set(unselected=True)
|
||||
bpy.data.objects["Viewpoint"].hide_set(False)
|
||||
context.area.type = old
|
||||
|
||||
if viewpoint.visualization_info.components.view_setup_hints:
|
||||
@@ -1000,6 +1002,7 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
obj = IfcStore.get_element(global_id)
|
||||
if obj:
|
||||
obj.select_set(True)
|
||||
obj.hide_set(False)
|
||||
|
||||
def set_colours(self, viewpoint):
|
||||
global_id_colours = {}
|
||||
|
||||
@@ -54,11 +54,11 @@ classes = (
|
||||
|
||||
def register():
|
||||
bpy.types.Scene.BIMClassificationProperties = bpy.props.PointerProperty(type=prop.BIMClassificationProperties)
|
||||
bpy.types.Object.BIMClassificationReferenceProperties = bpy.props.PointerProperty(
|
||||
bpy.types.Scene.BIMClassificationReferenceProperties = bpy.props.PointerProperty(
|
||||
type=prop.BIMClassificationReferenceProperties
|
||||
)
|
||||
|
||||
|
||||
def unregister():
|
||||
del bpy.types.Scene.BIMClassificationProperties
|
||||
del bpy.types.Object.BIMClassificationReferenceProperties
|
||||
del bpy.types.Scene.BIMClassificationReferenceProperties
|
||||
|
||||
@@ -88,7 +88,7 @@ class ClassificationReferencesData(ReferencesData):
|
||||
cls.data["references"] = cls.references()
|
||||
cls.data["active_classification_library"] = cls.active_classification_library()
|
||||
cls.data["classifications"] = cls.classifications()
|
||||
cls.data["object_type"] = "OBJECT"
|
||||
cls.data["object_type"] = "Object"
|
||||
|
||||
@classmethod
|
||||
def references(cls):
|
||||
@@ -112,17 +112,21 @@ class MaterialClassificationsData(ReferencesData):
|
||||
cls.data["references"] = cls.references()
|
||||
cls.data["active_classification_library"] = cls.active_classification_library()
|
||||
cls.data["classifications"] = cls.classifications()
|
||||
cls.data["object_type"] = "MATERIAL"
|
||||
cls.data["object_type"] = "Material"
|
||||
|
||||
@classmethod
|
||||
def references(cls):
|
||||
results = []
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object.active_material)
|
||||
if element:
|
||||
for reference in ifcopenshell.util.classification.get_references(element):
|
||||
data = reference.get_info()
|
||||
del data["ReferencedSource"]
|
||||
results.append(data)
|
||||
|
||||
props = bpy.context.scene.BIMMaterialProperties
|
||||
if props.materials and props.active_material_index < len(props.materials):
|
||||
material = props.materials[props.active_material_index]
|
||||
if material.ifc_definition_id:
|
||||
element = tool.Ifc.get().by_id(material.ifc_definition_id)
|
||||
for reference in ifcopenshell.util.classification.get_references(element):
|
||||
data = reference.get_info()
|
||||
del data["ReferencedSource"]
|
||||
results.append(data)
|
||||
return results
|
||||
|
||||
|
||||
@@ -136,7 +140,7 @@ class CostClassificationsData(ReferencesData):
|
||||
cls.data["references"] = cls.references()
|
||||
cls.data["active_classification_library"] = cls.active_classification_library()
|
||||
cls.data["classifications"] = cls.classifications()
|
||||
cls.data["object_type"] = "COST"
|
||||
cls.data["object_type"] = "Cost"
|
||||
|
||||
@classmethod
|
||||
def references(cls):
|
||||
|
||||
@@ -77,26 +77,37 @@ class AddManualClassificationReference(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.add_manual_classification_reference"
|
||||
bl_label = "Add Manual Classification Reference"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
obj_type: bpy.props.StringProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
props = obj.BIMClassificationReferenceProperties
|
||||
if self.obj_type == "Object":
|
||||
if context.selected_objects:
|
||||
objects = [o.name for o in context.selected_objects]
|
||||
else:
|
||||
objects = [context.active_object.name]
|
||||
else:
|
||||
objects = [self.obj]
|
||||
props = context.scene.BIMClassificationReferenceProperties
|
||||
attributes = blenderbim.bim.helper.export_attributes(props.reference_attributes)
|
||||
product = tool.Ifc.get_entity(obj)
|
||||
# TODO: refactor and support material and cost item classifications
|
||||
classification = tool.Ifc.get().by_id(int(props.classifications))
|
||||
reference = ifcopenshell.api.run(
|
||||
"classification.add_reference",
|
||||
tool.Ifc.get(),
|
||||
products=[product],
|
||||
classification=classification,
|
||||
identification="X",
|
||||
name="Unnamed",
|
||||
)
|
||||
ifcopenshell.api.run(
|
||||
"classification.edit_reference", tool.Ifc.get(), reference=reference, attributes=attributes
|
||||
)
|
||||
products = [
|
||||
tool.Ifc.get().by_id(ifc_definition_id)
|
||||
for obj in objects
|
||||
if (ifc_definition_id := tool.Blender.get_obj_ifc_definition_id(obj, self.obj_type, context))
|
||||
]
|
||||
if products:
|
||||
classification = tool.Ifc.get().by_id(int(props.classifications))
|
||||
reference = ifcopenshell.api.run(
|
||||
"classification.add_reference",
|
||||
tool.Ifc.get(),
|
||||
products=products,
|
||||
classification=classification,
|
||||
identification="X",
|
||||
name="Unnamed",
|
||||
)
|
||||
ifcopenshell.api.run(
|
||||
"classification.edit_reference", tool.Ifc.get(), reference=reference, attributes=attributes
|
||||
)
|
||||
props.is_adding = False
|
||||
|
||||
|
||||
@@ -150,8 +161,7 @@ class EnableAddingManualClassificationReference(bpy.types.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.active_object
|
||||
props = obj.BIMClassificationReferenceProperties
|
||||
props = context.scene.BIMClassificationReferenceProperties
|
||||
props.is_adding = True
|
||||
props.reference_attributes.clear()
|
||||
blenderbim.bim.helper.import_attributes2("IfcClassificationReference", props.reference_attributes)
|
||||
@@ -164,8 +174,7 @@ class DisableAddingManualClassificationReference(bpy.types.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.active_object
|
||||
props = obj.BIMClassificationReferenceProperties
|
||||
props = context.scene.BIMClassificationReferenceProperties
|
||||
props.is_adding = False
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -257,8 +266,7 @@ class EnableEditingClassificationReference(bpy.types.Operator):
|
||||
obj: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
|
||||
props = obj.BIMClassificationReferenceProperties
|
||||
props = context.scene.BIMClassificationReferenceProperties
|
||||
props.reference_attributes.clear()
|
||||
blenderbim.bim.helper.import_attributes2(tool.Ifc.get().by_id(self.reference), props.reference_attributes)
|
||||
props.active_reference_id = self.reference
|
||||
@@ -272,8 +280,7 @@ class DisableEditingClassificationReference(bpy.types.Operator):
|
||||
obj: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
|
||||
obj.BIMClassificationReferenceProperties.active_reference_id = 0
|
||||
context.scene.BIMClassificationReferenceProperties.active_reference_id = 0
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -326,8 +333,7 @@ class EditClassificationReference(bpy.types.Operator, tool.Ifc.Operator):
|
||||
obj: bpy.props.StringProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
|
||||
props = obj.BIMClassificationReferenceProperties
|
||||
props = context.scene.BIMClassificationReferenceProperties
|
||||
attributes = {}
|
||||
for attribute in props.reference_attributes:
|
||||
if attribute.is_null:
|
||||
@@ -368,9 +374,8 @@ class AddClassificationReference(bpy.types.Operator, tool.Ifc.Operator):
|
||||
classification = element
|
||||
break
|
||||
|
||||
ifc_file = tool.Ifc.get()
|
||||
products = [
|
||||
ifc_file.by_id(ifc_definition_id)
|
||||
tool.Ifc.get().by_id(ifc_definition_id)
|
||||
for obj in objects
|
||||
if (ifc_definition_id := tool.Blender.get_obj_ifc_definition_id(obj, self.obj_type, context))
|
||||
]
|
||||
|
||||
@@ -118,10 +118,9 @@ class BIM_PT_classifications(Panel):
|
||||
class ReferenceUI:
|
||||
def draw_ui(self, context):
|
||||
obj = context.active_object
|
||||
self.oprops = obj.BIMObjectProperties
|
||||
self.sprops = context.scene.BIMClassificationProperties
|
||||
self.bprops = context.scene.BIMBSDDProperties
|
||||
self.props = obj.BIMClassificationReferenceProperties
|
||||
self.props = context.scene.BIMClassificationReferenceProperties
|
||||
self.file = IfcStore.get_file()
|
||||
|
||||
self.draw_add_ui(context)
|
||||
@@ -155,7 +154,7 @@ class ReferenceUI:
|
||||
blenderbim.bim.helper.draw_attributes(self.props.reference_attributes, self.layout)
|
||||
row = self.layout.row(align=True)
|
||||
op = row.operator("bim.add_manual_classification_reference", text="Save", icon="CHECKMARK")
|
||||
op.type = self.data.data["object_type"]
|
||||
op.obj_type = self.data.data["object_type"]
|
||||
row.operator("bim.disable_adding_manual_classification_reference", text="", icon="CANCEL")
|
||||
else:
|
||||
row = self.layout.row()
|
||||
@@ -295,28 +294,31 @@ class BIM_PT_material_classifications(Panel, ReferenceUI):
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "material"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_materials"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if not tool.Ifc.get():
|
||||
return False
|
||||
try:
|
||||
return bool(context.active_object.active_material.BIMObjectProperties.ifc_definition_id)
|
||||
except:
|
||||
return False
|
||||
props = context.scene.BIMMaterialProperties
|
||||
if props.materials and props.active_material_index < len(props.materials):
|
||||
material = props.materials[props.active_material_index]
|
||||
if material.ifc_definition_id:
|
||||
return True
|
||||
return False
|
||||
|
||||
def draw(self, context):
|
||||
if not MaterialClassificationsData.is_loaded:
|
||||
MaterialClassificationsData.load()
|
||||
self.data = MaterialClassificationsData
|
||||
self.obj = context.active_object.active_material.name
|
||||
self.obj = ""
|
||||
self.obj_type = "Material"
|
||||
self.draw_ui(context)
|
||||
|
||||
|
||||
class BIM_PT_cost_classifications(Panel, ReferenceUI):
|
||||
bl_label = "Cost Classifications"
|
||||
bl_label = "Cost Item Classifications"
|
||||
bl_idname = "BIM_PT_cost_classifications"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
|
||||
@@ -82,6 +82,7 @@ classes = (
|
||||
prop.CostItem,
|
||||
prop.CostItemQuantity,
|
||||
prop.CostItemType,
|
||||
prop.ScheduleColumn,
|
||||
prop.BIMCostProperties,
|
||||
ui.BIM_PT_cost_schedules,
|
||||
ui.BIM_PT_cost_item_quantities,
|
||||
|
||||
@@ -19,10 +19,13 @@
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.cost
|
||||
import ifcopenshell.util.date
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.unit
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.bim.schema
|
||||
from ifcopenshell.util.doc import get_entity_doc, get_predefined_type_doc
|
||||
from typing import Any
|
||||
|
||||
|
||||
def refresh():
|
||||
@@ -34,6 +37,7 @@ def refresh():
|
||||
class CostSchedulesData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
_cost_values: dict[int, dict[str, Any]]
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
@@ -136,7 +140,7 @@ class CostSchedulesData:
|
||||
data["UnitBasisUnitSymbol"] = "U"
|
||||
if cost_value.Category == "*":
|
||||
is_sum = True
|
||||
cost_quantity = data["TotalCostQuantity"] or 1
|
||||
cost_quantity = 1 if data["TotalCostQuantity"] is None else data["TotalCostQuantity"]
|
||||
if has_unit_basis:
|
||||
data["TotalCost"] = data["TotalAppliedValue"] * cost_quantity / data["UnitBasisValueComponent"]
|
||||
else:
|
||||
@@ -155,6 +159,7 @@ class CostSchedulesData:
|
||||
data["UnitSymbol"] = "-"
|
||||
if cost_item.CostQuantities:
|
||||
quantity = cost_item.CostQuantities[0]
|
||||
data["QuantityType"] = quantity.is_a()
|
||||
unit = ifcopenshell.util.unit.get_property_unit(quantity, tool.Ifc.get())
|
||||
if unit:
|
||||
data["UnitSymbol"] = ifcopenshell.util.unit.get_unit_symbol(unit)
|
||||
|
||||
@@ -73,7 +73,7 @@ class RemoveCostSchedule(bpy.types.Operator, tool.Ifc.Operator):
|
||||
cost_schedule: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.remove_cost_schedule(tool.Ifc, cost_schedule=tool.Ifc.get().by_id(self.cost_schedule))
|
||||
core.remove_cost_schedule(tool.Ifc, tool.Cost, cost_schedule=tool.Ifc.get().by_id(self.cost_schedule))
|
||||
|
||||
|
||||
class EnableEditingCostSchedule(bpy.types.Operator):
|
||||
@@ -223,36 +223,40 @@ class EditCostItem(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
class AssignCostItemType(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.assign_cost_item_type"
|
||||
bl_label = "Assign Cost Item Type Product"
|
||||
bl_label = "Assign Cost Item To Product Types"
|
||||
bl_description = "Assign cost item to currently selected or active product types"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
cost_item: bpy.props.IntProperty()
|
||||
prop_name: bpy.props.StringProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.assign_cost_item_type(
|
||||
product_types = core.assign_cost_item_type(
|
||||
tool.Ifc,
|
||||
tool.Cost,
|
||||
tool.Spatial,
|
||||
cost_item=tool.Ifc.get().by_id(self.cost_item),
|
||||
prop_name=self.prop_name, # TODO: REVIEW PROP_NAME USABILITY
|
||||
)
|
||||
self.report({"INFO"}, f"Cost item was assigned to {len(product_types)} product types.")
|
||||
|
||||
|
||||
class UnassignCostItemType(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.unassign_cost_item_type"
|
||||
bl_label = "Unassign Cost Item Type"
|
||||
bl_description = "Unassign cost item from currently selected or active product types"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
cost_item: bpy.props.IntProperty()
|
||||
related_object: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.unassign_cost_item_type(
|
||||
product_types = core.unassign_cost_item_type(
|
||||
tool.Ifc,
|
||||
tool.Cost,
|
||||
tool.Spatial,
|
||||
cost_item=tool.Ifc.get().by_id(self.cost_item),
|
||||
product_types=[tool.Ifc.get().by_id(self.related_object)] if self.related_object else [],
|
||||
product_types=[tool.Ifc.get().by_id(self.related_object)] if self.related_object else None,
|
||||
)
|
||||
self.report({"INFO"}, f"Cost item was unassigned from {len(product_types)} product types.")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -287,9 +291,11 @@ class UnassignCostItemQuantity(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Ifc,
|
||||
tool.Cost,
|
||||
cost_item=tool.Ifc.get().by_id(self.cost_item),
|
||||
products=[tool.Ifc.get().by_id(self.related_object)]
|
||||
if self.related_object
|
||||
else tool.Spatial.get_selected_products(),
|
||||
products=(
|
||||
[tool.Ifc.get().by_id(self.related_object)]
|
||||
if self.related_object
|
||||
else tool.Spatial.get_selected_products()
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -509,7 +515,7 @@ class SelectCostScheduleProducts(bpy.types.Operator):
|
||||
|
||||
|
||||
class ImportCostScheduleCsv(bpy.types.Operator, ImportHelper, tool.Ifc.Operator):
|
||||
bl_idname = "import_cost_schedule_csv.bim"
|
||||
bl_idname = "bim.import_cost_schedule_csv"
|
||||
bl_label = "Import Cost Schedule CSV"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filename_ext = ".csv"
|
||||
@@ -527,6 +533,13 @@ class AddCostColumn(bpy.types.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
name: bpy.props.StringProperty()
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if not context.scene.BIMCostProperties.cost_column:
|
||||
cls.poll_message_set("Cost column name is empty")
|
||||
return False
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
core.add_cost_column(tool.Cost, self.name)
|
||||
return {"FINISHED"}
|
||||
@@ -538,7 +551,7 @@ class RemoveCostColumn(bpy.types.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
name: bpy.props.StringProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
def execute(self, context):
|
||||
core.remove_cost_column(tool.Cost, self.name)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -656,13 +669,17 @@ class ExportCostSchedules(bpy.types.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Export a cost schedule to a CSV, XSLX OR ODS file"
|
||||
cost_schedule: bpy.props.IntProperty()
|
||||
format: bpy.props.EnumProperty("Format", items=(("CSV", "CSV", ""), ("XLSX", "XLSX", ""), ("ODS", "ODS", "")))
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
format: bpy.props.EnumProperty(name="Format", items=(("CSV", "CSV", ""), ("XLSX", "XLSX", ""), ("ODS", "ODS", "")))
|
||||
directory: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
filter_folder: bpy.props.BoolProperty(
|
||||
name="Filter Folders",
|
||||
default=True,
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
cost_schedule = tool.Ifc.get().by_id(self.cost_schedule) if self.cost_schedule else None
|
||||
r = core.export_cost_schedules(
|
||||
tool.Cost, filepath=self.filepath, format=self.format, cost_schedule=cost_schedule
|
||||
tool.Cost, filepath=self.directory, format=self.format, cost_schedule=cost_schedule
|
||||
)
|
||||
if isinstance(r, str):
|
||||
self.report({"ERROR"}, r)
|
||||
@@ -676,6 +693,7 @@ class ExportCostSchedules(bpy.types.Operator):
|
||||
def draw(self, context):
|
||||
self.layout.label(text="Choose a format")
|
||||
self.layout.prop(self, "format")
|
||||
self.layout.label(text="Select a directory.")
|
||||
|
||||
|
||||
class ClearCostItemAssignments(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
@@ -175,6 +175,10 @@ def update_active_cost_item_resources(self, context):
|
||||
bpy.ops.bim.load_cost_item_resource_quantities()
|
||||
|
||||
|
||||
class ScheduleColumn(PropertyGroup):
|
||||
schedule_id: IntProperty()
|
||||
|
||||
|
||||
class BIMCostProperties(PropertyGroup):
|
||||
cost_schedule_predefined_types: EnumProperty(
|
||||
items=get_schedule_predefined_types, name="Predefined Type", default=None
|
||||
@@ -210,9 +214,14 @@ class BIMCostProperties(PropertyGroup):
|
||||
cost_value_attributes: CollectionProperty(name="Cost Value Attributes", type=Attribute)
|
||||
cost_value_formula: StringProperty(name="Cost Value Formula")
|
||||
cost_column: StringProperty(name="Cost Column")
|
||||
should_show_column_ui: BoolProperty(name="Should Show Column UI", default=False)
|
||||
should_show_column_ui: BoolProperty(
|
||||
name="Should Show Column UI",
|
||||
description="Display UI for adding cost schedule columns, column names represent a category for cost item values",
|
||||
default=False,
|
||||
)
|
||||
should_show_currency_ui: BoolProperty(name="Should Show Currency UI", default=False)
|
||||
columns: CollectionProperty(name="Columns", type=StrProperty)
|
||||
columns: CollectionProperty(name="Active Schedule Columns", type=StrProperty)
|
||||
columns_storage: CollectionProperty(name="Columns", type=ScheduleColumn)
|
||||
active_column_index: IntProperty(name="Active Column Index")
|
||||
cost_item_products: CollectionProperty(name="Cost Item Products", type=CostItemQuantity)
|
||||
active_cost_item_product_index: IntProperty(name="Active Cost Item Product Index")
|
||||
|
||||
@@ -21,6 +21,7 @@ import blenderbim.bim.module.cost.prop as CostProp
|
||||
from bpy.types import Panel, UIList
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.cost.data import CostSchedulesData
|
||||
from typing import Any
|
||||
|
||||
|
||||
class BIM_PT_cost_schedules(Panel):
|
||||
@@ -53,7 +54,7 @@ class BIM_PT_cost_schedules(Panel):
|
||||
row.label(text="No Cost Schedules found.", icon="TEXT")
|
||||
|
||||
row.operator("bim.add_cost_schedule", icon="ADD", text="")
|
||||
row.operator("import_cost_schedule_csv.bim",icon="IMPORT",text="")
|
||||
row.operator("bim.import_cost_schedule_csv",icon="IMPORT",text="")
|
||||
|
||||
for schedule in CostSchedulesData.data["schedules"]:
|
||||
self.draw_cost_schedule_ui(schedule)
|
||||
@@ -178,24 +179,33 @@ class BIM_PT_cost_schedules(Panel):
|
||||
"active_cost_item_index",
|
||||
)
|
||||
if self.props.active_cost_item_id:
|
||||
cost_item = CostSchedulesData.data["cost_items"][ifc_definition_id]
|
||||
if self.props.cost_item_editing_type == "ATTRIBUTES":
|
||||
self.draw_editable_cost_item_attributes_ui()
|
||||
elif self.props.cost_item_editing_type == "QUANTITIES":
|
||||
self.draw_editable_cost_item_quantities_ui()
|
||||
self.draw_editable_cost_item_quantities_ui(cost_item)
|
||||
elif self.props.cost_item_editing_type == "VALUES":
|
||||
self.draw_editable_cost_item_values_ui()
|
||||
|
||||
def draw_editable_cost_item_attributes_ui(self):
|
||||
blenderbim.bim.helper.draw_attributes(self.props.cost_item_attributes, self.layout)
|
||||
|
||||
def draw_editable_cost_item_quantities_ui(self):
|
||||
def draw_editable_cost_item_quantities_ui(self, cost_item: dict[str, Any]):
|
||||
quantities = CostSchedulesData.data["cost_quantities"]
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.props, "quantity_types", text="")
|
||||
# In IFC, all quantities of IfcCostTime should have 1 type.
|
||||
if quantities:
|
||||
quantity_class = cost_item["QuantityType"]
|
||||
row.label(text=quantity_class)
|
||||
else:
|
||||
row.prop(self.props, "quantity_types", text="")
|
||||
quantity_class = self.props.quantity_types
|
||||
|
||||
op = row.operator("bim.add_cost_item_quantity", text="", icon="ADD")
|
||||
op.cost_item = self.props.active_cost_item_id
|
||||
op.ifc_class = self.props.quantity_types
|
||||
op.ifc_class = quantity_class
|
||||
|
||||
for quantity in CostSchedulesData.data["cost_quantities"]:
|
||||
for quantity in quantities:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=quantity["name"])
|
||||
row.label(text=quantity["value"])
|
||||
@@ -634,7 +644,7 @@ class BIM_UL_cost_items_trait:
|
||||
layout.label(text=cost_item["UnitBasisUnitSymbol"])
|
||||
|
||||
def draw_total_quantity_column(self, layout, cost_item):
|
||||
if cost_item["TotalCostQuantity"]:
|
||||
if cost_item["TotalCostQuantity"] is not None:
|
||||
label = "{0:.2f}".format(cost_item["TotalCostQuantity"]) + f" {cost_item['UnitSymbol'] or '-'}"
|
||||
layout.label(text=label)
|
||||
else:
|
||||
@@ -745,9 +755,11 @@ class BIM_UL_product_cost_items(UIList):
|
||||
class BIM_PT_Costing_Tools(Panel):
|
||||
bl_label = "5D Tools"
|
||||
bl_idname = "BIM_PT_Costing_Tools"
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "UI"
|
||||
bl_category = "4D/5D Toolkit"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_tab_cost"
|
||||
|
||||
def draw(self, context):
|
||||
self.props = context.scene.BIMCostProperties
|
||||
|
||||
@@ -23,6 +23,7 @@ import ifccsv
|
||||
import logging
|
||||
import tempfile
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.selector
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.bim.module.drawing.scheduler as scheduler
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
@@ -24,7 +24,6 @@ import random
|
||||
import logging
|
||||
import platform
|
||||
import subprocess
|
||||
import addon_utils
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.element
|
||||
@@ -34,38 +33,17 @@ import blenderbim.tool as tool
|
||||
import blenderbim.core.debug as core
|
||||
import blenderbim.bim.handler
|
||||
import blenderbim.bim.import_ifc as import_ifc
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim import get_debug_info, format_debug_info
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
|
||||
class CopyDebugInformation(bpy.types.Operator):
|
||||
bl_idname = "bim.copy_debug_information"
|
||||
bl_label = "Copy Debug Information"
|
||||
bl_description = "Copies debugging information to your clipboard for use in bugreports"
|
||||
bl_description = "Copies debugging information to your clipboard for use in bug reports"
|
||||
|
||||
def execute(self, context):
|
||||
version = ".".join(
|
||||
[
|
||||
str(x)
|
||||
for x in [
|
||||
addon.bl_info.get("version", (-1, -1, -1))
|
||||
for addon in addon_utils.modules()
|
||||
if addon.bl_info["name"] == "BlenderBIM"
|
||||
][0]
|
||||
]
|
||||
)
|
||||
info = {
|
||||
"os": platform.system(),
|
||||
"os_version": platform.version(),
|
||||
"python_version": platform.python_version(),
|
||||
"architecture": platform.architecture(),
|
||||
"machine": platform.machine(),
|
||||
"processor": platform.processor(),
|
||||
"blender_version": bpy.app.version_string,
|
||||
"blenderbim_version": version,
|
||||
"ifc": False,
|
||||
}
|
||||
|
||||
info = get_debug_info()
|
||||
if tool.Ifc.get():
|
||||
info.update(
|
||||
{
|
||||
@@ -76,17 +54,15 @@ class CopyDebugInformation(bpy.types.Operator):
|
||||
}
|
||||
)
|
||||
|
||||
# Format it in a readable way
|
||||
text = "\n".join(f"{k}: {v}" for k, v in info.items())
|
||||
print(text)
|
||||
text = format_debug_info(info)
|
||||
|
||||
if platform.system() == "Windows":
|
||||
command = "echo | set /p nul=" + text.strip()
|
||||
elif platform.system() == "Darwin": # for MacOS
|
||||
command = 'printf "' + text.strip().replace("\n", "\\n").replace('"', "") + '" | pbcopy'
|
||||
else: # Linux
|
||||
command = 'printf "' + text.strip().replace("\n", "\\n").replace('"', "") + '" | xclip -selection clipboard'
|
||||
subprocess.run(command, shell=True, check=True)
|
||||
text_with_backticks = f"```\n{text}\n```"
|
||||
|
||||
print("-" * 80)
|
||||
print(text_with_backticks)
|
||||
print("-" * 80)
|
||||
|
||||
context.window_manager.clipboard = text_with_backticks
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
|
||||
@@ -159,9 +159,7 @@ class BaseDecorator:
|
||||
objecttype = "NOTDEFINED"
|
||||
|
||||
def __init__(self):
|
||||
self.font_id = blf.load(
|
||||
os.path.join(bpy.context.scene.BIMProperties.data_dir, "fonts", "OpenGost Type B TT.ttf")
|
||||
)
|
||||
self.font_id = 0 # 0 is the default font
|
||||
|
||||
# POLYLINE_UNIFORM_COLOR is good for smoothed lines since `bgl.enable(GL_LINE_SMOOTH)` is deprecated
|
||||
self.line_shader = gpu.shader.from_builtin("POLYLINE_UNIFORM_COLOR")
|
||||
@@ -404,7 +402,6 @@ class BaseDecorator:
|
||||
line_no += 1 if multiline_to_bottom else -1
|
||||
return
|
||||
|
||||
# 0 is the default font, but we're fancier than that
|
||||
font_id = self.font_id
|
||||
|
||||
color = context.preferences.addons["blenderbim"].preferences.decorations_colour
|
||||
@@ -2014,6 +2011,13 @@ class DecorationsHandler:
|
||||
for object_type in ("SLOPE_ANGLE", "SLOPE_FRACTION", "SLOPE_PERCENT"):
|
||||
self.decorators[object_type] = self.decorators["FALL"]
|
||||
self.decorators["MULTI_SYMBOL"] = self.decorators["SYMBOL"]
|
||||
if drawing_font := bpy.context.scene.DocProperties.drawing_font:
|
||||
drawing_font_path = os.path.join(bpy.context.scene.BIMProperties.data_dir, "fonts", drawing_font)
|
||||
if os.path.exists(drawing_font_path):
|
||||
font_id = blf.load(drawing_font_path)
|
||||
for decorator in self.decorators.values():
|
||||
decorator.font_id = font_id
|
||||
|
||||
|
||||
def get_objects_and_decorators(self, collection):
|
||||
# TODO: do it in data instead of the handler for performance?
|
||||
|
||||
@@ -529,28 +529,35 @@ class CreateDrawing(bpy.types.Operator):
|
||||
|
||||
files = {context.scene.BIMProperties.ifc_file: tool.Ifc.get()}
|
||||
|
||||
for link in context.scene.BIMProjectProperties.links:
|
||||
if link.name not in IfcStore.session_files:
|
||||
IfcStore.session_files[link.name] = ifcopenshell.open(link.name)
|
||||
files[link.name] = IfcStore.session_files[link.name]
|
||||
|
||||
target_view = ifcopenshell.util.element.get_psets(self.camera_element)["EPset_Drawing"]["TargetView"]
|
||||
self.setup_serialiser(target_view)
|
||||
|
||||
tree = ifcopenshell.geom.tree()
|
||||
tree.enable_face_styles(True)
|
||||
|
||||
for ifc_path, ifc in files.items():
|
||||
# Don't use draw.main() just whilst we're prototyping and experimenting
|
||||
# TODO: hash paths are never used
|
||||
ifc_hash = hashlib.md5(ifc_path.encode("utf-8")).hexdigest()
|
||||
ifc_cache_path = os.path.join(context.scene.BIMProperties.data_dir, "cache", f"{ifc_hash}.h5")
|
||||
|
||||
self.serialiser.setFile(ifc)
|
||||
drawing_elements = tool.Drawing.get_drawing_elements(self.camera_element, ifc_file=ifc)
|
||||
|
||||
# Get all representation contexts to see what we're dealing with.
|
||||
# Drawings only draw bodies and annotations (and facetation, due to a Revit bug).
|
||||
# A drawing prioritises a target view context first, followed by a model view context as a fallback.
|
||||
# Specifically for PLAN_VIEW and REFLECTED_PLAN_VIEW, any Plan context is also prioritised.
|
||||
target_view = ifcopenshell.util.element.get_psets(self.camera_element)["EPset_Drawing"]["TargetView"]
|
||||
contexts = self.get_linework_contexts(ifc, target_view)
|
||||
drawing_elements = tool.Drawing.get_drawing_elements(self.camera_element)
|
||||
|
||||
self.setup_serialiser(ifc, target_view)
|
||||
tree = ifcopenshell.geom.tree()
|
||||
tree.enable_face_styles(True)
|
||||
|
||||
self.serialize_contexts_elements(ifc, tree, contexts, "body", drawing_elements, target_view)
|
||||
self.serialize_contexts_elements(ifc, tree, contexts, "annotation", drawing_elements, target_view)
|
||||
|
||||
if self.camera_element not in drawing_elements:
|
||||
if tool.Ifc.get() == ifc and self.camera_element not in drawing_elements:
|
||||
with profile("Camera element"):
|
||||
# The camera must always be included, regardless of any include/exclude filters.
|
||||
geom_settings = ifcopenshell.geom.settings(DISABLE_TRIANGULATION=True, STRICT_TOLERANCE=True)
|
||||
@@ -865,13 +872,12 @@ class CreateDrawing(bpy.types.Operator):
|
||||
|
||||
return svg_path
|
||||
|
||||
def setup_serialiser(self, ifc, target_view):
|
||||
def setup_serialiser(self, target_view):
|
||||
self.svg_settings = ifcopenshell.geom.settings(
|
||||
DISABLE_TRIANGULATION=True, STRICT_TOLERANCE=True, INCLUDE_CURVES=True
|
||||
)
|
||||
self.svg_buffer = ifcopenshell.geom.serializers.buffer()
|
||||
self.serialiser = ifcopenshell.geom.serializers.svg(self.svg_buffer, self.svg_settings)
|
||||
self.serialiser.setFile(ifc)
|
||||
self.serialiser.setWithoutStoreys(True)
|
||||
self.serialiser.setPolygonal(True)
|
||||
self.serialiser.setUseHlrPoly(True)
|
||||
@@ -935,6 +941,18 @@ class CreateDrawing(bpy.types.Operator):
|
||||
self.is_manifold_cache[obj.data.name] = True
|
||||
return True
|
||||
|
||||
def get_element_by_guid(self, guid):
|
||||
try:
|
||||
return tool.Ifc.get().by_guid(guid)
|
||||
except:
|
||||
for link in bpy.context.scene.BIMProjectProperties.links:
|
||||
if link.name not in IfcStore.session_files:
|
||||
IfcStore.session_files[link.name] = ifcopenshell.open(link.name)
|
||||
try:
|
||||
return IfcStore.session_files[link.name].by_guid(guid)
|
||||
except:
|
||||
continue
|
||||
|
||||
def merge_linework_and_add_metadata(self, root):
|
||||
join_criteria = ifcopenshell.util.element.get_pset(self.camera_element, "EPset_Drawing", "JoinCriteria")
|
||||
if join_criteria:
|
||||
@@ -949,7 +967,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
|
||||
ifc = tool.Ifc.get()
|
||||
for el in root.findall(".//{http://www.w3.org/2000/svg}g[@{http://www.ifcopenshell.org/ns}guid]"):
|
||||
element = ifc.by_guid(el.get("{http://www.ifcopenshell.org/ns}guid"))
|
||||
element = self.get_element_by_guid(el.get("{http://www.ifcopenshell.org/ns}guid"))
|
||||
|
||||
if "projection" in el.get("class", "").split():
|
||||
classes = self.get_svg_classes(element)
|
||||
@@ -962,6 +980,10 @@ class CreateDrawing(bpy.types.Operator):
|
||||
el.set("class", " ".join(classes))
|
||||
|
||||
obj = tool.Ifc.get_object(element)
|
||||
|
||||
if not obj: # This is a linked model object. For now, do nothing.
|
||||
continue
|
||||
|
||||
if not self.is_manifold(obj):
|
||||
continue
|
||||
|
||||
@@ -1515,7 +1537,14 @@ class ActivateDrawing(bpy.types.Operator):
|
||||
if not self.camera_view_point:
|
||||
viewport_position = tool.Blender.get_viewport_position()
|
||||
|
||||
core.activate_drawing_view(tool.Ifc, tool.Drawing, drawing=drawing)
|
||||
try:
|
||||
core.activate_drawing_view(tool.Ifc, tool.Blender, tool.Drawing, drawing=drawing)
|
||||
except core.CameraNotAvailableError:
|
||||
self.report(
|
||||
{"ERROR"},
|
||||
"The drawing view is not available. Ensure you have not excluded it in the active view layer.",
|
||||
)
|
||||
return {"CANCELLED"}
|
||||
|
||||
if not self.camera_view_point:
|
||||
tool.Blender.set_viewport_position(viewport_position)
|
||||
|
||||
@@ -315,9 +315,7 @@ class RasterStyleProperty(enum.Enum):
|
||||
SPACE_SHADING = "space.shading"
|
||||
|
||||
|
||||
RASTER_STYLE_PROPERTIES_EXCLUDE = (
|
||||
"scene.render.filepath",
|
||||
)
|
||||
RASTER_STYLE_PROPERTIES_EXCLUDE = ("scene.render.filepath",)
|
||||
|
||||
|
||||
class DocProperties(PropertyGroup):
|
||||
@@ -371,6 +369,7 @@ class DocProperties(PropertyGroup):
|
||||
default=os.path.join("drawings", "assets", "shading_styles.json"), name="Default Shading Styles"
|
||||
)
|
||||
shadingstyle_default: StringProperty(default="Blender Default", name="Default Shading Style")
|
||||
drawing_font: StringProperty(default="OpenGost Type B TT.ttf", name="Drawing Font")
|
||||
|
||||
|
||||
class BIMCameraProperties(PropertyGroup):
|
||||
|
||||
@@ -16,22 +16,21 @@
|
||||
# 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/>.
|
||||
|
||||
from blenderbim.bim.module.drawing.svgwriter import SvgWriter
|
||||
import os
|
||||
import re
|
||||
import bpy
|
||||
import string
|
||||
import svgwrite
|
||||
import openpyxl
|
||||
|
||||
from blenderbim.bim.module.drawing.svgwriter import SvgWriter
|
||||
from odf.opendocument import load as load_ods
|
||||
from odf.table import Table, TableRow, TableColumn, TableCell
|
||||
from odf.text import P
|
||||
from odf.style import Style
|
||||
from textwrap import wrap
|
||||
from pathlib import Path
|
||||
import string
|
||||
|
||||
FONT_SIZE = 4.13
|
||||
FONT_WIDTH = lambda size: size * 0.45
|
||||
FONT_SIZE_PT = 12
|
||||
FONT_FAMILY = "OpenGost Type B TT"
|
||||
DEBUG = False
|
||||
|
||||
|
||||
@@ -63,11 +62,29 @@ class Scheduler:
|
||||
)
|
||||
self.padding = 1
|
||||
self.margin = 1
|
||||
|
||||
self.parse_css(infile)
|
||||
if infile.endswith("ods"):
|
||||
self.schedule_ods(infile, outfile)
|
||||
elif infile.endswith("xlsx"):
|
||||
self.schedule_xlsx(infile, outfile)
|
||||
|
||||
def parse_css(self, infile):
|
||||
stylesheet_path = os.path.splitext(infile)[0] + ".css"
|
||||
if not os.path.exists(stylesheet_path):
|
||||
stylesheet_path = os.path.join(bpy.context.scene.BIMProperties.data_dir, "assets", "schedule.css")
|
||||
with open(stylesheet_path, "r") as stylesheet:
|
||||
css = stylesheet.read()
|
||||
|
||||
matches = re.search("--font-size-pt:\s*([0-9.]+);", css)
|
||||
self.font_size_pt = float(matches.groups()[0]) if matches else 12 # Default to 12pt
|
||||
matches = re.search("--font-size-px:\s*([0-9.]+);", css)
|
||||
self.font_size_px = float(matches.groups()[0]) if matches else 4.13 # Magic number 4.13px ~= 12pt
|
||||
matches = re.search("--font-width:\s*([0-9.]+);", css)
|
||||
self.font_width = float(matches.groups()[0]) if matches else 0.45 # A magic number for OpenGost
|
||||
|
||||
self.svg.defs.add(self.svg.style(css))
|
||||
|
||||
def schedule_xlsx(self, infile, outfile):
|
||||
workbook = openpyxl.open(infile, data_only=True)
|
||||
sheet = workbook.active
|
||||
@@ -144,8 +161,8 @@ class Scheduler:
|
||||
x += unmerged_width
|
||||
continue
|
||||
|
||||
font_size = cell.font.size or 11 # 11pt default
|
||||
font_size = font_size / FONT_SIZE_PT * FONT_SIZE # Magic?
|
||||
font_size = cell.font.size or self.font_size_pt # 12pt default
|
||||
font_size = font_size / self.font_size_pt * self.font_size_px # Magic?
|
||||
|
||||
text_position = [0.0, 0.0]
|
||||
if cell.alignment.horizontal == "left":
|
||||
@@ -424,9 +441,9 @@ class Scheduler:
|
||||
italic_text = final_cell_style.get("font-style", None) == "italic"
|
||||
# NOTE: very naive since we're scaling text proportionally
|
||||
font_size = (
|
||||
float(final_cell_style.get("font-size", f"{FONT_SIZE_PT}pt")[:-2])
|
||||
/ FONT_SIZE_PT
|
||||
* FONT_SIZE
|
||||
float(final_cell_style.get("font-size", f"{self.font_size_pt}pt")[:-2])
|
||||
/ self.font_size_pt
|
||||
* self.font_size_px
|
||||
)
|
||||
|
||||
if p_tags:
|
||||
@@ -523,10 +540,7 @@ class Scheduler:
|
||||
"""
|
||||
text_lines = [str(p) for p in p_tags]
|
||||
box_alignment_params = SvgWriter.get_box_alignment_parameters(box_alignment)
|
||||
text_params = {
|
||||
"font-size": font_size,
|
||||
"font-family": FONT_FAMILY,
|
||||
}
|
||||
text_params = {"font-size": font_size}
|
||||
if bold:
|
||||
text_params["font-weight"] = "bold"
|
||||
if italic:
|
||||
@@ -546,13 +560,13 @@ class Scheduler:
|
||||
|
||||
text_tag = self.svg.text("", **(text_params | {"font-size": "0"} | box_alignment_params))
|
||||
|
||||
# TODO: should be done in less naive way
|
||||
# without using magic number for FONT_WIDTH
|
||||
# currently it might not work for all fonts and font sizes
|
||||
# TODO: Should be done without using magic number for self.font_width
|
||||
if wrap_text:
|
||||
wrapped_lines = []
|
||||
for line in text_lines:
|
||||
wrapped_line = wrap(line, width=int(cell_width // FONT_WIDTH(font_size)), break_long_words=False)
|
||||
wrapped_line = wrap(
|
||||
line, width=int(cell_width // (font_size * self.font_width)), break_long_words=False
|
||||
)
|
||||
wrapped_lines.extend(wrapped_line)
|
||||
else:
|
||||
wrapped_lines = text_lines
|
||||
|
||||
@@ -22,14 +22,20 @@ from . import ui, prop, operator
|
||||
classes = (
|
||||
operator.AddRepresentation,
|
||||
operator.CopyRepresentation,
|
||||
operator.DisableEditingRepresentationItemShapeAspect,
|
||||
operator.DisableEditingRepresentationItemStyle,
|
||||
operator.DisableEditingRepresentationItems,
|
||||
operator.DuplicateLinkedAggregateTo3dCursor,
|
||||
operator.DuplicateMoveLinkedAggregate,
|
||||
operator.DuplicateMoveLinkedAggregateMacro,
|
||||
operator.EditObjectPlacement,
|
||||
operator.EditRepresentationItemShapeAspect,
|
||||
operator.EditRepresentationItemStyle,
|
||||
operator.EnableEditingRepresentationItemShapeAspect,
|
||||
operator.EnableEditingRepresentationItemStyle,
|
||||
operator.EnableEditingRepresentationItems,
|
||||
operator.FlipObject,
|
||||
operator.GetRepresentationIfcParameters,
|
||||
operator.DuplicateMoveLinkedAggregate,
|
||||
operator.DuplicateMoveLinkedAggregateMacro,
|
||||
operator.DuplicateLinkedAggregateTo3dCursor,
|
||||
operator.OverrideDelete,
|
||||
operator.OverrideDuplicateMove,
|
||||
operator.OverrideDuplicateMoveLinked,
|
||||
@@ -46,19 +52,13 @@ classes = (
|
||||
operator.RefreshLinkedAggregate,
|
||||
operator.RemoveConnection,
|
||||
operator.RemoveRepresentation,
|
||||
operator.RemoveRepresentationItem,
|
||||
operator.RemoveRepresentationItemFromShapeAspect,
|
||||
operator.SelectConnection,
|
||||
operator.SwitchRepresentation,
|
||||
operator.UnassignRepresentationItemStyle,
|
||||
operator.UpdateParametricRepresentation,
|
||||
operator.UpdateRepresentation,
|
||||
operator.RemoveRepresentationItem,
|
||||
operator.EnableEditingRepresentationItemStyle,
|
||||
operator.EditRepresentationItemStyle,
|
||||
operator.DisableEditingRepresentationItemStyle,
|
||||
operator.UnassignRepresentationItemStyle,
|
||||
operator.EnableEditingRepresentationItemShapeAspect,
|
||||
operator.EditRepresentationItemShapeAspect,
|
||||
operator.DisableEditingRepresentationItemShapeAspect,
|
||||
operator.RemoveRepresentationItemFromShapeAspect,
|
||||
prop.RepresentationItem,
|
||||
prop.ShapeAspect,
|
||||
prop.BIMObjectGeometryProperties,
|
||||
@@ -120,6 +120,7 @@ def register():
|
||||
|
||||
km = wm.keyconfigs.addon.keymaps.new(name="Mesh", space_type="EMPTY")
|
||||
kmi = km.keymap_items.new("bim.override_mode_set_object", "TAB", "PRESS")
|
||||
kmi.properties.should_save = True
|
||||
addon_keymaps.append((km, kmi))
|
||||
kmi = km.keymap_items.new("wm.call_menu", "P", "PRESS")
|
||||
kmi.properties.name = ui.BIM_MT_hotkey_separate.bl_idname
|
||||
|
||||
@@ -136,19 +136,23 @@ class AddRepresentation(bpy.types.Operator, Operator):
|
||||
"for Profile - 2D bounding box by local XZ axes.\n"
|
||||
"For other contexts - bounding box is 3d.",
|
||||
),
|
||||
("PROJECT", "Full Representation", ""),
|
||||
("OBJECT", "From Object", "Copies geometry from another object"),
|
||||
("PROJECT", "Full Representation", "Reuses the current representation"),
|
||||
],
|
||||
name="Representation Conversion Method",
|
||||
)
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
props = obj.BIMGeometryProperties
|
||||
ifc_context = int(props.contexts or "0") or None
|
||||
props = context.scene.BIMGeometryProperties
|
||||
oprops = obj.BIMGeometryProperties
|
||||
ifc_context = int(oprops.contexts or "0") or None
|
||||
if not ifc_context:
|
||||
return
|
||||
ifc_context = tool.Ifc.get().by_id(ifc_context)
|
||||
|
||||
original_data = obj.data
|
||||
|
||||
if self.representation_conversion_method == "OUTLINE":
|
||||
if ifc_context.ContextType == "Plan":
|
||||
data = tool.Geometry.generate_outline_mesh(obj, axis="+Z")
|
||||
@@ -165,17 +169,31 @@ class AddRepresentation(bpy.types.Operator, Operator):
|
||||
else:
|
||||
data = tool.Geometry.generate_3d_box_mesh(obj)
|
||||
tool.Geometry.change_object_data(obj, data, is_global=True)
|
||||
elif (
|
||||
self.representation_conversion_method == "OBJECT"
|
||||
and props.representation_from_object
|
||||
and props.representation_from_object.data
|
||||
):
|
||||
data = tool.Geometry.duplicate_object_data(props.representation_from_object)
|
||||
tool.Geometry.change_object_data(obj, data, is_global=True)
|
||||
|
||||
core.add_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
tool.Style,
|
||||
tool.Surveyor,
|
||||
obj=obj,
|
||||
context=ifc_context,
|
||||
ifc_representation_class=None,
|
||||
profile_set_usage=None,
|
||||
)
|
||||
try:
|
||||
core.add_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
tool.Style,
|
||||
tool.Surveyor,
|
||||
obj=obj,
|
||||
context=ifc_context,
|
||||
ifc_representation_class=None,
|
||||
profile_set_usage=None,
|
||||
)
|
||||
except core.IncompatibleRepresentationError:
|
||||
if obj.data != original_data:
|
||||
tool.Geometry.change_object_data(obj, original_data, is_global=True)
|
||||
bpy.data.meshes.remove(data)
|
||||
self.report({"ERROR"}, "No compatible representation for the context could be created.")
|
||||
return {"CANCELLED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
@@ -183,6 +201,9 @@ class AddRepresentation(bpy.types.Operator, Operator):
|
||||
def draw(self, context):
|
||||
row = self.layout.row()
|
||||
row.prop(self, "representation_conversion_method", text="")
|
||||
if self.representation_conversion_method == "OBJECT":
|
||||
row = self.layout.row()
|
||||
row.prop(context.scene.BIMGeometryProperties, "representation_from_object", text="")
|
||||
|
||||
|
||||
class SelectConnection(bpy.types.Operator, Operator):
|
||||
@@ -379,7 +400,11 @@ class UpdateRepresentation(bpy.types.Operator, Operator):
|
||||
representation_data["profile_set_usage"] = tool.Geometry.get_profile_set_usage(product)
|
||||
representation_data["text_literal"] = tool.Geometry.get_text_literal(old_representation)
|
||||
|
||||
# TODO: replace with core.add_representation?
|
||||
new_representation = ifcopenshell.api.run("geometry.add_representation", self.file, **representation_data)
|
||||
if new_representation is None:
|
||||
self.report({"ERROR"}, "Error creating representation for Blender object.")
|
||||
return {"CANCELLED"}
|
||||
|
||||
if tool.Geometry.is_body_representation(new_representation):
|
||||
[
|
||||
@@ -917,7 +942,7 @@ class OverrideDuplicateMove(bpy.types.Operator):
|
||||
pset = ifcopenshell.util.element.get_pset(new[0], "BBIM_Linked_Aggregate")
|
||||
if pset:
|
||||
pset = tool.Ifc.get().by_id(pset["id"])
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=new[0],pset=pset)
|
||||
|
||||
if new[0].is_a("IfcElementAssembly"):
|
||||
linked_aggregate_group = [
|
||||
@@ -1029,7 +1054,7 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator):
|
||||
if self.group_name in product_groups_name:
|
||||
return
|
||||
|
||||
linked_aggregate_group = ifcopenshell.api.run("group.add_group", tool.Ifc.get(), Name=self.group_name)
|
||||
linked_aggregate_group = ifcopenshell.api.run("group.add_group", tool.Ifc.get(), name=self.group_name)
|
||||
ifcopenshell.api.run("group.assign_group", tool.Ifc.get(), products=[element], group=linked_aggregate_group)
|
||||
|
||||
def custom_incremental_naming_for_element_assembly(old_to_new):
|
||||
@@ -1658,20 +1683,11 @@ class OverrideModeSetObject(bpy.types.Operator):
|
||||
apply_openings=True,
|
||||
)
|
||||
|
||||
def draw(self, context):
|
||||
if self.is_valid:
|
||||
row = self.layout.row()
|
||||
row.prop(self, "should_save")
|
||||
else:
|
||||
row = self.layout.row()
|
||||
row.label(text="No Geometry Found: Object will revert to previous state.")
|
||||
|
||||
def invoke(self, context, event):
|
||||
return IfcStore.execute_ifc_operator(self, context, is_invoke=True)
|
||||
|
||||
def _invoke(self, context, event):
|
||||
self.is_valid = True
|
||||
self.should_save = True
|
||||
|
||||
bpy.ops.object.mode_set(mode="EDIT", toggle=True)
|
||||
|
||||
@@ -1733,8 +1749,6 @@ class OverrideModeSetObject(bpy.types.Operator):
|
||||
else:
|
||||
tool.Ifc.finish_edit(obj)
|
||||
|
||||
if self.edited_objs:
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
return self.execute(context)
|
||||
|
||||
|
||||
|
||||
@@ -138,3 +138,4 @@ class BIMGeometryProperties(PropertyGroup):
|
||||
name="IFC Interaction Mode",
|
||||
update=update_mode,
|
||||
)
|
||||
representation_from_object: PointerProperty(type=bpy.types.Object)
|
||||
|
||||
@@ -34,6 +34,8 @@ from blenderbim.bim.module.layer.data import LayersData
|
||||
|
||||
def mode_menu(self, context):
|
||||
row = self.layout.row(align=True)
|
||||
if context.scene.BIMGeometryProperties.mode == "EDIT":
|
||||
row.operator("bim.override_mode_set_object", icon="CANCEL", text="Discard Changes").should_save = False
|
||||
row.prop(context.scene.BIMGeometryProperties, "mode", text="", icon_value=blenderbim.bim.icons["IFC"].icon_id)
|
||||
|
||||
|
||||
|
||||
@@ -247,9 +247,11 @@ class IFCGIT_PT_revision_inspector(bpy.types.Panel):
|
||||
bl_idname = "IFCGIT_PT_revision_inspector"
|
||||
bl_label = "Git History"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "UI"
|
||||
bl_category = "BlenderBIM"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_parent_id = "IFCGIT_PT_panel"
|
||||
|
||||
def draw(self, context):
|
||||
|
||||
|
||||
@@ -30,7 +30,6 @@ classes = (
|
||||
operator.AssignMaterial,
|
||||
operator.AssignParameterizedProfile,
|
||||
operator.ContractMaterialCategory,
|
||||
operator.CopyMaterial,
|
||||
operator.DisableEditingAssignedMaterial,
|
||||
operator.DisableEditingMaterial,
|
||||
operator.DisableEditingMaterialSetItem,
|
||||
@@ -64,7 +63,6 @@ classes = (
|
||||
prop.BIMMaterialProperties,
|
||||
prop.BIMObjectMaterialProperties,
|
||||
ui.BIM_PT_materials,
|
||||
ui.BIM_PT_material,
|
||||
ui.BIM_PT_object_material,
|
||||
ui.BIM_UL_materials,
|
||||
)
|
||||
|
||||
@@ -36,31 +36,17 @@ class MaterialsData:
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {
|
||||
"total_materials": cls.total_materials(),
|
||||
"material_types": cls.material_types(),
|
||||
"profiles": cls.profiles(),
|
||||
"styles": cls.styles(),
|
||||
"contexts": cls.contexts(),
|
||||
"active_styles": cls.active_styles(),
|
||||
}
|
||||
cls.is_loaded = True
|
||||
cls.data["material_types"] = cls.material_types()
|
||||
cls.data["total_materials"] = cls.total_materials()
|
||||
cls.data["profiles"] = cls.profiles()
|
||||
cls.data["styles"] = cls.styles()
|
||||
cls.data["contexts"] = cls.contexts()
|
||||
cls.data["active_styles"] = cls.active_styles()
|
||||
|
||||
@classmethod
|
||||
def total_materials(cls):
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
return (
|
||||
len(tool.Ifc.get().by_type("IfcMaterial"))
|
||||
+ len(tool.Ifc.get().by_type("IfcMaterialLayerSet"))
|
||||
+ len(tool.Ifc.get().by_type("IfcMaterialList"))
|
||||
)
|
||||
return (
|
||||
len(tool.Ifc.get().by_type("IfcMaterial"))
|
||||
+ len(tool.Ifc.get().by_type("IfcMaterialConstituentSet"))
|
||||
+ len(tool.Ifc.get().by_type("IfcMaterialLayerSet"))
|
||||
+ len(tool.Ifc.get().by_type("IfcMaterialProfileSet"))
|
||||
+ len(tool.Ifc.get().by_type("IfcMaterialList"))
|
||||
)
|
||||
return len(tool.Ifc.get().by_type(bpy.context.scene.BIMMaterialProperties.material_type))
|
||||
|
||||
@classmethod
|
||||
def material_types(cls):
|
||||
|
||||
@@ -120,6 +120,8 @@ class AddMaterial(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty(name="Material Name")
|
||||
name: bpy.props.StringProperty(default="Default")
|
||||
category: bpy.props.StringProperty(default="")
|
||||
description: bpy.props.StringProperty(default="")
|
||||
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
@@ -127,10 +129,14 @@ class AddMaterial(bpy.types.Operator, tool.Ifc.Operator):
|
||||
def draw(self, context):
|
||||
row = self.layout
|
||||
row.prop(self, "name", text="Name")
|
||||
row = self.layout
|
||||
row.prop(self, "description", text="Description")
|
||||
row = self.layout
|
||||
row.prop(self, "category", text="Category")
|
||||
|
||||
def _execute(self, context):
|
||||
obj = bpy.data.materials.get(self.obj) if self.obj else None
|
||||
core.add_material(tool.Ifc, tool.Material, tool.Style, obj=obj, name=self.name)
|
||||
core.add_material(tool.Ifc, tool.Material, tool.Style, obj=obj, name=self.name, category=self.category, description=self.description)
|
||||
material_prop_purge()
|
||||
|
||||
|
||||
@@ -141,8 +147,19 @@ class DuplicateMaterial(bpy.types.Operator, tool.Ifc.Operator):
|
||||
material: bpy.props.IntProperty(name="Material ID")
|
||||
|
||||
def _execute(self, context):
|
||||
ifc_file = tool.Ifc.get()
|
||||
tool.Material.duplicate_material(ifc_file.by_id(self.material))
|
||||
material = tool.Ifc.get().by_id(self.material)
|
||||
new = tool.Ifc.run("material.copy_material", material=material)
|
||||
|
||||
blender_material = tool.Ifc.get_object(material)
|
||||
new_blender = blender_material.copy()
|
||||
new_blender.use_fake_user = True
|
||||
tool.Ifc.link(new, new_blender)
|
||||
|
||||
if not new.is_a("IfcMaterialList"):
|
||||
name = new[0] + " Copy"
|
||||
new[0] = name
|
||||
new_blender.name = name
|
||||
|
||||
material_prop_purge()
|
||||
bpy.ops.bim.load_materials()
|
||||
|
||||
@@ -185,9 +202,10 @@ class UnlinkMaterial(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.unlink_material"
|
||||
bl_label = "Unlink Material"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
material: bpy.props.IntProperty(name="Material ID")
|
||||
|
||||
def _execute(self, context):
|
||||
core.unlink_material(tool.Ifc, obj=context.active_object.active_material)
|
||||
core.unlink_material(tool.Ifc, obj=tool.Ifc.get_object(tool.Ifc.get().by_id(self.material)))
|
||||
|
||||
|
||||
class AssignMaterial(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -679,35 +697,6 @@ class EditMaterialSetItem(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bpy.ops.bim.disable_editing_material_set_item(obj=obj.name)
|
||||
|
||||
|
||||
class CopyMaterial(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.copy_material"
|
||||
bl_label = "Copy Material"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
blender_material = context.active_object.active_material
|
||||
material = tool.Ifc.get_entity(blender_material)
|
||||
|
||||
if tool.Ifc.has_changed_shading(blender_material):
|
||||
blenderbim.core.style.update_style_colours(tool.Ifc, tool.Style, obj=blender_material)
|
||||
|
||||
copied_material = ifcopenshell.api.run("material.copy_material", tool.Ifc.get(), material=material)
|
||||
copied_blender_material = blender_material.copy()
|
||||
copied_style = self.get_style(copied_material)
|
||||
tool.Ifc.link(copied_material, copied_blender_material)
|
||||
if copied_style:
|
||||
tool.Ifc.link(copied_style, copied_blender_material)
|
||||
context.active_object.active_material = copied_blender_material
|
||||
|
||||
def get_style(self, material):
|
||||
for material_representation in material.HasRepresentation:
|
||||
for representation in material_representation.Representations:
|
||||
for item in representation.Items:
|
||||
for style in item.Styles:
|
||||
if style.is_a("IfcSurfaceStyle"):
|
||||
return style
|
||||
|
||||
|
||||
class ExpandMaterialCategory(bpy.types.Operator):
|
||||
bl_idname = "bim.expand_material_category"
|
||||
bl_label = "Expand Material Category"
|
||||
|
||||
@@ -97,6 +97,10 @@ def get_material_types(self, context):
|
||||
return MaterialsData.data["material_types"]
|
||||
|
||||
|
||||
def update_material_type(self, context):
|
||||
MaterialsData.data["total_materials"] = MaterialsData.total_materials()
|
||||
|
||||
|
||||
def get_profiles(self, context):
|
||||
if not MaterialsData.is_loaded:
|
||||
MaterialsData.load()
|
||||
@@ -126,7 +130,7 @@ class Material(PropertyGroup):
|
||||
|
||||
class BIMMaterialProperties(PropertyGroup):
|
||||
is_editing: BoolProperty(name="Is Editing", default=False)
|
||||
material_type: EnumProperty(items=get_material_types, name="Material Type")
|
||||
material_type: EnumProperty(items=get_material_types, update=update_material_type, name="Material Type")
|
||||
materials: CollectionProperty(name="Materials", type=Material)
|
||||
active_material_index: IntProperty(name="Active Material Index")
|
||||
profiles: EnumProperty(items=get_profiles, name="Profiles")
|
||||
|
||||
@@ -45,7 +45,7 @@ class BIM_PT_materials(Panel):
|
||||
self.props = context.scene.BIMMaterialProperties
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="{} Materials".format(MaterialsData.data["total_materials"]), icon="NODE_MATERIAL")
|
||||
row.label(text=f"{MaterialsData.data['total_materials']} Materials", icon="NODE_MATERIAL")
|
||||
if self.props.is_editing:
|
||||
row.operator("bim.disable_editing_materials", text="", icon="CANCEL")
|
||||
else:
|
||||
@@ -69,6 +69,8 @@ class BIM_PT_materials(Panel):
|
||||
op.material = material.ifc_definition_id
|
||||
op = row.operator("bim.enable_editing_material_style", text="", icon="SHADING_RENDERED")
|
||||
op.material = material.ifc_definition_id
|
||||
op = row.operator("bim.unlink_material", icon="UNLINKED", text="")
|
||||
op.material = material.ifc_definition_id
|
||||
row.operator("bim.remove_material", text="", icon="X").material = material.ifc_definition_id
|
||||
|
||||
self.draw_editing_ui()
|
||||
@@ -114,36 +116,14 @@ class BIM_PT_materials(Panel):
|
||||
row.operator("bim.disable_editing_material", text="", icon="CANCEL")
|
||||
|
||||
|
||||
class BIM_PT_material(Panel):
|
||||
bl_label = "Material"
|
||||
bl_idname = "BIM_PT_material"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "material"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return IfcStore.get_file() and context.active_object and context.active_object.active_material
|
||||
|
||||
def draw(self, context):
|
||||
row = self.layout.row(align=True)
|
||||
material_id = context.active_object.active_material.BIMObjectProperties.ifc_definition_id
|
||||
if bool(material_id):
|
||||
row.operator("bim.remove_material", icon="X", text="Remove IFC Material").material = material_id
|
||||
row.operator("bim.copy_material", icon="DUPLICATE", text="")
|
||||
row.operator("bim.unlink_material", icon="UNLINKED", text="")
|
||||
else:
|
||||
op = row.operator("bim.add_material", icon="ADD", text="Create IFC Material")
|
||||
op.obj = context.active_object.active_material.name
|
||||
|
||||
|
||||
class BIM_PT_object_material(Panel):
|
||||
bl_label = "Object Material"
|
||||
bl_idname = "BIM_PT_object_material"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_tab_materials"
|
||||
bl_parent_id = "BIM_PT_tab_object_materials"
|
||||
bl_options = {"HIDE_HEADER"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
|
||||
@@ -23,6 +23,7 @@ import blenderbim.bim.handler
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.misc as core
|
||||
import blenderbim.core.geometry as core_geometry
|
||||
import blenderbim.core.root
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from mathutils import Vector, Matrix, Euler
|
||||
|
||||
@@ -134,6 +135,10 @@ class ResizeToStorey(bpy.types.Operator, Operator):
|
||||
class SplitAlongEdge(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.split_along_edge"
|
||||
bl_label = "Split Along Edge"
|
||||
bl_description = (
|
||||
"Active object is considered to be a cutting object."
|
||||
"Will unassign element from a type if type has a representation."
|
||||
)
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
@@ -144,16 +149,25 @@ class SplitAlongEdge(bpy.types.Operator, Operator):
|
||||
cutter = context.active_object
|
||||
objs = [o for o in context.selected_objects if o != cutter]
|
||||
|
||||
objs_to_cut = []
|
||||
# Splitting only works on meshes
|
||||
for obj in objs:
|
||||
# You cannot split meshes if the representation is mapped.
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element:
|
||||
relating_type = tool.Root.get_element_type(element)
|
||||
if relating_type and tool.Root.does_type_have_representations(relating_type):
|
||||
bpy.ops.bim.unassign_type(related_object=obj.name)
|
||||
if not element:
|
||||
continue
|
||||
|
||||
relating_type = tool.Root.get_element_type(element)
|
||||
if relating_type and tool.Root.does_type_have_representations(relating_type):
|
||||
bpy.ops.bim.unassign_type(related_object=obj.name)
|
||||
|
||||
# refresh representation
|
||||
representation = tool.Geometry.get_active_representation(obj)
|
||||
|
||||
# skip empty objects that might get in the way
|
||||
if not representation:
|
||||
continue
|
||||
|
||||
core_geometry.switch_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
@@ -168,11 +182,13 @@ class SplitAlongEdge(bpy.types.Operator, Operator):
|
||||
if not tool.Geometry.is_meshlike(representation):
|
||||
bpy.ops.bim.update_representation(obj=obj.name, ifc_representation_class="IfcTessellatedFaceSet")
|
||||
|
||||
new_objs = tool.Misc.split_objects_with_cutter(objs, cutter)
|
||||
objs_to_cut.append(obj)
|
||||
|
||||
new_objs = tool.Misc.split_objects_with_cutter(objs_to_cut, cutter)
|
||||
for obj in new_objs:
|
||||
blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=obj)
|
||||
bpy.ops.bim.update_representation(obj=obj.name)
|
||||
for obj in objs:
|
||||
for obj in objs_to_cut:
|
||||
bpy.ops.bim.update_representation(obj=obj.name)
|
||||
|
||||
representation = tool.Geometry.get_active_representation(obj)
|
||||
@@ -187,6 +203,8 @@ class SplitAlongEdge(bpy.types.Operator, Operator):
|
||||
apply_openings=True,
|
||||
)
|
||||
|
||||
self.report({"INFO"}, f"Splitting finished, {len(new_objs)} new objects created.")
|
||||
|
||||
|
||||
class GetConnectedSystemElements(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.get_connected_system_elements"
|
||||
|
||||
@@ -22,10 +22,11 @@ import bpy
|
||||
class BIM_PT_misc_utilities(bpy.types.Panel):
|
||||
bl_idname = "BIM_PT_misc_utilities"
|
||||
bl_label = "Miscellaneous"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "output"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "UI"
|
||||
bl_category = "BlenderBIM"
|
||||
bl_parent_id = "BIM_PT_tab_sandbox"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
@@ -193,7 +193,7 @@ class RemoveArray(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
pset = tool.Ifc.get().by_id(pset["id"])
|
||||
if len(data) == 1:
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=element, pset=pset)
|
||||
else:
|
||||
del data[self.item]
|
||||
data = tool.Ifc.get().createIfcText(json.dumps(data))
|
||||
|
||||
@@ -53,7 +53,7 @@ class AuthoringData:
|
||||
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()
|
||||
cls.data["predefined_type"] = cls.predefined_type()
|
||||
cls.data["predefined_type"] = cls.predefined_type() # only after .relating_type_id()
|
||||
cls.data["type_class"] = cls.type_class()
|
||||
|
||||
# only after .type_class()
|
||||
@@ -121,9 +121,10 @@ class AuthoringData:
|
||||
def type_thumbnail(cls):
|
||||
if not cls.data["relating_type_id"]:
|
||||
return 0
|
||||
if not tool.Blender.enum_property_has_valid_index(cls.props, "relating_type_id", cls.data["relating_type_id"]):
|
||||
relating_type_id = tool.Blender.get_enum_safe(cls.props, "relating_type_id")
|
||||
if relating_type_id is None:
|
||||
return 0
|
||||
element = tool.Ifc.get().by_id(int(cls.props.relating_type_id))
|
||||
element = tool.Ifc.get().by_id(int(relating_type_id))
|
||||
return cls.type_thumbnails.get(element.id(), None) or 0
|
||||
|
||||
@classmethod
|
||||
@@ -251,8 +252,8 @@ class AuthoringData:
|
||||
|
||||
@classmethod
|
||||
def predefined_type(cls):
|
||||
relating_type_id = cls.props.relating_type_id
|
||||
if not relating_type_id:
|
||||
relating_type_id = tool.Blender.get_enum_safe(cls.props, "relating_type_id")
|
||||
if relating_type_id is None:
|
||||
return
|
||||
relating_type = tool.Ifc.get().by_id(int(relating_type_id))
|
||||
if not hasattr(relating_type, "PredefinedType"):
|
||||
|
||||
@@ -22,20 +22,24 @@ import bmesh
|
||||
from bmesh.types import BMVert, BMFace
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.representation
|
||||
from ifcopenshell.util.shape_builder import V
|
||||
import blenderbim
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.geometry
|
||||
import blenderbim.core.geometry as core
|
||||
from blenderbim.bim.module.model.window import create_bm_window, create_bm_box, update_simple_openings
|
||||
import blenderbim.core.root
|
||||
from blenderbim.bim.module.model.window import create_bm_window, create_bm_box
|
||||
|
||||
from mathutils import Vector, Matrix
|
||||
from pprint import pprint
|
||||
|
||||
import json
|
||||
import collections
|
||||
|
||||
|
||||
def update_door_modifier_representation(context, obj):
|
||||
def update_door_modifier_representation(context: bpy.types.Context, obj: bpy.types.Object) -> None:
|
||||
props = obj.BIMDoorProperties
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
ifc_file = tool.Ifc.get()
|
||||
@@ -159,7 +163,7 @@ def update_door_modifier_representation(context, obj):
|
||||
occurrence.OverallWidth = props.overall_width / si_conversion
|
||||
occurrence.OverallHeight = props.overall_height / si_conversion
|
||||
|
||||
update_simple_openings(element, props.overall_width / si_conversion, props.overall_height / si_conversion)
|
||||
tool.Model.update_simple_openings(element)
|
||||
|
||||
|
||||
# TODO: move it out to tools
|
||||
@@ -644,6 +648,6 @@ class RemoveDoor(bpy.types.Operator, tool.Ifc.Operator):
|
||||
obj.BIMDoorProperties.is_editing = False
|
||||
|
||||
pset = tool.Pset.get_element_pset(element, "BBIM_Door")
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=element, pset=pset)
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -44,6 +44,7 @@ from bpy.types import SpaceView3D
|
||||
from bpy.props import FloatProperty
|
||||
from bpy_extras.object_utils import AddObjectHelper, object_data_add
|
||||
from gpu_extras.batch import batch_for_shader
|
||||
from typing import Union, Optional, Any
|
||||
|
||||
|
||||
class AddFilledOpening(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -59,9 +60,15 @@ class AddFilledOpening(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
|
||||
class FilledOpeningGenerator:
|
||||
def generate(self, filling_obj, voided_obj, target=None):
|
||||
def generate(
|
||||
self,
|
||||
filling_obj: Union[bpy.types.Object, None],
|
||||
voided_obj: Union[bpy.types.Object, None],
|
||||
target: Optional[Vector] = None,
|
||||
) -> None:
|
||||
props = bpy.context.scene.BIMModelProperties
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
opening_thickness_si = 0.0
|
||||
|
||||
filling = tool.Ifc.get_entity(filling_obj)
|
||||
element = tool.Ifc.get_entity(voided_obj)
|
||||
@@ -92,6 +99,7 @@ class FilledOpeningGenerator:
|
||||
# In this prototype, we assume openings are only added to axis-based elements
|
||||
layers = tool.Model.get_material_layer_parameters(element)
|
||||
if layers["layer_set_direction"] == "AXIS2":
|
||||
opening_thickness_si = layers["thickness"] * 2
|
||||
axis = tool.Model.get_wall_axis(voided_obj, layers=layers)["base"]
|
||||
new_matrix = voided_obj.matrix_world.copy()
|
||||
point_on_axis = tool.Cad.point_on_edge(target, axis)
|
||||
@@ -122,44 +130,37 @@ class FilledOpeningGenerator:
|
||||
|
||||
existing_opening_occurrence = self.get_existing_opening_occurrence_if_any(filling)
|
||||
|
||||
if existing_opening_occurrence:
|
||||
opening = ifcopenshell.api.run(
|
||||
"root.create_entity",
|
||||
tool.Ifc.get(),
|
||||
ifc_class="IfcOpeningElement",
|
||||
predefined_type="OPENING",
|
||||
name="Opening",
|
||||
)
|
||||
ifcopenshell.api.run(
|
||||
"geometry.edit_object_placement", tool.Ifc.get(), product=opening, matrix=filling_obj.matrix_world
|
||||
)
|
||||
opening = ifcopenshell.api.run(
|
||||
"root.create_entity",
|
||||
tool.Ifc.get(),
|
||||
ifc_class="IfcOpeningElement",
|
||||
predefined_type="OPENING",
|
||||
name="Opening",
|
||||
)
|
||||
ifcopenshell.api.run(
|
||||
"geometry.edit_object_placement",
|
||||
tool.Ifc.get(),
|
||||
product=opening,
|
||||
matrix=np.array(filling_obj.matrix_world),
|
||||
is_si=True,
|
||||
)
|
||||
|
||||
if existing_opening_occurrence:
|
||||
representation = ifcopenshell.util.representation.get_representation(
|
||||
existing_opening_occurrence, "Model", "Body", "MODEL_VIEW"
|
||||
)
|
||||
representation = ifcopenshell.util.representation.resolve_representation(representation)
|
||||
mapped_representation = ifcopenshell.api.run(
|
||||
"geometry.map_representation", tool.Ifc.get(), representation=representation
|
||||
)
|
||||
ifcopenshell.api.run(
|
||||
"geometry.assign_representation", tool.Ifc.get(), product=opening, representation=mapped_representation
|
||||
)
|
||||
else:
|
||||
representation = self.generate_opening_from_filling(filling, filling_obj)
|
||||
opening = ifcopenshell.api.run(
|
||||
"root.create_entity", tool.Ifc.get(), ifc_class="IfcOpeningElement", predefined_type="OPENING"
|
||||
representation = self.generate_opening_from_filling(
|
||||
filling, filling_obj, opening_thickness_si=opening_thickness_si
|
||||
)
|
||||
|
||||
matrix = np.array(filling_obj.matrix_world)
|
||||
ifcopenshell.api.run(
|
||||
"geometry.edit_object_placement", tool.Ifc.get(), product=opening, matrix=matrix, is_si=True
|
||||
)
|
||||
mapped_representation = ifcopenshell.api.run(
|
||||
"geometry.map_representation", tool.Ifc.get(), representation=representation
|
||||
)
|
||||
ifcopenshell.api.run(
|
||||
"geometry.assign_representation", tool.Ifc.get(), product=opening, representation=mapped_representation
|
||||
)
|
||||
mapped_representation = ifcopenshell.api.run(
|
||||
"geometry.map_representation", tool.Ifc.get(), representation=representation
|
||||
)
|
||||
ifcopenshell.api.run(
|
||||
"geometry.assign_representation", tool.Ifc.get(), product=opening, representation=mapped_representation
|
||||
)
|
||||
|
||||
ifcopenshell.api.run("void.add_opening", tool.Ifc.get(), opening=opening, element=element)
|
||||
ifcopenshell.api.run("void.add_filling", tool.Ifc.get(), opening=opening, element=filling)
|
||||
@@ -187,7 +188,7 @@ class FilledOpeningGenerator:
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
def regenerate_from_type(self, usecase_path, ifc_file, settings):
|
||||
def regenerate_from_type(self, usecase_path: str, ifc_file: ifcopenshell.file, settings: dict[str, Any]) -> None:
|
||||
relating_type = settings["relating_type"]
|
||||
|
||||
for related_object in settings["related_objects"]:
|
||||
@@ -252,10 +253,15 @@ class FilledOpeningGenerator:
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
def generate_opening_from_filling(self, filling, filling_obj):
|
||||
def generate_opening_from_filling(
|
||||
self,
|
||||
filling: ifcopenshell.entity_instance,
|
||||
filling_obj: bpy.types.Object,
|
||||
opening_thickness_si: float = 0.0,
|
||||
) -> ifcopenshell.entity_instance:
|
||||
# Since openings are reused later, we give a default thickness of 1.2m
|
||||
# which should cover the majority of curved, or super thick walls.
|
||||
thickness = 1.2
|
||||
thickness = max(1.2, opening_thickness_si)
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
shape_builder = ifcopenshell.util.shape_builder.ShapeBuilder(tool.Ifc.get())
|
||||
|
||||
@@ -346,7 +352,9 @@ class FilledOpeningGenerator:
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_existing_opening_occurrence_if_any(self, filling):
|
||||
def get_existing_opening_occurrence_if_any(
|
||||
self, filling: ifcopenshell.entity_instance
|
||||
) -> Union[ifcopenshell.entity_instance, None]:
|
||||
filling_type = ifcopenshell.util.element.get_type(filling)
|
||||
if filling_type:
|
||||
filling_occurrences = ifcopenshell.util.element.get_types(filling_type)
|
||||
|
||||
@@ -138,7 +138,10 @@ class AddConstrTypeInstance(bpy.types.Operator):
|
||||
if not relating_type_id:
|
||||
return {"FINISHED"}
|
||||
|
||||
if self.from_invoke:
|
||||
# Check relating_type_id enum_items since it's possible
|
||||
# that we're adding e.g. IfcRoofType being in a Slab Tool
|
||||
# and roof type id won't be present in the relating_type_id enum.
|
||||
if self.from_invoke and str(self.relating_type_id) in AuthoringData.data["relating_type_id"]:
|
||||
props.relating_type_id = str(self.relating_type_id)
|
||||
|
||||
relating_type = tool.Ifc.get().by_id(int(relating_type_id))
|
||||
|
||||
@@ -94,7 +94,6 @@ class DumbProfileGenerator:
|
||||
obj=obj,
|
||||
ifc_class=ifc_class,
|
||||
should_add_representation=False,
|
||||
context=self.body_context,
|
||||
)
|
||||
ifcopenshell.api.run("type.assign_type", self.file, related_objects=[element], relating_type=self.relating_type)
|
||||
|
||||
@@ -931,13 +930,11 @@ class PatchNonParametricMepSegment(bpy.types.Operator, tool.Ifc.Operator):
|
||||
return context.active_object
|
||||
|
||||
def _execute(self, context):
|
||||
styles = tool.Geometry.get_styles(context.active_object)
|
||||
blenderbim.core.material.patch_non_parametric_mep_segment(
|
||||
tool.Ifc, tool.Material, tool.Profile, obj=context.active_object
|
||||
)
|
||||
bpy.ops.bim.enable_editing_extrusion_axis()
|
||||
bpy.ops.bim.edit_extrusion_axis()
|
||||
styles = tool.Geometry.get_styles(context.active_object)
|
||||
|
||||
|
||||
class EnableEditingExtrusionAxis(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
@@ -60,7 +60,7 @@ def update_ifc_class(self, context):
|
||||
bpy.ops.bim.load_type_thumbnails(ifc_class=self.ifc_class)
|
||||
AuthoringData.data["relating_type_id"] = AuthoringData.relating_type_id()
|
||||
AuthoringData.data["type_thumbnail"] = AuthoringData.type_thumbnail()
|
||||
if not tool.Blender.enum_property_has_valid_index(self, "relating_type_id", AuthoringData.data["relating_type_id"]):
|
||||
if tool.Blender.get_enum_safe(self, "relating_type_id") is None:
|
||||
self["relating_type_id"] = 0
|
||||
|
||||
|
||||
|
||||
@@ -20,8 +20,12 @@
|
||||
import bpy
|
||||
import bmesh
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.representation
|
||||
import ifcopenshell.util.unit
|
||||
from ifcopenshell.util.shape_builder import V
|
||||
import blenderbim
|
||||
import blenderbim.core.root
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.model.door import bm_sort_out_geom
|
||||
@@ -535,5 +539,5 @@ class RemoveRailing(bpy.types.Operator, tool.Ifc.Operator):
|
||||
obj.BIMRailingProperties.is_editing = False
|
||||
|
||||
pset = tool.Pset.get_element_pset(element, "BBIM_Railing")
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=element, pset=pset)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -20,7 +20,11 @@ import bpy
|
||||
import bmesh
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.representation
|
||||
import ifcopenshell.util.unit
|
||||
import blenderbim
|
||||
import blenderbim.core.root
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim.helper import convert_property_group_from_si
|
||||
from blenderbim.bim.module.model.door import bm_sort_out_geom
|
||||
@@ -757,7 +761,7 @@ class RemoveRoof(bpy.types.Operator, tool.Ifc.Operator):
|
||||
obj.BIMRoofProperties.is_editing = False
|
||||
|
||||
pset = tool.Pset.get_element_pset(element, "BBIM_Roof")
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=element, pset=pset)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
|
||||
@@ -176,7 +176,6 @@ class DumbSlabGenerator:
|
||||
obj=obj,
|
||||
ifc_class=ifc_class,
|
||||
should_add_representation=False,
|
||||
context=self.body_context,
|
||||
)
|
||||
ifcopenshell.api.run("type.assign_type", self.file, related_objects=[element], relating_type=self.relating_type)
|
||||
|
||||
|
||||
@@ -20,7 +20,11 @@ import bpy
|
||||
import json
|
||||
import bmesh
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.representation
|
||||
import ifcopenshell.util.unit
|
||||
import blenderbim
|
||||
import blenderbim.core.root
|
||||
import blenderbim.tool as tool
|
||||
from mathutils import Vector
|
||||
from bmesh.types import BMVert
|
||||
@@ -217,7 +221,6 @@ class BIM_OT_add_clever_stair(bpy.types.Operator, tool.Ifc.Operator):
|
||||
collection = context.view_layer.active_layer_collection.collection
|
||||
collection.objects.link(obj)
|
||||
|
||||
body_context = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW")
|
||||
element = blenderbim.core.root.assign_class(
|
||||
tool.Ifc,
|
||||
tool.Collector,
|
||||
@@ -225,7 +228,6 @@ class BIM_OT_add_clever_stair(bpy.types.Operator, tool.Ifc.Operator):
|
||||
obj=obj,
|
||||
ifc_class="IfcStairFlight",
|
||||
should_add_representation=False,
|
||||
context=body_context,
|
||||
)
|
||||
if tool.Ifc.get_schema() != "IFC2X3":
|
||||
element.PredefinedType = "STRAIGHT"
|
||||
@@ -337,6 +339,6 @@ class RemoveStair(bpy.types.Operator, tool.Ifc.Operator):
|
||||
obj.BIMStairProperties.is_editing = False
|
||||
|
||||
pset = tool.Pset.get_element_pset(element, "BBIM_Stair")
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=element, pset=pset)
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -607,7 +607,6 @@ class DumbWallGenerator:
|
||||
obj=obj,
|
||||
ifc_class=ifc_class,
|
||||
should_add_representation=False,
|
||||
context=self.body_context,
|
||||
)
|
||||
ifcopenshell.api.run("type.assign_type", self.file, related_objects=[element], relating_type=self.relating_type)
|
||||
if self.axis_context:
|
||||
|
||||
@@ -26,66 +26,16 @@ import blenderbim.tool as tool
|
||||
import blenderbim.core.root
|
||||
import blenderbim.core.geometry
|
||||
from ifcopenshell.api.geometry.add_window_representation import DEFAULT_PANEL_SCHEMAS
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.representation
|
||||
import ifcopenshell.util.shape_builder
|
||||
import ifcopenshell.util.unit
|
||||
from ifcopenshell.util.shape_builder import V
|
||||
from bmesh.types import BMVert
|
||||
from mathutils import Vector
|
||||
|
||||
|
||||
# TODO: move to some utils helpers/tool module
|
||||
def update_simple_openings(element, opening_width, opening_height):
|
||||
ifc_file = tool.Ifc.get()
|
||||
fillings = tool.Ifc.get_all_element_occurrences(element)
|
||||
|
||||
voided_objs = set()
|
||||
has_replaced_opening_representation = False
|
||||
for filling in fillings:
|
||||
if not filling.FillsVoids:
|
||||
continue
|
||||
|
||||
opening = filling.FillsVoids[0].RelatingOpeningElement
|
||||
voided_obj = tool.Ifc.get_object(opening.VoidsElements[0].RelatingBuildingElement)
|
||||
voided_objs.add(voided_obj)
|
||||
|
||||
# we assume that the same element type (e.g. window)
|
||||
# will be used only for voiding objects of the same thickness (by y-dimension)
|
||||
# (e.g. all walls window's attached to will share the same thickness)
|
||||
# If that's not the case it will some linings will be too thick or too thin
|
||||
if has_replaced_opening_representation:
|
||||
continue
|
||||
|
||||
old_representation = ifcopenshell.util.representation.get_representation(opening, "Model", "Body", "MODEL_VIEW")
|
||||
old_representation = tool.Geometry.resolve_mapped_representation(old_representation)
|
||||
ifcopenshell.api.run(
|
||||
"geometry.unassign_representation", ifc_file, product=opening, representation=old_representation
|
||||
)
|
||||
|
||||
thickness = voided_obj.dimensions[1] + 0.1 + 0.1
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
|
||||
shape_builder = ifcopenshell.util.shape_builder.ShapeBuilder(ifc_file)
|
||||
context = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW")
|
||||
|
||||
extrusion = shape_builder.extrude(
|
||||
shape_builder.rectangle(size=Vector([opening_width, 0.0, opening_height]).xz),
|
||||
magnitude=thickness / unit_scale,
|
||||
position=Vector([0.0, -0.1 / unit_scale, 0.0]),
|
||||
**shape_builder.extrude_kwargs("Y")
|
||||
)
|
||||
|
||||
new_representation = shape_builder.get_representation(context, extrusion)
|
||||
|
||||
for inverse in ifc_file.get_inverse(old_representation):
|
||||
ifcopenshell.util.element.replace_attribute(inverse, old_representation, new_representation)
|
||||
|
||||
ifcopenshell.api.run("geometry.remove_representation", ifc_file, representation=old_representation)
|
||||
|
||||
has_replaced_opening_representation = True
|
||||
|
||||
tool.Model.reload_body_representation(voided_objs)
|
||||
if fillings:
|
||||
with bpy.context.temp_override(selected_objects=[tool.Ifc.get_object(f) for f in fillings]):
|
||||
bpy.ops.bim.recalculate_fill()
|
||||
|
||||
|
||||
def update_window_modifier_representation(context, obj):
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
props = obj.BIMWindowProperties
|
||||
@@ -175,7 +125,7 @@ def update_window_modifier_representation(context, obj):
|
||||
occurrence.OverallWidth = props.overall_width / si_conversion
|
||||
occurrence.OverallHeight = props.overall_height / si_conversion
|
||||
|
||||
update_simple_openings(element, props.overall_width / si_conversion, props.overall_height / si_conversion)
|
||||
tool.Model.update_simple_openings(element)
|
||||
|
||||
|
||||
def create_bm_window_frame(bm, size: Vector, thickness: list, position: Vector = V(0, 0, 0).freeze()):
|
||||
@@ -590,6 +540,6 @@ class RemoveWindow(bpy.types.Operator, tool.Ifc.Operator):
|
||||
obj.BIMWindowProperties.is_editing = False
|
||||
|
||||
pset = tool.Pset.get_element_pset(element, "BBIM_Window")
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=element, pset=pset)
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -175,7 +175,7 @@ class EnableEditingArbitraryProfile(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
def disable_editing_arbitrary_profile(context):
|
||||
obj = context.active_object
|
||||
if obj and obj.data and obj.data.BIMMeshProperties.subshape_type == "PROFILE":
|
||||
if obj and obj.type == "MESH" and obj.data and obj.data.BIMMeshProperties.subshape_type == "PROFILE":
|
||||
ProfileDecorator.uninstall()
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
profile_mesh = obj.data
|
||||
|
||||
@@ -36,8 +36,8 @@ classes = (
|
||||
operator.EnableCulling,
|
||||
operator.EnableEditingHeader,
|
||||
operator.ExportIFC,
|
||||
operator.ExportIFCDeprecated,
|
||||
operator.FlipClippingPlane,
|
||||
operator.ImportIFC,
|
||||
operator.LinkIfc,
|
||||
operator.LoadLink,
|
||||
operator.LoadLinkedProject,
|
||||
@@ -76,6 +76,11 @@ classes = (
|
||||
gizmo.ClippingPlane,
|
||||
)
|
||||
|
||||
if bpy.app.version >= (4, 1, 0):
|
||||
classes += (
|
||||
operator.IFCFileHandlerOperator,
|
||||
operator.BIM_FH_import_ifc,
|
||||
)
|
||||
|
||||
addon_keymaps = []
|
||||
|
||||
@@ -97,7 +102,7 @@ def register():
|
||||
addon_keymaps.append((km, kmi))
|
||||
|
||||
km = wm.keyconfigs.addon.keymaps.new(name="Window", space_type="EMPTY")
|
||||
kmi = km.keymap_items.new("export_ifc.bim", "S", "PRESS", ctrl=True)
|
||||
kmi = km.keymap_items.new("bim.save_project", "S", "PRESS", ctrl=True)
|
||||
kmi.properties.should_save_as = False
|
||||
addon_keymaps.append((km, kmi))
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import bpy
|
||||
import time
|
||||
import logging
|
||||
import tempfile
|
||||
import traceback
|
||||
import subprocess
|
||||
import numpy as np
|
||||
import ifcopenshell
|
||||
@@ -674,19 +675,23 @@ class LoadProject(bpy.types.Operator, IFCFileSelector):
|
||||
return self.finish_loading_project(context)
|
||||
|
||||
def finish_loading_project(self, context):
|
||||
if not self.is_existing_ifc_file():
|
||||
return {"FINISHED"}
|
||||
try:
|
||||
if not self.is_existing_ifc_file():
|
||||
return {"FINISHED"}
|
||||
|
||||
if tool.Blender.is_default_scene():
|
||||
for obj in bpy.data.objects:
|
||||
bpy.data.objects.remove(obj)
|
||||
if tool.Blender.is_default_scene():
|
||||
for obj in bpy.data.objects:
|
||||
bpy.data.objects.remove(obj)
|
||||
|
||||
context.scene.BIMProperties.ifc_file = self.get_filepath()
|
||||
context.scene.BIMProjectProperties.is_loading = True
|
||||
context.scene.BIMProjectProperties.total_elements = len(tool.Ifc.get().by_type("IfcElement"))
|
||||
tool.Blender.register_toolbar()
|
||||
if not self.is_advanced:
|
||||
bpy.ops.bim.load_project_elements()
|
||||
context.scene.BIMProperties.ifc_file = self.get_filepath()
|
||||
context.scene.BIMProjectProperties.is_loading = True
|
||||
context.scene.BIMProjectProperties.total_elements = len(tool.Ifc.get().by_type("IfcElement"))
|
||||
tool.Blender.register_toolbar()
|
||||
if not self.is_advanced:
|
||||
bpy.ops.bim.load_project_elements()
|
||||
except:
|
||||
blenderbim.last_error = traceback.format_exc()
|
||||
raise
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
@@ -1101,8 +1106,8 @@ class ToggleLinkVisibility(bpy.types.Operator):
|
||||
]
|
||||
|
||||
|
||||
class ExportIFC(bpy.types.Operator):
|
||||
bl_idname = "export_ifc.bim"
|
||||
class ExportIFCBase:
|
||||
bl_idname = "bim.save_project"
|
||||
bl_label = "Save IFC"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filename_ext = ".ifc"
|
||||
@@ -1219,14 +1224,20 @@ class ExportIFC(bpy.types.Operator):
|
||||
return "Save the IFC file. Will save both .IFC/.BLEND files if synced together"
|
||||
|
||||
|
||||
class ImportIFC(bpy.types.Operator):
|
||||
bl_idname = "import_ifc.bim"
|
||||
bl_label = "Import IFC"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
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):
|
||||
bpy.ops.bim.load_project("INVOKE_DEFAULT")
|
||||
return {"FINISHED"}
|
||||
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):
|
||||
@@ -2074,3 +2085,46 @@ class BIM_OT_load_clipping_planes(bpy.types.Operator):
|
||||
obj.location = values["location"]
|
||||
obj.rotation_euler = values["rotation"]
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
if bpy.app.version >= (4, 1, 0):
|
||||
|
||||
class IFCFileHandlerOperator(bpy.types.Operator):
|
||||
bl_idname = "bim.load_project_file_handler"
|
||||
bl_label = "Import .ifc file"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
directory: bpy.props.StringProperty(subtype="FILE_PATH", options={"SKIP_SAVE", "HIDDEN"})
|
||||
files: bpy.props.CollectionProperty(type=bpy.types.OperatorFileListElement, options={"SKIP_SAVE", "HIDDEN"})
|
||||
|
||||
def invoke(self, context, event):
|
||||
# Keeping code in .invoke() as we'll probably add some
|
||||
# popup windows later.
|
||||
|
||||
# `files` contain only .ifc files.
|
||||
filepath = Path(self.directory)
|
||||
# If user is just drag'n'dropping a single file -> load it as a new project,
|
||||
# if they're holding ALT -> link the file/files to the current project.
|
||||
if event.alt:
|
||||
# Passing self.files directly results in TypeError.
|
||||
serialized_files = [{"name": f.name} for f in self.files]
|
||||
return bpy.ops.bim.link_ifc(directory=self.directory, files=serialized_files)
|
||||
else:
|
||||
if len(self.files) == 1:
|
||||
return bpy.ops.bim.load_project(filepath=(filepath / self.files[0].name).as_posix())
|
||||
else:
|
||||
self.report(
|
||||
{"INFO"},
|
||||
"To link multiple IFC files hold ALT while drag'n'dropping them.",
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
class BIM_FH_import_ifc(bpy.types.FileHandler):
|
||||
bl_label = "IFC File Handler"
|
||||
bl_import_operator = IFCFileHandlerOperator.bl_idname
|
||||
bl_file_extensions = ".ifc"
|
||||
|
||||
# FileHandler won't work without poll_drop defined.
|
||||
@classmethod
|
||||
def poll_drop(cls, context):
|
||||
return True
|
||||
|
||||
@@ -68,9 +68,9 @@ def file_menu(self, context):
|
||||
op = self.layout.operator("bim.load_project", text="Open IFC Project", icon="FILEBROWSER")
|
||||
op.should_start_fresh_session = True
|
||||
self.layout.separator()
|
||||
op = self.layout.operator("export_ifc.bim", icon="FILE_TICK", text="Save IFC Project")
|
||||
op = self.layout.operator("bim.save_project", icon="FILE_TICK", text="Save IFC Project")
|
||||
op.should_save_as = False
|
||||
op = self.layout.operator("export_ifc.bim", text="Save IFC Project As...")
|
||||
op = self.layout.operator("bim.save_project", text="Save IFC Project As...")
|
||||
op.should_save_as = True
|
||||
self.layout.separator()
|
||||
self.layout.operator("bim.revert_project")
|
||||
|
||||
@@ -20,6 +20,7 @@ import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.AddProposedProp,
|
||||
operator.AddPset,
|
||||
operator.AddQto,
|
||||
operator.CopyPropertyToSelection,
|
||||
@@ -39,14 +40,6 @@ classes = (
|
||||
prop.IfcPropertyEnumeratedValue,
|
||||
prop.IfcProperty,
|
||||
prop.PsetProperties,
|
||||
prop.MaterialPsetProperties,
|
||||
prop.MaterialSetPsetProperties,
|
||||
prop.MaterialSetItemPsetProperties,
|
||||
prop.TaskPsetProperties,
|
||||
prop.ResourcePsetProperties,
|
||||
prop.GroupPsetProperties,
|
||||
prop.ProfilePsetProperties,
|
||||
prop.WorkSchedulePsetProperties,
|
||||
prop.RenameProperties,
|
||||
prop.AddEditProperties,
|
||||
prop.DeletePsets,
|
||||
@@ -71,14 +64,14 @@ classes = (
|
||||
|
||||
def register():
|
||||
bpy.types.Object.PsetProperties = bpy.props.PointerProperty(type=prop.PsetProperties)
|
||||
bpy.types.Object.MaterialSetPsetProperties = bpy.props.PointerProperty(type=prop.MaterialSetPsetProperties)
|
||||
bpy.types.Object.MaterialSetItemPsetProperties = bpy.props.PointerProperty(type=prop.MaterialSetItemPsetProperties)
|
||||
bpy.types.Material.PsetProperties = bpy.props.PointerProperty(type=prop.MaterialPsetProperties)
|
||||
bpy.types.Scene.TaskPsetProperties = bpy.props.PointerProperty(type=prop.TaskPsetProperties)
|
||||
bpy.types.Scene.ResourcePsetProperties = bpy.props.PointerProperty(type=prop.ResourcePsetProperties)
|
||||
bpy.types.Scene.GroupPsetProperties = bpy.props.PointerProperty(type=prop.GroupPsetProperties)
|
||||
bpy.types.Scene.ProfilePsetProperties = bpy.props.PointerProperty(type=prop.ProfilePsetProperties)
|
||||
bpy.types.Scene.WorkSchedulePsetProperties = bpy.props.PointerProperty(type=prop.WorkSchedulePsetProperties)
|
||||
bpy.types.Scene.MaterialPsetProperties = bpy.props.PointerProperty(type=prop.PsetProperties)
|
||||
bpy.types.Object.MaterialSetPsetProperties = bpy.props.PointerProperty(type=prop.PsetProperties)
|
||||
bpy.types.Object.MaterialSetItemPsetProperties = bpy.props.PointerProperty(type=prop.PsetProperties)
|
||||
bpy.types.Scene.TaskPsetProperties = bpy.props.PointerProperty(type=prop.PsetProperties)
|
||||
bpy.types.Scene.ResourcePsetProperties = bpy.props.PointerProperty(type=prop.PsetProperties)
|
||||
bpy.types.Scene.GroupPsetProperties = bpy.props.PointerProperty(type=prop.PsetProperties)
|
||||
bpy.types.Scene.ProfilePsetProperties = bpy.props.PointerProperty(type=prop.PsetProperties)
|
||||
bpy.types.Scene.WorkSchedulePsetProperties = bpy.props.PointerProperty(type=prop.PsetProperties)
|
||||
bpy.types.Scene.RenameProperties = bpy.props.CollectionProperty(type=prop.RenameProperties)
|
||||
bpy.types.Scene.AddEditProperties = bpy.props.CollectionProperty(type=prop.AddEditProperties)
|
||||
bpy.types.Scene.DeletePsets = bpy.props.CollectionProperty(type=prop.DeletePsets)
|
||||
@@ -86,7 +79,9 @@ def register():
|
||||
|
||||
def unregister():
|
||||
del bpy.types.Object.PsetProperties
|
||||
del bpy.types.Material.PsetProperties
|
||||
del bpy.types.Scene.MaterialPsetProperties
|
||||
del bpy.types.Object.MaterialSetPsetProperties
|
||||
del bpy.types.Object.MaterialSetItemPsetProperties
|
||||
del bpy.types.Scene.TaskPsetProperties
|
||||
del bpy.types.Scene.ResourcePsetProperties
|
||||
del bpy.types.Scene.GroupPsetProperties
|
||||
|
||||
@@ -61,6 +61,15 @@ class Data:
|
||||
)
|
||||
return sorted(results, key=lambda v: v["Name"])
|
||||
|
||||
@classmethod
|
||||
def format_pset_enum(cls, psets):
|
||||
enum_items = []
|
||||
version = tool.Ifc.get_schema()
|
||||
for pset in psets:
|
||||
doc = ifcopenshell.util.doc.get_property_set_doc(version, pset.Name) or {}
|
||||
enum_items.append((pset.Name, pset.Name, doc.get("description", "")))
|
||||
return enum_items
|
||||
|
||||
|
||||
class ObjectPsetsData(Data):
|
||||
data = {}
|
||||
@@ -114,15 +123,6 @@ class ObjectPsetsData(Data):
|
||||
)
|
||||
return cls.format_pset_enum(qtos)
|
||||
|
||||
@classmethod
|
||||
def format_pset_enum(cls, psets):
|
||||
enum_items = []
|
||||
version = tool.Ifc.get_schema()
|
||||
for pset in psets:
|
||||
doc = ifcopenshell.util.doc.get_property_set_doc(version, pset.Name) or {}
|
||||
enum_items.append((pset.Name, pset.Name, doc.get("description", "")))
|
||||
return enum_items
|
||||
|
||||
|
||||
class ObjectQtosData(Data):
|
||||
data = {}
|
||||
@@ -157,12 +157,34 @@ class MaterialPsetsData(Data):
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
ifc_definition_id = None
|
||||
props = bpy.context.scene.BIMMaterialProperties
|
||||
if props.materials and props.active_material_index < len(props.materials):
|
||||
ifc_definition_id = props.materials[props.active_material_index].ifc_definition_id
|
||||
|
||||
cls.data = {
|
||||
"ifc_definition_id": bpy.context.active_object.active_material.BIMObjectProperties.ifc_definition_id,
|
||||
"psets": cls.psetqtos(tool.Ifc.get_entity(bpy.context.active_object.active_material)),
|
||||
"ifc_definition_id": ifc_definition_id,
|
||||
"psets": cls.psetqtos(tool.Ifc.get().by_id(ifc_definition_id)),
|
||||
"pset_name": cls.pset_name(),
|
||||
}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def pset_name(cls):
|
||||
props = bpy.context.scene.BIMMaterialProperties
|
||||
if props.materials and props.active_material_index < len(props.materials):
|
||||
material = props.materials[props.active_material_index]
|
||||
if material.ifc_definition_id:
|
||||
material = tool.Ifc.get().by_id(material.ifc_definition_id)
|
||||
category = getattr(material, "Category", None) or None
|
||||
psets = blenderbim.bim.schema.ifc.psetqto.get_applicable("IfcMaterial", category, pset_only=True)
|
||||
psetnames = cls.format_pset_enum(psets)
|
||||
assigned_names = ifcopenshell.util.element.get_psets(
|
||||
material, psets_only=True, should_inherit=False
|
||||
).keys()
|
||||
return [p for p in psetnames if p[0] not in assigned_names]
|
||||
return []
|
||||
|
||||
|
||||
class MaterialSetPsetsData(Data):
|
||||
data = {}
|
||||
|
||||
@@ -31,7 +31,6 @@ import blenderbim.core.qto as QtoCore
|
||||
import blenderbim.bim.module.pset.data
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.pset.qto_calculator import QtoCalculator
|
||||
from blenderbim.bim.module.pset.calc_quantity_function_mapper import mapper
|
||||
|
||||
|
||||
class Operator:
|
||||
@@ -41,35 +40,15 @@ class Operator:
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
def get_pset_props(context, obj, obj_type):
|
||||
if obj_type == "Object":
|
||||
return bpy.data.objects.get(obj).PsetProperties
|
||||
elif obj_type == "Material":
|
||||
return bpy.data.materials.get(obj).PsetProperties
|
||||
elif obj_type == "MaterialSet":
|
||||
return bpy.data.objects.get(obj).MaterialSetPsetProperties
|
||||
elif obj_type == "MaterialSetItem":
|
||||
return bpy.data.objects.get(obj).MaterialSetItemPsetProperties
|
||||
elif obj_type == "Task":
|
||||
return context.scene.TaskPsetProperties
|
||||
elif obj_type == "Resource":
|
||||
return context.scene.ResourcePsetProperties
|
||||
elif obj_type == "Profile":
|
||||
return context.scene.ProfilePsetProperties
|
||||
elif obj_type == "WorkSchedule":
|
||||
return context.scene.WorkSchedulePsetProperties
|
||||
elif obj_type == "Group":
|
||||
return context.scene.GroupPsetProperties
|
||||
|
||||
class TogglePsetExpansion(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.toggle_pset_expansion"
|
||||
bl_label = "Toggle Pset Expansion"
|
||||
pset_id: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
blenderbim.bim.module.pset.data.is_expanded[
|
||||
self.pset_id
|
||||
] = not blenderbim.bim.module.pset.data.is_expanded.setdefault(self.pset_id, True)
|
||||
blenderbim.bim.module.pset.data.is_expanded[self.pset_id] = (
|
||||
not blenderbim.bim.module.pset.data.is_expanded.setdefault(self.pset_id, True)
|
||||
)
|
||||
|
||||
|
||||
class EnablePsetEditing(bpy.types.Operator):
|
||||
@@ -83,160 +62,15 @@ class EnablePsetEditing(bpy.types.Operator):
|
||||
obj_type: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
self.props = get_pset_props(context, self.obj, self.obj_type)
|
||||
self.props.properties.clear()
|
||||
|
||||
if self.pset_id:
|
||||
pset = tool.Ifc.get().by_id(self.pset_id)
|
||||
self.props.active_pset_name = pset.Name
|
||||
self.props.active_pset_type = ""
|
||||
pset_template = blenderbim.bim.schema.ifc.psetqto.get_by_name(pset.Name)
|
||||
self.pset_name = pset.Name
|
||||
else:
|
||||
pset = None
|
||||
self.props.active_pset_name = self.pset_name
|
||||
self.props.active_pset_type = self.pset_type
|
||||
pset_template = blenderbim.bim.schema.ifc.psetqto.get_by_name(self.pset_name)
|
||||
|
||||
if pset_template:
|
||||
self.load_from_pset_template(pset_template, pset)
|
||||
else:
|
||||
self.load_from_pset_data(pset)
|
||||
|
||||
self.props.active_pset_id = self.pset_id
|
||||
core.enable_pset_editing(tool.Pset, pset, self.pset_name, self.pset_type, self.obj, self.obj_type)
|
||||
return {"FINISHED"}
|
||||
|
||||
def load_from_pset_template(self, pset_template, pset):
|
||||
if pset:
|
||||
data = ifcopenshell.util.element.get_property_definition(pset)
|
||||
del data["id"]
|
||||
else:
|
||||
data = {}
|
||||
for prop_template in pset_template.HasPropertyTemplates:
|
||||
if not prop_template.is_a("IfcSimplePropertyTemplate"):
|
||||
continue # Other types not yet supported
|
||||
if prop_template.TemplateType == "P_SINGLEVALUE":
|
||||
self.load_single_value(pset_template, prop_template, data)
|
||||
elif prop_template.TemplateType.startswith("Q_"):
|
||||
self.load_single_value(pset_template, prop_template, data)
|
||||
elif prop_template.TemplateType == "P_ENUMERATEDVALUE":
|
||||
self.load_enumerated_value(prop_template, data)
|
||||
else:
|
||||
# NOTE: currently unsupported types:
|
||||
# - P_BOUNDEDVALUE
|
||||
# - P_LISTVALUE
|
||||
# - P_REFERENCEVALUE
|
||||
# - P_TABLEVALUE
|
||||
pass
|
||||
|
||||
def load_single_value(self, pset_template, prop_template, data):
|
||||
prop = self.props.properties.add()
|
||||
prop.name = prop_template.Name
|
||||
prop.value_type = "IfcPropertySingleValue"
|
||||
metadata = prop.metadata
|
||||
metadata.name = prop_template.Name
|
||||
metadata.is_null = data.get(prop_template.Name, None) is None
|
||||
metadata.is_optional = True
|
||||
metadata.is_uri = prop_template.PrimaryMeasureType == "IfcURIReference"
|
||||
metadata.has_calculator = bool(mapper.get(pset_template.Name, {}).get(prop_template.Name, None))
|
||||
metadata.data_type = self.get_data_type(prop_template)
|
||||
|
||||
special_type = ""
|
||||
if prop_template.PrimaryMeasureType in (
|
||||
"IfcPositiveLengthMeasure",
|
||||
"IfcLengthMeasure",
|
||||
) or prop_template.TemplateType == "Q_LENGTH":
|
||||
special_type = "LENGTH"
|
||||
elif prop_template.PrimaryMeasureType == "IfcAreaMeasure" or prop_template.TemplateType == "Q_AREA":
|
||||
special_type = "AREA"
|
||||
elif prop_template.PrimaryMeasureType == "IfcVolumeMeasure" or prop_template.TemplateType == "Q_VOLUME":
|
||||
special_type = "VOLUME"
|
||||
metadata.special_type = special_type
|
||||
|
||||
if metadata.data_type == "string":
|
||||
metadata.string_value = "" if metadata.is_null else str(data[prop_template.Name])
|
||||
elif metadata.data_type == "integer":
|
||||
metadata.int_value = 0 if metadata.is_null else int(data[prop_template.Name])
|
||||
elif metadata.data_type == "float":
|
||||
metadata.float_value = 0.0 if metadata.is_null else float(data[prop_template.Name])
|
||||
elif metadata.data_type == "boolean":
|
||||
metadata.bool_value = False if metadata.is_null else bool(data[prop_template.Name])
|
||||
|
||||
metadata.ifc_class = pset_template.Name
|
||||
blenderbim.bim.helper.add_attribute_description(metadata, prop_template)
|
||||
|
||||
def get_data_type(self, prop_template):
|
||||
if prop_template.TemplateType in ["Q_LENGTH", "Q_AREA", "Q_VOLUME", "Q_WEIGHT", "Q_TIME"]:
|
||||
return "float"
|
||||
elif prop_template.TemplateType == "Q_COUNT":
|
||||
return "integer"
|
||||
return ifcopenshell.util.attribute.get_primitive_type(
|
||||
IfcStore.get_schema().declaration_by_name(prop_template.PrimaryMeasureType or "IfcLabel")
|
||||
)
|
||||
|
||||
def load_enumerated_value(self, prop_template, data):
|
||||
enum_items = [v.wrappedValue for v in prop_template.Enumerators.EnumerationValues]
|
||||
selected_enum_items = data.get(prop_template.Name, []) or []
|
||||
|
||||
prop = self.props.properties.add()
|
||||
prop.name = prop_template.Name
|
||||
prop.value_type = "IfcPropertyEnumeratedValue"
|
||||
metadata = prop.metadata
|
||||
metadata.name = prop_template.Name
|
||||
metadata.is_null = data.get(prop_template.Name, None) is None
|
||||
metadata.is_optional = True
|
||||
metadata.is_uri = prop_template.PrimaryMeasureType == "IfcURIReference"
|
||||
|
||||
# Cute hack to abuse the metadata to find the Blender data_type
|
||||
metadata.set_value(enum_items[0])
|
||||
data_type = metadata.get_value_name()
|
||||
|
||||
for enum in enum_items:
|
||||
new = prop.enumerated_value.enumerated_values.add()
|
||||
setattr(new, data_type, enum)
|
||||
new.is_selected = enum in selected_enum_items
|
||||
|
||||
def load_from_pset_data(self, pset):
|
||||
props = []
|
||||
if pset.is_a("IfcElementQuantity"):
|
||||
props = pset.Quantities
|
||||
elif pset.is_a("IfcPropertySet"):
|
||||
props = pset.HasProperties
|
||||
elif pset.is_a("IfcMaterialProperties") or pset.is_a("IfcProfileProperties"):
|
||||
props = pset.Properties
|
||||
|
||||
for prop in props:
|
||||
if prop.is_a("IfcPropertyEnumeratedValue"):
|
||||
simple_prop = self.props.properties.add()
|
||||
simple_prop.name = prop.Name
|
||||
simple_prop.value_type = "IfcPropertyEnumeratedValue"
|
||||
metadata = simple_prop.metadata
|
||||
metadata.name = prop.Name
|
||||
metadata.is_null = len(simple_prop.enumerated_value.enumerated_values) == 0
|
||||
metadata.is_optional = True
|
||||
metadata.set_value(prop.EnumerationReference.EnumerationValues[0].wrappedValue)
|
||||
|
||||
enum_items = [v.wrappedValue for v in prop.EnumerationReference.EnumerationValues]
|
||||
selected_enum_items = [v.wrappedValue for v in prop.EnumerationValues]
|
||||
data_type = metadata.get_value_name(display_only=True)
|
||||
|
||||
for enum in enum_items:
|
||||
new = simple_prop.enumerated_value.enumerated_values.add()
|
||||
setattr(new, data_type, enum)
|
||||
new.is_selected = enum in selected_enum_items
|
||||
else:
|
||||
if prop.is_a("IfcPropertySingleValue"):
|
||||
value = prop.NominalValue.wrappedValue if prop.NominalValue else None
|
||||
elif prop.is_a("IfcPhysicalSimpleQuantity"):
|
||||
value = prop[3]
|
||||
new_prop = self.props.properties.add()
|
||||
new_prop.name = prop.Name
|
||||
metadata = new_prop.metadata
|
||||
metadata.set_value(value)
|
||||
metadata.name = prop.Name
|
||||
metadata.is_null = value is None
|
||||
metadata.is_optional = True
|
||||
metadata.set_value(metadata.get_value_default() if metadata.is_null else value)
|
||||
|
||||
|
||||
class DisablePsetEditing(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.disable_pset_editing"
|
||||
@@ -246,7 +80,7 @@ class DisablePsetEditing(bpy.types.Operator, Operator):
|
||||
obj_type: bpy.props.StringProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
props = get_pset_props(context, self.obj, self.obj_type)
|
||||
props = tool.Pset.get_pset_props(self.obj, self.obj_type)
|
||||
if props.active_pset_id:
|
||||
pset = tool.Ifc.get().by_id(props.active_pset_id)
|
||||
ifc_definition_id = tool.Blender.get_obj_ifc_definition_id(self.obj, self.obj_type, context)
|
||||
@@ -270,7 +104,7 @@ class EditPset(bpy.types.Operator, Operator):
|
||||
|
||||
def _execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
props = get_pset_props(context, self.obj, self.obj_type)
|
||||
props = tool.Pset.get_pset_props(self.obj, self.obj_type)
|
||||
ifc_definition_id = tool.Blender.get_obj_ifc_definition_id(self.obj, self.obj_type, context)
|
||||
element = tool.Ifc.get().by_id(ifc_definition_id)
|
||||
properties = {}
|
||||
@@ -310,6 +144,8 @@ class EditPset(bpy.types.Operator, Operator):
|
||||
for key, value in properties.items():
|
||||
if isinstance(value, float):
|
||||
properties[key] = round(value, 4)
|
||||
elif not isinstance(value, int):
|
||||
properties[key] = 0
|
||||
ifcopenshell.api.run(
|
||||
"pset.edit_qto",
|
||||
self.file,
|
||||
@@ -341,7 +177,7 @@ class RemovePset(bpy.types.Operator, Operator):
|
||||
objects = [self.obj]
|
||||
pset_name = tool.Ifc.get().by_id(self.pset_id).Name
|
||||
for obj in objects:
|
||||
props = get_pset_props(context, obj, self.obj_type)
|
||||
props = tool.Pset.get_pset_props(obj, self.obj_type)
|
||||
ifc_definition_id = tool.Blender.get_obj_ifc_definition_id(obj, self.obj_type, context)
|
||||
element = tool.Ifc.get().by_id(ifc_definition_id)
|
||||
pset = ifcopenshell.util.element.get_psets(element, should_inherit=False).get(pset_name, None)
|
||||
@@ -372,11 +208,9 @@ class AddQto(bpy.types.Operator, Operator):
|
||||
|
||||
def _execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
props = get_pset_props(context, self.obj, self.obj_type)
|
||||
ifc_definition_id = tool.Blender.get_obj_ifc_definition_id(self.obj, self.obj_type, context)
|
||||
element = tool.Ifc.get().by_id(ifc_definition_id)
|
||||
qto_name = tool.Pset.get_pset_name(self.obj, self.obj_type, pset_type="QTO")
|
||||
bpy.ops.bim.enable_pset_editing(
|
||||
pset_id=0, pset_name=props.qto_name, pset_type="QTO", obj=self.obj, obj_type=self.obj_type
|
||||
pset_id=0, pset_name=qto_name, pset_type="QTO", obj=self.obj, obj_type=self.obj_type
|
||||
)
|
||||
|
||||
|
||||
@@ -634,3 +468,17 @@ class BIM_OT_bulk_remove_psets(bpy.types.Operator):
|
||||
|
||||
self.report({"INFO"}, "Finished applying changes")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddProposedProp(bpy.types.Operator):
|
||||
bl_idname = "bim.add_proposed_prop"
|
||||
bl_label = "Add Proposed Prop"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
obj_type: bpy.props.StringProperty()
|
||||
prop_name: bpy.props.StringProperty()
|
||||
prop_value: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
core.add_proposed_prop(tool.Pset, self.obj, self.obj_type, self.prop_name, self.prop_value)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -19,10 +19,11 @@
|
||||
import bpy
|
||||
import blenderbim.bim.schema
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.attribute
|
||||
import ifcopenshell.util.element
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim.prop import Attribute, StrProperty
|
||||
from blenderbim.bim.module.pset.data import AddEditCustomPropertiesData, ObjectPsetsData
|
||||
from blenderbim.bim.module.pset.data import AddEditCustomPropertiesData, ObjectPsetsData, MaterialPsetsData
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
@@ -58,18 +59,39 @@ def blender_formatted_enum_from_psets(psets):
|
||||
|
||||
|
||||
def get_pset_name(self, context):
|
||||
pset_type = repr(self)
|
||||
prop_type = pset_type.split(".")[-1]
|
||||
results = []
|
||||
if "bpy.data.objects" in pset_type:
|
||||
if prop_type == "PsetProperties":
|
||||
results = get_object_pset_name(self, context)
|
||||
elif prop_type == "MaterialSetPsetProperties":
|
||||
results = get_material_set_pset_names(self, context)
|
||||
elif prop_type == "MaterialSetItemPsetProperties":
|
||||
results = get_material_set_item_pset_names(self, context)
|
||||
elif prop_type == "MaterialPsetProperties":
|
||||
results = get_material_pset_names(self, context)
|
||||
elif prop_type == "ResourcePsetProperties":
|
||||
results = get_resource_pset_names(self, context)
|
||||
elif prop_type == "GroupPsetProperties":
|
||||
results = get_group_pset_names(self, context)
|
||||
elif prop_type == "ProfilePsetProperties":
|
||||
results = get_profile_pset_names(self, context)
|
||||
elif prop_type == "WorkSchedulePsetProperties":
|
||||
results = get_work_schedule_pset_names(self, context)
|
||||
return [("BBIM_CUSTOM", "Custom Pset", "Create a property set without using a template."), None] + results
|
||||
|
||||
|
||||
def get_object_pset_name(self, context):
|
||||
if not ObjectPsetsData.is_loaded:
|
||||
ObjectPsetsData.load()
|
||||
return ObjectPsetsData.data["pset_name"]
|
||||
|
||||
|
||||
def get_material_pset_names(self, context):
|
||||
global psetnames
|
||||
ifc_class = "IfcMaterial"
|
||||
if ifc_class not in psetnames:
|
||||
psets = blenderbim.bim.schema.ifc.psetqto.get_applicable(ifc_class, pset_only=True)
|
||||
psetnames[ifc_class] = blender_formatted_enum_from_psets(psets)
|
||||
return psetnames[ifc_class]
|
||||
if not MaterialPsetsData.is_loaded:
|
||||
MaterialPsetsData.load()
|
||||
return MaterialPsetsData.data["pset_name"]
|
||||
|
||||
|
||||
def get_material_set_pset_names(self, context):
|
||||
@@ -167,6 +189,21 @@ def get_work_schedule_pset_names(self, context):
|
||||
|
||||
|
||||
def get_qto_name(self, context):
|
||||
pset_type = repr(self)
|
||||
prop_type = pset_type.split(".")[-1]
|
||||
if "bpy.data.objects" in pset_type:
|
||||
if prop_type == "PsetProperties":
|
||||
results = get_object_qto_name(self, context)
|
||||
elif prop_type == "TaskPsetProperties":
|
||||
results = get_task_qto_names(self, context)
|
||||
elif prop_type == "ResourcePsetProperties":
|
||||
results = get_resource_qto_names(self, context)
|
||||
elif prop_type == "GroupPsetProperties":
|
||||
results = get_group_qto_names(self, context)
|
||||
return [("BBIM_CUSTOM", "Custom Qto", "Create a quantity set without using a template."), None] + results
|
||||
|
||||
|
||||
def get_object_qto_name(self, context):
|
||||
if not ObjectPsetsData.is_loaded:
|
||||
ObjectPsetsData.load()
|
||||
return ObjectPsetsData.data["qto_name"]
|
||||
@@ -198,76 +235,14 @@ class IfcProperty(PropertyGroup):
|
||||
|
||||
class PsetProperties(PropertyGroup):
|
||||
active_pset_id: IntProperty(name="Active Pset ID")
|
||||
active_pset_has_template: BoolProperty(name="Active Pset Has Template")
|
||||
active_pset_name: StringProperty(name="Pset Name")
|
||||
active_pset_type: StringProperty(name="Active Pset Type")
|
||||
properties: CollectionProperty(name="Properties", type=IfcProperty)
|
||||
pset_name: EnumProperty(items=get_pset_name, name="Pset Name")
|
||||
qto_name: EnumProperty(items=get_qto_name, name="Qto Name")
|
||||
|
||||
|
||||
class MaterialPsetProperties(PropertyGroup):
|
||||
active_pset_id: IntProperty(name="Active Pset ID")
|
||||
active_pset_name: StringProperty(name="Pset Name")
|
||||
active_pset_type: StringProperty(name="Active Pset Type")
|
||||
properties: CollectionProperty(name="Properties", type=IfcProperty)
|
||||
pset_name: EnumProperty(items=get_material_pset_names, name="Pset Name")
|
||||
|
||||
|
||||
class MaterialSetPsetProperties(PropertyGroup):
|
||||
active_pset_id: IntProperty(name="Active Pset ID")
|
||||
active_pset_name: StringProperty(name="Pset Name")
|
||||
active_pset_type: StringProperty(name="Active Pset Type")
|
||||
properties: CollectionProperty(name="Properties", type=IfcProperty)
|
||||
pset_name: EnumProperty(items=get_material_set_pset_names, name="Pset Name")
|
||||
|
||||
|
||||
class MaterialSetItemPsetProperties(PropertyGroup):
|
||||
active_pset_id: IntProperty(name="Active Pset ID")
|
||||
active_pset_name: StringProperty(name="Pset Name")
|
||||
active_pset_type: StringProperty(name="Active Pset Type")
|
||||
properties: CollectionProperty(name="Properties", type=IfcProperty)
|
||||
pset_name: EnumProperty(items=get_material_set_item_pset_names, name="Pset Name")
|
||||
|
||||
|
||||
class TaskPsetProperties(PropertyGroup):
|
||||
active_pset_id: IntProperty(name="Active Pset ID")
|
||||
active_pset_name: StringProperty(name="Pset Name")
|
||||
active_pset_type: StringProperty(name="Active Pset Type")
|
||||
properties: CollectionProperty(name="Properties", type=IfcProperty)
|
||||
qto_name: EnumProperty(items=get_task_qto_names, name="Qto Name")
|
||||
|
||||
|
||||
class ResourcePsetProperties(PropertyGroup):
|
||||
active_pset_id: IntProperty(name="Active Pset ID")
|
||||
active_pset_name: StringProperty(name="Pset Name")
|
||||
active_pset_type: StringProperty(name="Active Pset Type")
|
||||
properties: CollectionProperty(name="Properties", type=IfcProperty)
|
||||
pset_name: EnumProperty(items=get_resource_pset_names, name="Pset Name")
|
||||
qto_name: EnumProperty(items=get_resource_qto_names, name="Qto Name")
|
||||
|
||||
|
||||
class GroupPsetProperties(PropertyGroup):
|
||||
active_pset_id: IntProperty(name="Active Pset ID")
|
||||
active_pset_name: StringProperty(name="Pset Name")
|
||||
active_pset_type: StringProperty(name="Active Pset Type")
|
||||
properties: CollectionProperty(name="Properties", type=IfcProperty)
|
||||
pset_name: EnumProperty(items=get_group_pset_names, name="Pset Name")
|
||||
qto_name: EnumProperty(items=get_group_qto_names, name="Qto Name")
|
||||
|
||||
class ProfilePsetProperties(PropertyGroup):
|
||||
active_pset_id: IntProperty(name="Active Pset ID")
|
||||
active_pset_name: StringProperty(name="Pset Name")
|
||||
active_pset_type: StringProperty(name="Active Pset Type")
|
||||
properties: CollectionProperty(name="Properties", type=IfcProperty)
|
||||
pset_name: EnumProperty(items=get_profile_pset_names, name="Pset Name")
|
||||
|
||||
|
||||
class WorkSchedulePsetProperties(PropertyGroup):
|
||||
active_pset_id: IntProperty(name="Active Pset ID")
|
||||
active_pset_name: StringProperty(name="Pset Name")
|
||||
active_pset_type: StringProperty(name="Active Pset Type")
|
||||
properties: CollectionProperty(name="Properties", type=IfcProperty)
|
||||
pset_name: EnumProperty(items=get_work_schedule_pset_names, name="Pset Name")
|
||||
prop_name: StringProperty(name="Property Name", default="MyProperty")
|
||||
prop_value: StringProperty(name="Property Value", default="Some Value")
|
||||
|
||||
|
||||
class RenameProperties(PropertyGroup):
|
||||
|
||||
@@ -79,13 +79,7 @@ def draw_enumerated_property(prop, layout, copy_operator=None):
|
||||
|
||||
|
||||
def get_active_pset_obj_name(context, obj_type):
|
||||
if obj_type == "Object":
|
||||
return context.active_object.name
|
||||
elif obj_type == "Material":
|
||||
return context.active_object.active_material.name
|
||||
elif obj_type == "MaterialSet":
|
||||
return context.active_object.name
|
||||
elif obj_type == "MaterialSetItem":
|
||||
if obj_type in ("Object", "Material", "MaterialSet", "MaterialSetItem"):
|
||||
return context.active_object.name
|
||||
return ""
|
||||
|
||||
@@ -101,6 +95,7 @@ def draw_psetqto_ui(context, pset_id, pset, props, layout, obj_type, allow_remov
|
||||
if props.active_pset_id == pset_id:
|
||||
row.prop(props, "active_pset_name", icon="COPY_ID", text="")
|
||||
op = row.operator("bim.edit_pset", icon="CHECKMARK", text="")
|
||||
op.pset_id = pset_id
|
||||
op.obj = obj_name
|
||||
op.obj_type = obj_type
|
||||
op = row.operator("bim.disable_pset_editing", icon="CANCEL", text="")
|
||||
@@ -132,6 +127,16 @@ def draw_psetqto_ui(context, pset_id, pset, props, layout, obj_type, allow_remov
|
||||
if props.active_pset_id == pset_id:
|
||||
for prop in props.properties:
|
||||
draw_psetqto_editable_ui(box, props, prop)
|
||||
|
||||
if not props.active_pset_has_template:
|
||||
row = box.row(align=True)
|
||||
row.prop(props, "prop_name", text="")
|
||||
row.prop(props, "prop_value", text="")
|
||||
op = row.operator("bim.add_proposed_prop", text="", icon="ADD")
|
||||
op.obj = obj_name
|
||||
op.obj_type = obj_type
|
||||
op.prop_name = props.prop_name
|
||||
op.prop_value = props.prop_value
|
||||
else:
|
||||
has_props_displayed = False
|
||||
for prop in pset["Properties"]:
|
||||
@@ -143,7 +148,9 @@ def draw_psetqto_ui(context, pset_id, pset, props, layout, obj_type, allow_remov
|
||||
row = box.row(align=True)
|
||||
row.scale_y = 0.8
|
||||
row.label(text=prop["Name"])
|
||||
row.label(text=str(prop["NominalValue"]))
|
||||
op = row.operator("bim.select_similar", text=str(prop["NominalValue"]), icon="NONE", emboss=False)
|
||||
op.key = pset['Name']
|
||||
|
||||
if not has_props_displayed:
|
||||
row = box.row()
|
||||
row.scale_y = 0.8
|
||||
@@ -273,36 +280,36 @@ class BIM_PT_material_psets(Panel):
|
||||
bl_idname = "BIM_PT_material_psets"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "material"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_materials"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if not context.active_object:
|
||||
return False
|
||||
if not context.active_object.active_material:
|
||||
return False
|
||||
props = context.active_object.active_material.BIMObjectProperties
|
||||
if not props.ifc_definition_id:
|
||||
return False
|
||||
file = IfcStore.get_file()
|
||||
if not file or file.schema == "IFC2X3":
|
||||
ifc_file = tool.Ifc.get()
|
||||
if not ifc_file or ifc_file.schema == "IFC2X3":
|
||||
return False # We don't support material psets in IFC2X3 because they suck
|
||||
return True
|
||||
props = context.scene.BIMMaterialProperties
|
||||
if props.materials and props.active_material_index < len(props.materials):
|
||||
material = props.materials[props.active_material_index]
|
||||
if material.ifc_definition_id:
|
||||
return True
|
||||
return False
|
||||
|
||||
def draw(self, context):
|
||||
props = context.scene.BIMMaterialProperties
|
||||
if props.materials and props.active_material_index < len(props.materials):
|
||||
ifc_definition_id = props.materials[props.active_material_index].ifc_definition_id
|
||||
|
||||
if not MaterialPsetsData.is_loaded:
|
||||
MaterialPsetsData.load()
|
||||
elif (
|
||||
context.active_object.active_material.BIMObjectProperties.ifc_definition_id
|
||||
!= MaterialPsetsData.data["ifc_definition_id"]
|
||||
):
|
||||
elif ifc_definition_id != MaterialPsetsData.data["ifc_definition_id"]:
|
||||
MaterialPsetsData.load()
|
||||
|
||||
props = context.active_object.active_material.PsetProperties
|
||||
props = context.scene.MaterialPsetProperties
|
||||
row = self.layout.row(align=True)
|
||||
prop_with_search(row, props, "pset_name", text="")
|
||||
op = row.operator("bim.add_pset", icon="ADD", text="")
|
||||
op.obj = context.active_object.active_material.name
|
||||
op.obj = ""
|
||||
op.obj_type = "Material"
|
||||
|
||||
if not props.active_pset_id and props.active_pset_name and props.active_pset_type == "PSET":
|
||||
|
||||
@@ -21,6 +21,7 @@ import bpy
|
||||
import pathlib
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.attribute
|
||||
import ifcopenshell.util.doc
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
@@ -36,10 +37,14 @@ class PsetTemplatesData:
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.is_loaded = True
|
||||
cls.data["pset_template_files"] = cls.pset_template_files()
|
||||
|
||||
# after pset_template_files
|
||||
cls.data["pset_templates"] = cls.pset_templates()
|
||||
|
||||
# after pset_template_files because it loads IfcStore.pset_template_file
|
||||
cls.data["primary_measure_type"] = cls.primary_measure_type()
|
||||
cls.data["property_template_type"] = cls.property_template_type()
|
||||
cls.data["pset_template_files"] = cls.pset_template_files()
|
||||
cls.data["pset_templates"] = cls.pset_templates()
|
||||
cls.data["pset_template"] = cls.pset_template()
|
||||
cls.data["prop_templates"] = cls.prop_templates()
|
||||
|
||||
|
||||
@@ -268,7 +268,6 @@ class RemovePropTemplate(bpy.types.Operator, Operator):
|
||||
prop_template: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMPsetTemplateProperties
|
||||
ifcopenshell.api.run(
|
||||
"pset_template.remove_prop_template",
|
||||
IfcStore.pset_template_file,
|
||||
@@ -287,21 +286,21 @@ class EditPropTemplate(bpy.types.Operator, Operator):
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMPsetTemplateProperties
|
||||
if props.active_prop_template.template_type == "P_ENUMERATEDVALUE":
|
||||
enumerator = self.generate_prop_enum(props)
|
||||
data_type = props.active_prop_template.get_value_name()
|
||||
prop = props.active_prop_template
|
||||
enumerators = [getattr(ev, data_type) for ev in prop.enum_values]
|
||||
else:
|
||||
enumerator = None
|
||||
enumerators = None
|
||||
ifcopenshell.api.run(
|
||||
"pset_template.edit_prop_template",
|
||||
IfcStore.pset_template_file,
|
||||
**{
|
||||
"prop_template": IfcStore.pset_template_file.by_id(props.active_prop_template_id),
|
||||
"attributes": {
|
||||
"Name": props.active_prop_template.name,
|
||||
"Description": props.active_prop_template.description,
|
||||
"PrimaryMeasureType": props.active_prop_template.primary_measure_type,
|
||||
"TemplateType": props.active_prop_template.template_type,
|
||||
"Enumerators": enumerator,
|
||||
},
|
||||
prop_template=IfcStore.pset_template_file.by_id(props.active_prop_template_id),
|
||||
attributes={
|
||||
"Name": props.active_prop_template.name,
|
||||
"Description": props.active_prop_template.description,
|
||||
"PrimaryMeasureType": props.active_prop_template.primary_measure_type,
|
||||
"TemplateType": props.active_prop_template.template_type,
|
||||
"Enumerators": enumerators,
|
||||
}
|
||||
)
|
||||
bpy.ops.bim.disable_editing_prop_template()
|
||||
@@ -309,18 +308,3 @@ class EditPropTemplate(bpy.types.Operator, Operator):
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
if tool.Ifc.get():
|
||||
blenderbim.bim.schema.reload(tool.Ifc.get().schema)
|
||||
|
||||
# TODO -This will need to go into the
|
||||
# api code at some point - vulevukusej
|
||||
def generate_prop_enum(self, props):
|
||||
self.file = IfcStore.pset_template_file
|
||||
data_type = props.active_prop_template.get_value_name()
|
||||
prop = props.active_prop_template
|
||||
prop_enum = self.file.create_entity(
|
||||
"IFCPROPERTYENUMERATION",
|
||||
Name=prop.name,
|
||||
EnumerationValues=tuple(
|
||||
self.file.create_entity(prop.primary_measure_type, getattr(ev, data_type)) for ev in prop.enum_values
|
||||
),
|
||||
)
|
||||
return prop_enum
|
||||
|
||||
@@ -190,4 +190,4 @@ class BIMPsetTemplateProperties(PropertyGroup):
|
||||
active_prop_template_id: IntProperty(name="Active Prop Template Id")
|
||||
active_pset_template: PointerProperty(type=PsetTemplate)
|
||||
active_prop_template: PointerProperty(type=PropTemplate)
|
||||
new_template_filename: StringProperty("New TemplateFileName")
|
||||
new_template_filename: StringProperty(name="New TemplateFileName")
|
||||
|
||||
@@ -170,6 +170,7 @@ class QuantifyObjects(bpy.types.Operator):
|
||||
ifcopenshell.api.run("pset.edit_qto", self.file, qto=qto, properties={props.prop_name: result})
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AssignBaseQto(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.assign_objects_base_qto"
|
||||
bl_label = "Assign IFC Object Quantity Set"
|
||||
@@ -181,9 +182,10 @@ class AssignBaseQto(bpy.types.Operator, tool.Ifc.Operator):
|
||||
return tool.Ifc.get() and context.selected_objects
|
||||
|
||||
def _execute(self, context):
|
||||
core.assign_objects_base_qto(tool.Ifc, tool.Qto, selected_objects = context.selected_objects)
|
||||
core.assign_objects_base_qto(tool.Ifc, tool.Qto, selected_objects=context.selected_objects)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class CalculateAllQuantities(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.calculate_all_quantities"
|
||||
bl_label = "Calculate All Quantities"
|
||||
@@ -195,5 +197,7 @@ class CalculateAllQuantities(bpy.types.Operator, tool.Ifc.Operator):
|
||||
return tool.Ifc.get() and context.selected_objects
|
||||
|
||||
def _execute(self, context):
|
||||
core.calculate_objects_base_quantities(tool.Ifc, tool.Cost, tool.Qto, QtoCalculator(), selected_objects = context.selected_objects)
|
||||
core.calculate_objects_base_quantities(
|
||||
tool.Ifc, tool.Cost, tool.Qto, QtoCalculator(), selected_objects=context.selected_objects
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -24,9 +24,11 @@ class BIM_PT_qto_utilities(bpy.types.Panel):
|
||||
bl_idname = "BIM_PT_qto_utilities"
|
||||
bl_label = "Quantity Take-off"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "UI"
|
||||
bl_category = "BlenderBIM"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_tab_qto"
|
||||
bl_options = {"HIDE_HEADER"}
|
||||
|
||||
def draw(self, context):
|
||||
if not QtoData.is_loaded:
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
import bpy
|
||||
import blenderbim.tool as tool
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.resource
|
||||
|
||||
|
||||
def refresh():
|
||||
@@ -28,7 +29,6 @@ def refresh():
|
||||
class ResourceData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
cost_values = {}
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
@@ -139,7 +139,7 @@ class ResourceData:
|
||||
return results
|
||||
|
||||
@classmethod
|
||||
def active_resource_ids(cls):
|
||||
def active_resource_ids(cls) -> list[int]:
|
||||
obj = bpy.context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
|
||||
@@ -128,9 +128,9 @@ class ContractResource(bpy.types.Operator):
|
||||
class AssignResource(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.assign_resource"
|
||||
bl_label = "Assign Resource"
|
||||
bl_description = "Assign resource to the selected objects"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
resource: bpy.props.IntProperty()
|
||||
related_object: bpy.props.StringProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.assign_resource(tool.Ifc, tool.Spatial, resource=tool.Ifc.get().by_id(self.resource))
|
||||
@@ -139,9 +139,9 @@ class AssignResource(bpy.types.Operator, tool.Ifc.Operator):
|
||||
class UnassignResource(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.unassign_resource"
|
||||
bl_label = "Unassign Resource"
|
||||
bl_description = "Unassign resource from the selected objects"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
resource: bpy.props.IntProperty()
|
||||
related_object: bpy.props.StringProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.unassign_resource(tool.Ifc, tool.Spatial, resource=tool.Ifc.get().by_id(self.resource))
|
||||
@@ -328,7 +328,7 @@ class EditResourceQuantity(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
|
||||
class ImportResources(bpy.types.Operator, tool.Ifc.Operator, ImportHelper):
|
||||
bl_idname = "import_resources.bim"
|
||||
bl_idname = "bim.import_resources"
|
||||
bl_label = "Import Resources"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filename_ext = ".csv"
|
||||
|
||||
@@ -37,13 +37,29 @@ from bpy.props import (
|
||||
CollectionProperty,
|
||||
)
|
||||
|
||||
|
||||
quantitytypes_enum = []
|
||||
quantitytypes_enum = {}
|
||||
|
||||
|
||||
def purge():
|
||||
global quantitytypes_enum
|
||||
quantitytypes_enum = []
|
||||
def setup_quantity_types_enum():
|
||||
# https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcConstructionResource.htm#Table-7.3.3.7.1.3.H
|
||||
resources = {
|
||||
"IfcCrewResource": ("IfcQuantityTime",),
|
||||
"IfcLaborResource": ("IfcQuantityTime",),
|
||||
"IfcSubContractResource": ("IfcQuantityTime",),
|
||||
"IfcConstructionEquipmentResource": ("IfcQuantityTime",),
|
||||
"IfcConstructionMaterialResource": (
|
||||
"IfcQuantityVolume",
|
||||
"IfcQuantityArea",
|
||||
"IfcQuantityLength",
|
||||
"IfcQuantityWeight",
|
||||
),
|
||||
"IfcConstructionProductResource": ("IfcQuantityCount",),
|
||||
}
|
||||
for resource, quantities in resources.items():
|
||||
quantitytypes_enum[resource] = [(q, q, "") for q in quantities]
|
||||
|
||||
|
||||
setup_quantity_types_enum()
|
||||
|
||||
|
||||
def updateResourceName(self, context):
|
||||
@@ -63,15 +79,7 @@ def updateResourceName(self, context):
|
||||
|
||||
|
||||
def get_quantity_types(self, context):
|
||||
global quantitytypes_enum
|
||||
if len(quantitytypes_enum) == 0 and IfcStore.get_schema():
|
||||
quantitytypes_enum.extend(
|
||||
[
|
||||
(t.name(), t.name(), "")
|
||||
for t in IfcStore.get_schema().declaration_by_name("IfcPhysicalSimpleQuantity").subtypes()
|
||||
]
|
||||
)
|
||||
return quantitytypes_enum
|
||||
return quantitytypes_enum[self.active_resource_class]
|
||||
|
||||
|
||||
def update_active_resource_index(self, context):
|
||||
@@ -88,9 +96,7 @@ def updateResourceUsage(self, context):
|
||||
resource = tool.Ifc.get().by_id(self.ifc_definition_id)
|
||||
if resource.Usage and resource.Usage.ScheduleUsage == self.schedule_usage:
|
||||
return
|
||||
tool.Resource.run_edit_resource_time(resource, attributes={
|
||||
"ScheduleUsage": self.schedule_usage
|
||||
})
|
||||
tool.Resource.run_edit_resource_time(resource, attributes={"ScheduleUsage": self.schedule_usage})
|
||||
tool.Sequence.load_task_properties()
|
||||
tool.Resource.load_resource_properties()
|
||||
tool.Sequence.refresh_task_resources()
|
||||
@@ -127,12 +133,21 @@ class BIMResourceProperties(PropertyGroup):
|
||||
is_editing: BoolProperty(name="Is Editing")
|
||||
active_resource_index: IntProperty(name="Active Resource Index", update=update_active_resource_index)
|
||||
active_resource_id: IntProperty(name="Active Resource Id")
|
||||
active_resource_class: StringProperty(name="Active Resource Type")
|
||||
contracted_resources: StringProperty(name="Contracted Resources", default="[]")
|
||||
is_resource_update_enabled: BoolProperty(name="Is Resource Update Enabled", default=True)
|
||||
is_loaded: BoolProperty(name="Is Editing")
|
||||
active_resource_time_id: IntProperty(name="Active Resource Usage Id")
|
||||
resource_time_attributes: CollectionProperty(name="Resource Usage Attributes", type=Attribute)
|
||||
editing_resource_type: StringProperty(name="Editing Resource Type")
|
||||
editing_resource_type: EnumProperty(
|
||||
name="Editing Resource Type",
|
||||
items=(
|
||||
("ATTRIBUTES", "", ""),
|
||||
("USAGE", "", ""),
|
||||
("COSTS", "", ""),
|
||||
("QUANTITY", "", ""),
|
||||
),
|
||||
)
|
||||
cost_types: EnumProperty(
|
||||
items=[
|
||||
("FIXED", "Fixed", "The cost value is a fixed number"),
|
||||
|
||||
@@ -51,7 +51,7 @@ class BIM_PT_resources(Panel):
|
||||
row.operator("bim.disable_resource_editing_ui", text="", icon="CANCEL")
|
||||
else:
|
||||
row.operator("bim.load_resources", text="", icon="GREASEPENCIL")
|
||||
row.operator("import_resources.bim", text="", icon="IMPORT")
|
||||
row.operator("bim.import_resources", text="", icon="IMPORT")
|
||||
if not self.props.is_editing:
|
||||
return
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@ import re
|
||||
import bpy
|
||||
import json
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.guid
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.selector
|
||||
from ifcopenshell.util.selector import Selector
|
||||
@@ -213,7 +215,7 @@ class SaveSearch(Operator, tool.Ifc.Operator):
|
||||
group = group[0]
|
||||
group.Description = description
|
||||
else:
|
||||
group = ifcopenshell.api.run("group.add_group", tool.Ifc.get(), Name=self.name, Description=description)
|
||||
group = ifcopenshell.api.run("group.add_group", tool.Ifc.get(), name=self.name, description=description)
|
||||
if results:
|
||||
ifcopenshell.api.run("group.assign_group", tool.Ifc.get(), products=list(results), group=group)
|
||||
|
||||
@@ -366,7 +368,7 @@ class SaveColourscheme(Operator, tool.Ifc.Operator):
|
||||
description = json.dumps(
|
||||
{"type": "BBIM_Search", "colourscheme": colourscheme, "colourscheme_query": query}
|
||||
)
|
||||
group = ifcopenshell.api.run("group.add_group", tool.Ifc.get(), Name=self.name, Description=description)
|
||||
group = ifcopenshell.api.run("group.add_group", tool.Ifc.get(), name=self.name, description=description)
|
||||
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
@@ -633,12 +635,14 @@ class SelectSimilar(Operator, tool.Ifc.Operator):
|
||||
bl_label = "Select Similar"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
key: bpy.props.StringProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMSearchProperties
|
||||
obj = context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
key = props.element_key
|
||||
if props.element_key == "PredefinedType":
|
||||
key = self.key
|
||||
if key == "PredefinedType":
|
||||
key = "predefined_type"
|
||||
value = ifcopenshell.util.selector.get_element_value(element, key)
|
||||
for obj in context.visible_objects:
|
||||
|
||||
@@ -113,7 +113,8 @@ class BIM_PT_select_similar(Panel):
|
||||
if SelectSimilarData.data["element_key"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "element_key", text="")
|
||||
row.operator("bim.select_similar", text="", icon="RESTRICT_SELECT_OFF")
|
||||
op = row.operator("bim.select_similar", text="", icon="RESTRICT_SELECT_OFF")
|
||||
op.key = props.element_key
|
||||
else:
|
||||
row = self.layout.row()
|
||||
row.label(text=f"No Active Element")
|
||||
|
||||
@@ -628,7 +628,7 @@ class DisableEditingWorkCalendar(bpy.types.Operator):
|
||||
|
||||
|
||||
class ImportCSV(bpy.types.Operator, tool.Ifc.Operator, ImportHelper):
|
||||
bl_idname = "import_csv.bim"
|
||||
bl_idname = "bim.import_csv"
|
||||
bl_label = "Import CSV"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filename_ext = ".csv"
|
||||
@@ -653,7 +653,7 @@ class ImportCSV(bpy.types.Operator, tool.Ifc.Operator, ImportHelper):
|
||||
|
||||
|
||||
class ImportP6(bpy.types.Operator, ImportHelper):
|
||||
bl_idname = "import_p6.bim"
|
||||
bl_idname = "bim.import_p6"
|
||||
bl_label = "Import P6"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filename_ext = ".xml"
|
||||
@@ -679,7 +679,7 @@ class ImportP6(bpy.types.Operator, ImportHelper):
|
||||
|
||||
|
||||
class ImportP6XER(bpy.types.Operator, ImportHelper):
|
||||
bl_idname = "import_p6xer.bim"
|
||||
bl_idname = "bim.import_p6xer"
|
||||
bl_label = "Import P6 XER"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filename_ext = ".xer"
|
||||
@@ -705,7 +705,7 @@ class ImportP6XER(bpy.types.Operator, ImportHelper):
|
||||
|
||||
|
||||
class ImportPP(bpy.types.Operator, ImportHelper):
|
||||
bl_idname = "import_pp.bim"
|
||||
bl_idname = "bim.import_pp"
|
||||
bl_label = "Import Powerproject .pp"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filename_ext = ".pp"
|
||||
@@ -731,7 +731,7 @@ class ImportPP(bpy.types.Operator, ImportHelper):
|
||||
|
||||
|
||||
class ImportMSP(bpy.types.Operator, ImportHelper):
|
||||
bl_idname = "import_msp.bim"
|
||||
bl_idname = "bim.import_msp"
|
||||
bl_label = "Import MSP"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filename_ext = ".xml"
|
||||
@@ -757,7 +757,7 @@ class ImportMSP(bpy.types.Operator, ImportHelper):
|
||||
|
||||
|
||||
class ExportMSP(bpy.types.Operator, ImportHelper):
|
||||
bl_idname = "export_msp.bim"
|
||||
bl_idname = "bim.export_msp"
|
||||
bl_label = "Export MSP"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filename_ext = ".xml"
|
||||
@@ -787,7 +787,7 @@ class ExportMSP(bpy.types.Operator, ImportHelper):
|
||||
|
||||
|
||||
class ExportP6(bpy.types.Operator, ImportHelper):
|
||||
bl_idname = "export_p6.bim"
|
||||
bl_idname = "bim.export_p6"
|
||||
bl_label = "Export P6"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filename_ext = ".xml"
|
||||
@@ -1409,9 +1409,13 @@ class LoadProductTasks(bpy.types.Operator):
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
core.load_product_related_tasks(
|
||||
result = core.load_product_related_tasks(
|
||||
tool.Sequence, product=tool.Ifc.get().by_id(context.active_object.BIMObjectProperties.ifc_definition_id)
|
||||
)
|
||||
if isinstance(result, str):
|
||||
self.report({"INFO"}, result)
|
||||
else:
|
||||
self.report({"INFO"}, f"{len(result)} product tasks loaded.")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
|
||||
@@ -1084,9 +1084,11 @@ class BIM_PT_work_calendars(Panel):
|
||||
class BIM_PT_4D_Tools(Panel):
|
||||
bl_label = "4D Tools"
|
||||
bl_idname = "BIM_PT_4D_Tools"
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "UI"
|
||||
bl_category = "4D/5D Toolkit"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_tab_sequence"
|
||||
|
||||
def draw(self, context):
|
||||
self.props = context.scene.BIMWorkScheduleProperties
|
||||
|
||||
@@ -44,6 +44,8 @@ def update_elevation(self, context):
|
||||
|
||||
|
||||
def update_active_container_index(self, context):
|
||||
if self.active_container_index < 0:
|
||||
return
|
||||
self.active_container_id = self.containers[self.active_container_index].ifc_definition_id
|
||||
self.container_name = self.containers[self.active_container_index].name
|
||||
self.elevation = self.containers[self.active_container_index].elevation
|
||||
|
||||
@@ -116,7 +116,7 @@ class BIM_PT_SpatialManager(Panel):
|
||||
self.props = context.scene.BIMSpatialManagerProperties
|
||||
row = self.layout.row()
|
||||
row.operator("bim.load_container_manager", icon="FILE_REFRESH", text="Load Spatial Structure")
|
||||
if self.props.active_container_index < len(self.props.containers):
|
||||
if 0 <= self.props.active_container_index < len(self.props.containers):
|
||||
ifc_definition_id = self.props.containers[self.props.active_container_index].ifc_definition_id
|
||||
row = self.layout.row()
|
||||
row.alignment = "RIGHT"
|
||||
@@ -134,7 +134,7 @@ class BIM_PT_SpatialManager(Panel):
|
||||
"active_container_index",
|
||||
)
|
||||
row = self.layout.row()
|
||||
if self.props.active_container_index < len(self.props.containers):
|
||||
if 0 <= self.props.active_container_index < len(self.props.containers):
|
||||
row.prop(self.props, "container_name", text="")
|
||||
row.prop(self.props, "elevation", text="")
|
||||
op = row.operator("bim.edit_container_attributes", icon="CHECKMARK", text="Apply")
|
||||
|
||||
@@ -20,6 +20,7 @@ import bpy
|
||||
import json
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.attribute
|
||||
import blenderbim.bim.helper
|
||||
import blenderbim.bim.handler
|
||||
import blenderbim.core.structural as core
|
||||
|
||||
@@ -50,8 +50,6 @@ classes = (
|
||||
prop.BIMStylesProperties,
|
||||
prop.BIMStyleProperties,
|
||||
ui.BIM_PT_styles,
|
||||
ui.BIM_PT_style,
|
||||
ui.BIM_PT_style_attributes,
|
||||
ui.BIM_UL_styles,
|
||||
)
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@ from ifcopenshell.util.doc import get_entity_doc
|
||||
|
||||
def refresh():
|
||||
StylesData.is_loaded = False
|
||||
StyleAttributesData.is_loaded = False
|
||||
|
||||
|
||||
class StylesData:
|
||||
@@ -68,30 +67,3 @@ class StylesData:
|
||||
@classmethod
|
||||
def total_styles(cls):
|
||||
return len(tool.Ifc.get().by_type("IfcPresentationStyle"))
|
||||
|
||||
|
||||
class StyleAttributesData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {
|
||||
"ifc_style_id": cls.ifc_style_id(),
|
||||
"attributes": cls.attributes(),
|
||||
}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def ifc_style_id(cls):
|
||||
return bpy.context.active_object.active_material.BIMMaterialProperties.ifc_style_id
|
||||
|
||||
@classmethod
|
||||
def attributes(cls):
|
||||
style = tool.Ifc.get().by_id(bpy.context.active_object.active_material.BIMMaterialProperties.ifc_style_id)
|
||||
results = []
|
||||
for name, value in style.get_info().items():
|
||||
if name in ["id", "type", "Styles"]:
|
||||
continue
|
||||
results.append({"name": name, "value": str(value)})
|
||||
return results
|
||||
|
||||
@@ -28,6 +28,7 @@ from pathlib import Path
|
||||
from mathutils import Vector
|
||||
|
||||
|
||||
# TODO: is this still relevant or can it be deleted?
|
||||
class UpdateStyleColours(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.update_style_colours"
|
||||
bl_label = "Save Current Shading Style"
|
||||
@@ -53,6 +54,7 @@ class UpdateStyleColours(bpy.types.Operator, tool.Ifc.Operator):
|
||||
self.report({"INFO"}, "Check the system console to see saved style properties")
|
||||
|
||||
|
||||
# TODO: is this still relevant or can it be deleted?
|
||||
class UpdateStyleTextures(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.update_style_textures"
|
||||
bl_label = "Update Style Textures"
|
||||
@@ -93,9 +95,10 @@ class UnlinkStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.unlink_style"
|
||||
bl_label = "Unlink Style"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
style: bpy.props.IntProperty(default=0)
|
||||
|
||||
def _execute(self, context):
|
||||
core.unlink_style(tool.Ifc, tool.Style, obj=context.active_object.active_material)
|
||||
core.unlink_style(tool.Ifc, style=tool.Ifc.get().by_id(self.style))
|
||||
|
||||
|
||||
class EnableEditingStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
@@ -122,7 +122,7 @@ class ColourRgb(PropertyGroup):
|
||||
name: StringProperty()
|
||||
color_value: FloatVectorProperty(size=3, subtype="COLOR", default=(1, 1, 1))
|
||||
# not exposed in the UI, here just to preserve the data
|
||||
color_name: StringProperty("Color Name")
|
||||
color_name: StringProperty(name="Color Name")
|
||||
|
||||
# to fit blender.bim.helper.export_attributes
|
||||
def get_value(self):
|
||||
|
||||
@@ -21,7 +21,7 @@ import blenderbim.bim.helper
|
||||
import blenderbim.tool as tool
|
||||
from bpy.types import Panel, UIList
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.style.data import StylesData, StyleAttributesData
|
||||
from blenderbim.bim.module.style.data import StylesData
|
||||
from bl_ui.properties_material import MaterialButtonsPanel
|
||||
|
||||
|
||||
@@ -62,11 +62,11 @@ class BIM_PT_styles(Panel):
|
||||
row.operator("bim.enable_adding_presentation_style", text="", icon="ADD")
|
||||
if active_style:
|
||||
style = self.props.styles[self.props.active_style_index]
|
||||
material_name = StylesData.data["styles_to_blender_material_names"][self.props.active_style_index]
|
||||
material = bpy.data.materials[material_name]
|
||||
|
||||
row.operator("bim.duplicate_style", text="", icon="DUPLICATE").style = style.ifc_definition_id
|
||||
row.operator("bim.select_by_style", text="", icon="RESTRICT_SELECT_OFF").style = style.ifc_definition_id
|
||||
op = row.operator("bim.unlink_style", text="", icon="UNLINKED")
|
||||
op.style = style.ifc_definition_id
|
||||
op = row.operator("bim.enable_editing_style", text="", icon="GREASEPENCIL")
|
||||
op.style = style.ifc_definition_id
|
||||
row.operator("bim.remove_style", text="", icon="X").style = style.ifc_definition_id
|
||||
@@ -93,9 +93,12 @@ class BIM_PT_styles(Panel):
|
||||
# style ui tools
|
||||
if active_style:
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(material.BIMStyleProperties, "active_style_type", icon="SHADING_RENDERED", text="")
|
||||
op = row.operator("bim.update_current_style", icon="FILE_REFRESH", text="")
|
||||
op.style_id = style.ifc_definition_id
|
||||
material_name = StylesData.data["styles_to_blender_material_names"][self.props.active_style_index]
|
||||
if material_name: # The user may have unlinked the style, so the material may not exist
|
||||
material = bpy.data.materials[material_name]
|
||||
row.prop(material.BIMStyleProperties, "active_style_type", icon="SHADING_RENDERED", text="")
|
||||
op = row.operator("bim.update_current_style", icon="FILE_REFRESH", text="")
|
||||
op.style_id = style.ifc_definition_id
|
||||
|
||||
if self.props.style_type == "IfcSurfaceStyle":
|
||||
self.layout.label(text="Surface Style Element:")
|
||||
@@ -259,80 +262,6 @@ class BIM_PT_styles(Panel):
|
||||
row.operator("bim.disable_editing_style", text="", icon="CANCEL")
|
||||
|
||||
|
||||
class BIM_PT_style(MaterialButtonsPanel, Panel):
|
||||
bl_label = "Style"
|
||||
bl_idname = "BIM_PT_style"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "material"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return (
|
||||
IfcStore.get_file()
|
||||
and context.active_object is not None
|
||||
and context.active_object.active_material is not None
|
||||
)
|
||||
|
||||
def draw(self, context):
|
||||
mat = context.material
|
||||
props = mat.BIMMaterialProperties
|
||||
row = self.layout.row(align=True)
|
||||
if not props.ifc_style_id:
|
||||
row.operator("bim.add_style", icon="ADD")
|
||||
return
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.update_style_colours", icon="GREASEPENCIL")
|
||||
row.operator("bim.update_style_textures", icon="TEXTURE", text="")
|
||||
row.operator("bim.unlink_style", icon="UNLINKED", text="")
|
||||
row.operator("bim.remove_style", icon="X", text="").style = props.ifc_style_id
|
||||
|
||||
|
||||
class BIM_PT_style_attributes(Panel):
|
||||
bl_label = "Style Attributes"
|
||||
bl_idname = "BIM_PT_style_attributes"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "material"
|
||||
bl_parent_id = "BIM_PT_style"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if not IfcStore.get_file():
|
||||
return False
|
||||
try:
|
||||
return bool(context.active_object.active_material.BIMMaterialProperties.ifc_style_id)
|
||||
except:
|
||||
return False
|
||||
|
||||
def draw(self, context):
|
||||
if not StyleAttributesData.is_loaded:
|
||||
StyleAttributesData.load()
|
||||
elif (
|
||||
context.active_object.active_material.BIMMaterialProperties.ifc_style_id
|
||||
!= StyleAttributesData.data["ifc_style_id"]
|
||||
):
|
||||
StyleAttributesData.load()
|
||||
|
||||
obj = context.active_object.active_material
|
||||
mprops = obj.BIMMaterialProperties
|
||||
props = obj.BIMStyleProperties
|
||||
if props.is_editing:
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.edit_style", icon="CHECKMARK")
|
||||
row.operator("bim.disable_editing_style", icon="CANCEL", text="")
|
||||
blenderbim.bim.helper.draw_attributes(props.attributes, self.layout)
|
||||
else:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="STEP ID")
|
||||
row.label(text=str(mprops.ifc_style_id))
|
||||
|
||||
for attribute in StyleAttributesData.data["attributes"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=attribute["name"])
|
||||
row.label(text=attribute["value"])
|
||||
|
||||
|
||||
class BIM_UL_styles(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if item:
|
||||
|
||||
@@ -22,6 +22,7 @@ import ifcopenshell.util.element
|
||||
import ifcopenshell.util.schema
|
||||
import ifcopenshell.util.representation
|
||||
import ifcopenshell.util.type
|
||||
import ifcopenshell.util.unit
|
||||
import ifcopenshell.api
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.geometry
|
||||
@@ -454,7 +455,6 @@ class AddType(bpy.types.Operator, tool.Ifc.Operator):
|
||||
predefined_type=predefined_type,
|
||||
ifc_class="IfcStairFlightType",
|
||||
should_add_representation=False,
|
||||
context=body,
|
||||
)
|
||||
tool.Blender.select_and_activate_single_object(context, obj)
|
||||
bpy.ops.bim.add_stair()
|
||||
@@ -512,10 +512,7 @@ class RemoveType(bpy.types.Operator, tool.Ifc.Operator):
|
||||
def _execute(self, context):
|
||||
element = tool.Ifc.get().by_id(self.element)
|
||||
obj = tool.Ifc.get_object(element)
|
||||
ifcopenshell.api.run("root.remove_product", tool.Ifc.get(), product=element)
|
||||
if obj:
|
||||
tool.Ifc.unlink(obj=obj)
|
||||
bpy.data.objects.remove(obj)
|
||||
tool.Geometry.delete_ifc_object(obj)
|
||||
|
||||
|
||||
class RenameType(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
@@ -21,6 +21,7 @@ import ifcopenshell.api
|
||||
import ifcopenshell.util.representation
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.geometry
|
||||
import blenderbim.core.root
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.model.opening import FilledOpeningGenerator
|
||||
|
||||
@@ -77,6 +78,10 @@ class AddOpening(bpy.types.Operator, tool.Ifc.Operator):
|
||||
self.report({"INFO"}, "You can't add an opening to another opening.")
|
||||
continue
|
||||
|
||||
if not hasattr(element1, "HasOpenings"):
|
||||
self.report({"INFO"}, f"An {element1.is_a()} is not allowed to have an opening.")
|
||||
continue
|
||||
|
||||
if tool.Ifc.is_moved(obj1):
|
||||
blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj1)
|
||||
|
||||
|
||||
@@ -90,6 +90,15 @@ class OpenUri(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class CloseError(bpy.types.Operator):
|
||||
bl_idname = "bim.close_error"
|
||||
bl_label = "Close Error"
|
||||
|
||||
def execute(self, context):
|
||||
blenderbim.last_error = None
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SelectURIAttribute(bpy.types.Operator):
|
||||
bl_idname = "bim.select_uri_attribute"
|
||||
bl_label = "Select URI Attribute"
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
import os
|
||||
import bpy
|
||||
import addon_utils
|
||||
import platform
|
||||
from pathlib import Path
|
||||
from bpy.types import Panel
|
||||
from bpy.props import StringProperty, IntProperty, BoolProperty
|
||||
@@ -29,8 +30,9 @@ from ifcopenshell.util.doc import (
|
||||
get_attribute_doc,
|
||||
)
|
||||
from . import ifc
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim import get_debug_info
|
||||
import blenderbim.bim
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim.helper import IfcHeaderExtractor
|
||||
from blenderbim.bim.prop import Attribute
|
||||
|
||||
@@ -193,18 +195,19 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
|
||||
pdf_command: StringProperty(name="PDF Command", description='E.g. [["firefox", "path"]]')
|
||||
spreadsheet_command: StringProperty(name="Spreadsheet Command", description='E.g. [["libreoffice", "path"]]')
|
||||
openlca_port: IntProperty(name="OpenLCA IPC Port", default=8080)
|
||||
should_hide_empty_props: BoolProperty(name="Should Hide Empty Properties", default=True)
|
||||
should_setup_workspace: BoolProperty(name="Should Setup Workspace Layout for BIM", default=True)
|
||||
should_hide_empty_props: BoolProperty(name="Hide Empty Properties", default=True)
|
||||
should_setup_workspace: BoolProperty(name="Setup Workspace Layout for BIM", default=True)
|
||||
activate_workspace: BoolProperty(name="Activate BIM Workspace on Startup", default=True)
|
||||
should_setup_toolbar: BoolProperty(
|
||||
name="Always Show Toolbar In 3D Viewport",
|
||||
default=True,
|
||||
description="If disabled, the toolbar will only load when an IFC model is active",
|
||||
)
|
||||
should_play_chaching_sound: BoolProperty(
|
||||
name="Should Make A Cha-Ching Sound When Project Costs Updates", default=False
|
||||
name="Play A Cha-Ching Sound When Project Costs Updates", default=False
|
||||
)
|
||||
lock_grids_on_import: BoolProperty(name="Should Lock Grids By Default", default=True)
|
||||
spatial_elements_unselectable: BoolProperty(name="Should Make Spatial Elements Unselectable By Default", default=True)
|
||||
lock_grids_on_import: BoolProperty(name="Lock Grids By Default", default=True)
|
||||
spatial_elements_unselectable: BoolProperty(name="Make Spatial Elements Unselectable By Default", default=True)
|
||||
decorations_colour: bpy.props.FloatVectorProperty(
|
||||
name="Decorations Colour", subtype="COLOR", default=(1, 1, 1, 1), min=0.0, max=1.0, size=4
|
||||
)
|
||||
@@ -281,6 +284,8 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
|
||||
row = layout.row()
|
||||
row.prop(self, "should_setup_workspace")
|
||||
row = layout.row()
|
||||
row.prop(self, "activate_workspace")
|
||||
row = layout.row()
|
||||
row.prop(self, "should_setup_toolbar")
|
||||
row = layout.row()
|
||||
row.prop(self, "should_play_chaching_sound")
|
||||
@@ -289,8 +294,6 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
|
||||
row = layout.row()
|
||||
row.prop(self, "spatial_elements_unselectable")
|
||||
|
||||
|
||||
|
||||
row = layout.row()
|
||||
row.prop(context.scene.BIMProjectProperties, "should_disable_undo_on_save")
|
||||
row = layout.row()
|
||||
@@ -319,28 +322,30 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
|
||||
row.prop(context.scene.BIMProperties, "data_dir")
|
||||
row.operator("bim.select_data_dir", icon="FILE_FOLDER", text="")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row = self.layout.row()
|
||||
row.prop(context.scene.BIMProperties, "pset_dir")
|
||||
row = self.layout.row(align=True)
|
||||
row = self.layout.row()
|
||||
row.prop(context.scene.DocProperties, "sheets_dir")
|
||||
row = self.layout.row(align=True)
|
||||
row = self.layout.row()
|
||||
row.prop(context.scene.DocProperties, "layouts_dir")
|
||||
row = self.layout.row(align=True)
|
||||
row = self.layout.row()
|
||||
row.prop(context.scene.DocProperties, "titleblocks_dir")
|
||||
row = self.layout.row(align=True)
|
||||
row = self.layout.row()
|
||||
row.prop(context.scene.DocProperties, "drawings_dir")
|
||||
row = self.layout.row(align=True)
|
||||
row = self.layout.row()
|
||||
row.prop(context.scene.DocProperties, "stylesheet_path")
|
||||
row = self.layout.row(align=True)
|
||||
row = self.layout.row()
|
||||
row.prop(context.scene.DocProperties, "markers_path")
|
||||
row = self.layout.row(align=True)
|
||||
row = self.layout.row()
|
||||
row.prop(context.scene.DocProperties, "symbols_path")
|
||||
row = self.layout.row(align=True)
|
||||
row = self.layout.row()
|
||||
row.prop(context.scene.DocProperties, "patterns_path")
|
||||
row = self.layout.row(align=True)
|
||||
row = self.layout.row()
|
||||
row.prop(context.scene.DocProperties, "shadingstyles_path")
|
||||
row = self.layout.row(align=True)
|
||||
row = self.layout.row()
|
||||
row.prop(context.scene.DocProperties, "shadingstyle_default")
|
||||
row = self.layout.row()
|
||||
row.prop(context.scene.DocProperties, "drawing_font")
|
||||
|
||||
|
||||
# Scene panel groups
|
||||
@@ -402,6 +407,21 @@ class BIM_PT_tabs(Panel):
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(aprops, "tab", text="")
|
||||
|
||||
if blenderbim.last_error:
|
||||
box = self.layout.box()
|
||||
box.alert=True
|
||||
row = box.row(align=True)
|
||||
row.label(text="BlenderBIM experienced an error :(", icon="ERROR")
|
||||
row.operator("bim.close_error", text="", icon="CANCEL")
|
||||
if platform.system() == "Windows":
|
||||
box.operator("wm.console_toggle", text="View the console for full logs.", icon="CONSOLE")
|
||||
else:
|
||||
box.label(text="View the console for full logs.", icon="CONSOLE")
|
||||
box.operator("bim.copy_debug_information", text="Copy Error Message To Clipboard")
|
||||
op = box.operator("bim.open_uri", text="How Can I Fix This?")
|
||||
op.uri = "https://docs.blenderbim.org/users/troubleshooting.html"
|
||||
|
||||
except:
|
||||
pass # Prior to load_post, we may not have any area properties setup
|
||||
|
||||
@@ -518,6 +538,20 @@ class BIM_PT_tab_status(Panel):
|
||||
pass
|
||||
|
||||
|
||||
class BIM_PT_tab_qto(Panel):
|
||||
bl_label = "Quantity Take-off"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return tool.Blender.is_tab(context, "SCHEDULING") and tool.Ifc.get()
|
||||
|
||||
def draw(self, context):
|
||||
pass
|
||||
|
||||
|
||||
class BIM_PT_tab_resources(Panel):
|
||||
bl_label = "Resources"
|
||||
bl_space_type = "PROPERTIES"
|
||||
@@ -724,6 +758,21 @@ class BIM_PT_tab_parametric_geometry(Panel):
|
||||
pass
|
||||
|
||||
|
||||
class BIM_PT_tab_object_materials(Panel):
|
||||
bl_label = "Object Materials"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_order = 1
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return tool.Blender.is_tab(context, "GEOMETRY") and tool.Ifc.get()
|
||||
|
||||
def draw(self, context):
|
||||
pass
|
||||
|
||||
|
||||
class BIM_PT_tab_materials(Panel):
|
||||
bl_label = "Materials"
|
||||
bl_space_type = "PROPERTIES"
|
||||
|
||||
@@ -1,183 +1,232 @@
|
||||
def add_cost_schedule(ifc, name, predefined_type):
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import annotations
|
||||
from typing import TYPE_CHECKING, Optional, Union
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import blenderbim.tool as tool
|
||||
|
||||
|
||||
def add_cost_schedule(ifc: tool.Ifc, name, predefined_type):
|
||||
ifc.run("cost.add_cost_schedule", name=name, predefined_type=predefined_type)
|
||||
|
||||
|
||||
def edit_cost_schedule(ifc, cost, cost_schedule):
|
||||
def edit_cost_schedule(ifc: tool.Ifc, cost: tool.Cost, cost_schedule: ifcopenshell.entity_instance):
|
||||
attributes = cost.get_cost_schedule_attributes()
|
||||
ifc.run("cost.edit_cost_schedule", cost_schedule=cost_schedule, attributes=attributes)
|
||||
cost.disable_editing_cost_schedule()
|
||||
|
||||
|
||||
def disable_editing_cost_schedule(cost):
|
||||
def disable_editing_cost_schedule(cost: tool.Cost):
|
||||
cost.disable_editing_cost_schedule()
|
||||
|
||||
|
||||
def remove_cost_schedule(ifc, cost_schedule):
|
||||
def remove_cost_schedule(ifc: tool.Ifc, cost: tool.Cost, cost_schedule: ifcopenshell.entity_instance) -> None:
|
||||
ifc.run("cost.remove_cost_schedule", cost_schedule=cost_schedule)
|
||||
cost.remove_stored_schedule_columns(cost_schedule)
|
||||
|
||||
|
||||
def enable_editing_cost_schedule_attributes(cost, cost_schedule):
|
||||
def enable_editing_cost_schedule_attributes(cost: tool.Cost, cost_schedule: ifcopenshell.entity_instance):
|
||||
cost.load_cost_schedule_attributes(cost_schedule)
|
||||
cost.enable_editing_cost_schedule_attributes(cost_schedule)
|
||||
|
||||
|
||||
def enable_editing_cost_items(cost, cost_schedule):
|
||||
def enable_editing_cost_items(cost: tool.Cost, cost_schedule: ifcopenshell.entity_instance):
|
||||
cost.enable_editing_cost_items(cost_schedule)
|
||||
cost.load_active_schedule_columns()
|
||||
cost.load_cost_schedule_tree()
|
||||
cost.play_sound()
|
||||
|
||||
|
||||
def add_summary_cost_item(ifc, cost, cost_schedule):
|
||||
def add_summary_cost_item(ifc: tool.Ifc, cost: tool.Cost, cost_schedule: ifcopenshell.entity_instance):
|
||||
ifc.run("cost.add_cost_item", cost_schedule=cost_schedule)
|
||||
cost.load_cost_schedule_tree()
|
||||
# cost.play_sound()
|
||||
|
||||
|
||||
def add_cost_item(ifc, cost, cost_item):
|
||||
def add_cost_item(ifc: tool.Ifc, cost: tool.Cost, cost_item: ifcopenshell.entity_instance):
|
||||
ifc.run("cost.add_cost_item", cost_item=cost_item)
|
||||
cost.load_cost_schedule_tree()
|
||||
# cost.enable_editing_cost_schedule_attributes(cost_schedule)
|
||||
|
||||
|
||||
def expand_cost_item(cost, cost_item):
|
||||
def expand_cost_item(cost: tool.Cost, cost_item: ifcopenshell.entity_instance):
|
||||
cost.expand_cost_item(cost_item)
|
||||
cost.load_cost_schedule_tree()
|
||||
|
||||
|
||||
def expand_cost_items(cost):
|
||||
def expand_cost_items(cost: tool.Cost):
|
||||
cost.expand_cost_items()
|
||||
cost.load_cost_schedule_tree()
|
||||
|
||||
|
||||
def contract_cost_item(cost, cost_item):
|
||||
def contract_cost_item(cost: tool.Cost, cost_item: ifcopenshell.entity_instance):
|
||||
cost.contract_cost_item(cost_item)
|
||||
cost.load_cost_schedule_tree()
|
||||
|
||||
|
||||
def contract_cost_items(cost):
|
||||
def contract_cost_items(cost: tool.Cost):
|
||||
cost.contract_cost_items()
|
||||
cost.load_cost_schedule_tree()
|
||||
|
||||
|
||||
def remove_cost_item(ifc, cost, cost_item_id):
|
||||
def remove_cost_item(ifc: tool.Ifc, cost: tool.Cost, cost_item_id: int):
|
||||
cost_item = ifc.get().by_id(cost_item_id)
|
||||
ifc.run("cost.remove_cost_item", cost_item=cost_item)
|
||||
cost.clean_up_cost_item_tree(cost_item_id)
|
||||
cost.load_cost_schedule_tree()
|
||||
|
||||
|
||||
def enable_editing_cost_item_attributes(cost, cost_item):
|
||||
def enable_editing_cost_item_attributes(cost: tool.Cost, cost_item: ifcopenshell.entity_instance):
|
||||
cost.enable_editing_cost_item_attributes(cost_item)
|
||||
cost.load_cost_item_attributes(cost_item)
|
||||
|
||||
|
||||
def disable_editing_cost_item(cost):
|
||||
def disable_editing_cost_item(cost: tool.Cost):
|
||||
cost.disable_editing_cost_item()
|
||||
|
||||
|
||||
def edit_cost_item(ifc, cost):
|
||||
def edit_cost_item(ifc: tool.Ifc, cost: tool.Cost):
|
||||
attributes = cost.get_cost_item_attributes()
|
||||
ifc.run("cost.edit_cost_item", cost_item=cost.get_active_cost_item(), attributes=attributes)
|
||||
cost.disable_editing_cost_item()
|
||||
cost.load_cost_schedule_tree()
|
||||
|
||||
|
||||
def assign_cost_item_type(ifc, cost, spatial, cost_item, prop_name):
|
||||
product_types = spatial.get_selected_product_types()
|
||||
[
|
||||
def assign_cost_item_type(
|
||||
ifc: tool.Ifc, cost: tool.Cost, spatial: tool.Spatial, cost_item: ifcopenshell.entity_instance, prop_name
|
||||
) -> list[ifcopenshell.entity_instance]:
|
||||
"""
|
||||
Returns:
|
||||
List of found product types.
|
||||
"""
|
||||
product_types = list(spatial.get_selected_product_types())
|
||||
rels = [
|
||||
ifc.run("control.assign_control", relating_control=cost_item, related_object=product_type)
|
||||
for product_type in product_types
|
||||
]
|
||||
cost.load_cost_item_types(cost_item)
|
||||
return product_types
|
||||
|
||||
|
||||
def unassign_cost_item_type(ifc, cost, spatial, cost_item, product_types):
|
||||
def unassign_cost_item_type(
|
||||
ifc: tool.Ifc,
|
||||
cost: tool.Cost,
|
||||
spatial: tool.Spatial,
|
||||
cost_item: ifcopenshell.entity_instance,
|
||||
product_types: Optional[list[ifcopenshell.entity_instance]] = None,
|
||||
) -> list[ifcopenshell.entity_instance]:
|
||||
"""
|
||||
Returns:
|
||||
List of found product types.
|
||||
"""
|
||||
if not product_types:
|
||||
product_types = spatial.get_selected_product_types()
|
||||
product_types = list(spatial.get_selected_product_types())
|
||||
[
|
||||
ifc.run("control.unassign_control", relating_control=cost_item, related_object=product_type)
|
||||
for product_type in product_types
|
||||
]
|
||||
cost.load_cost_item_types(cost_item)
|
||||
return product_types
|
||||
|
||||
|
||||
def load_cost_item_types(cost):
|
||||
def load_cost_item_types(cost: tool.Cost):
|
||||
cost_item = cost.get_active_cost_item()
|
||||
cost.load_cost_item_types(cost_item)
|
||||
|
||||
|
||||
def assign_cost_item_quantity(ifc, cost, cost_item, related_object_type, prop_name):
|
||||
def assign_cost_item_quantity(
|
||||
ifc: tool.Ifc, cost: tool.Cost, cost_item: ifcopenshell.entity_instance, related_object_type, prop_name
|
||||
):
|
||||
products = cost.get_products(related_object_type)
|
||||
if products:
|
||||
ifc.run("cost.assign_cost_item_quantity", cost_item=cost_item, products=products, prop_name=prop_name)
|
||||
cost.load_cost_item_quantity_assignments(cost_item, related_object_type=related_object_type)
|
||||
|
||||
|
||||
def load_cost_item_quantities(cost):
|
||||
def load_cost_item_quantities(cost: tool.Cost):
|
||||
cost.load_cost_item_quantities()
|
||||
|
||||
|
||||
def load_cost_item_element_quantities(cost):
|
||||
def load_cost_item_element_quantities(cost: tool.Cost):
|
||||
cost_item = cost.get_highlighted_cost_item()
|
||||
cost.load_cost_item_quantity_assignments(cost_item, related_object_type="PRODUCT")
|
||||
|
||||
|
||||
def load_cost_item_task_quantities(cost):
|
||||
def load_cost_item_task_quantities(cost: tool.Cost):
|
||||
cost_item = cost.get_highlighted_cost_item()
|
||||
cost.load_cost_item_quantity_assignments(cost_item, related_object_type="PROCESS")
|
||||
|
||||
|
||||
def load_cost_item_resource_quantities(cost):
|
||||
def load_cost_item_resource_quantities(cost: tool.Cost):
|
||||
cost_item = cost.get_highlighted_cost_item()
|
||||
cost.load_cost_item_quantity_assignments(cost_item, related_object_type="RESOURCE")
|
||||
|
||||
|
||||
def assign_cost_value(ifc, cost_item, cost_rate):
|
||||
def assign_cost_value(ifc: tool.Ifc, cost_item: ifcopenshell.entity_instance, cost_rate):
|
||||
ifc.run("cost.assign_cost_value", cost_item=cost_item, cost_rate=cost_rate)
|
||||
|
||||
|
||||
def load_schedule_of_rates(cost, schedule_of_rates):
|
||||
def load_schedule_of_rates(cost: tool.Cost, schedule_of_rates):
|
||||
cost.load_schedule_of_rates_tree(schedule_of_rates)
|
||||
|
||||
|
||||
def unassign_cost_item_quantity(ifc, cost, cost_item, products):
|
||||
def unassign_cost_item_quantity(ifc: tool.Ifc, cost: tool.Cost, cost_item: ifcopenshell.entity_instance, products):
|
||||
ifc.run("cost.unassign_cost_item_quantity", cost_item=cost_item, products=products)
|
||||
cost.load_cost_item_quantities()
|
||||
|
||||
|
||||
def enable_editing_cost_item_quantities(cost, cost_item):
|
||||
def enable_editing_cost_item_quantities(cost: tool.Cost, cost_item: ifcopenshell.entity_instance):
|
||||
cost.enable_editing_cost_item_quantities(cost_item)
|
||||
|
||||
|
||||
def enable_editing_cost_item_values(cost, cost_item):
|
||||
def enable_editing_cost_item_values(cost: tool.Cost, cost_item: ifcopenshell.entity_instance):
|
||||
cost.enable_editing_cost_item_values(cost_item)
|
||||
|
||||
|
||||
def add_cost_item_quantity(ifc, cost_item, ifc_class):
|
||||
def add_cost_item_quantity(ifc: tool.Ifc, cost_item: ifcopenshell.entity_instance, ifc_class):
|
||||
ifc.run("cost.add_cost_item_quantity", cost_item=cost_item, ifc_class=ifc_class)
|
||||
|
||||
|
||||
def remove_cost_item_quantity(ifc, cost_item, physical_quantity):
|
||||
def remove_cost_item_quantity(ifc: tool.Ifc, cost_item: ifcopenshell.entity_instance, physical_quantity):
|
||||
ifc.run("cost.remove_cost_item_quantity", cost_item=cost_item, physical_quantity=physical_quantity)
|
||||
|
||||
|
||||
def enable_editing_cost_item_quantity(cost, physical_quantity):
|
||||
def enable_editing_cost_item_quantity(cost: tool.Cost, physical_quantity):
|
||||
cost.load_cost_item_quantity_attributes(physical_quantity)
|
||||
cost.enable_editing_cost_item_quantity(physical_quantity)
|
||||
|
||||
|
||||
def disable_editing_cost_item_quantity(cost):
|
||||
def disable_editing_cost_item_quantity(cost: tool.Cost):
|
||||
cost.disable_editing_cost_item_quantity()
|
||||
|
||||
|
||||
def edit_cost_item_quantity(ifc, cost, physical_quantity):
|
||||
def edit_cost_item_quantity(ifc: tool.Ifc, cost: tool.Cost, physical_quantity):
|
||||
attributes = cost.get_cost_item_quantity_attributes()
|
||||
ifc.run("cost.edit_cost_item_quantity", physical_quantity=physical_quantity, attributes=attributes)
|
||||
cost.disable_editing_cost_item_quantity()
|
||||
cost.load_cost_item_quantities()
|
||||
|
||||
|
||||
def add_cost_value(ifc, cost, parent, cost_type, cost_category):
|
||||
def add_cost_value(ifc: tool.Ifc, cost: tool.Cost, parent, cost_type, cost_category):
|
||||
value = ifc.run("cost.add_cost_value", parent=parent)
|
||||
ifc.run(
|
||||
"cost.edit_cost_value",
|
||||
@@ -186,82 +235,84 @@ def add_cost_value(ifc, cost, parent, cost_type, cost_category):
|
||||
)
|
||||
|
||||
|
||||
def remove_cost_value(ifc, parent, cost_value):
|
||||
def remove_cost_value(ifc: tool.Ifc, parent, cost_value):
|
||||
ifc.run("cost.remove_cost_value", parent=parent, cost_value=cost_value)
|
||||
|
||||
|
||||
def enable_editing_cost_item_value(cost, cost_value):
|
||||
def enable_editing_cost_item_value(cost: tool.Cost, cost_value):
|
||||
cost.load_cost_item_value_attributes(cost_value)
|
||||
cost.enable_editing_cost_item_value(cost_value)
|
||||
|
||||
|
||||
def disable_editing_cost_item_value(cost):
|
||||
def disable_editing_cost_item_value(cost: tool.Cost):
|
||||
cost.disable_editing_cost_item_value()
|
||||
|
||||
|
||||
def enable_editing_cost_item_value_formula(cost, cost_value):
|
||||
def enable_editing_cost_item_value_formula(cost: tool.Cost, cost_value):
|
||||
cost.load_cost_item_value_formula_attributes(cost_value)
|
||||
cost.enable_editing_cost_item_value_formula(cost_value)
|
||||
|
||||
|
||||
def edit_cost_item_value_formula(ifc, cost, cost_value):
|
||||
def edit_cost_item_value_formula(ifc: tool.Ifc, cost: tool.Cost, cost_value):
|
||||
formula = cost.get_cost_item_value_formula()
|
||||
ifc.run("cost.edit_cost_value_formula", cost_value=cost_value, formula=formula)
|
||||
cost.disable_editing_cost_item_value()
|
||||
|
||||
|
||||
def edit_cost_value(ifc, cost, cost_value):
|
||||
def edit_cost_value(ifc: tool.Ifc, cost: tool.Cost, cost_value):
|
||||
attributes = cost.get_cost_value_attributes()
|
||||
ifc.run("cost.edit_cost_value", cost_value=cost_value, attributes=attributes)
|
||||
cost.disable_editing_cost_item_value()
|
||||
# cost.load_cost_item_values(cost.get_highlighted_cost_item())
|
||||
|
||||
|
||||
def copy_cost_item_values(ifc, cost, source, destination):
|
||||
def copy_cost_item_values(ifc: tool.Ifc, cost: tool.Cost, source, destination):
|
||||
ifc.run("cost.copy_cost_item_values", source=source, destination=destination)
|
||||
|
||||
|
||||
def select_cost_item_products(cost, spatial, cost_item):
|
||||
def select_cost_item_products(cost: tool.Cost, spatial: tool.Spatial, cost_item: ifcopenshell.entity_instance):
|
||||
is_deep = cost.show_nested_cost_item_elements()
|
||||
products = cost.get_cost_item_products(cost_item, is_deep)
|
||||
spatial.select_products(products)
|
||||
|
||||
|
||||
def select_cost_schedule_products(cost, spatial, cost_schedule):
|
||||
def select_cost_schedule_products(cost: tool.Cost, spatial: tool.Spatial, cost_schedule: ifcopenshell.entity_instance):
|
||||
products = cost.get_cost_schedule_products(cost_schedule)
|
||||
spatial.select_products(products)
|
||||
|
||||
|
||||
def import_cost_schedule_csv(cost, file_path, is_schedule_of_rates):
|
||||
def import_cost_schedule_csv(cost: tool.Cost, file_path, is_schedule_of_rates):
|
||||
cost.import_cost_schedule_csv(file_path, is_schedule_of_rates)
|
||||
|
||||
|
||||
def add_cost_column(cost, name):
|
||||
def add_cost_column(cost: tool.Cost, name):
|
||||
cost.add_cost_column(name)
|
||||
|
||||
|
||||
def remove_cost_column(cost, name):
|
||||
def remove_cost_column(cost: tool.Cost, name):
|
||||
cost.remove_cost_column(name)
|
||||
|
||||
|
||||
def expand_cost_item_rate(cost, cost_item):
|
||||
def expand_cost_item_rate(cost: tool.Cost, cost_item: ifcopenshell.entity_instance):
|
||||
cost.expand_cost_item_rate(cost_item)
|
||||
|
||||
|
||||
def contract_cost_item_rate(cost, cost_item):
|
||||
def contract_cost_item_rate(cost: tool.Cost, cost_item: ifcopenshell.entity_instance):
|
||||
cost.contract_cost_item_rate(cost_item)
|
||||
|
||||
|
||||
def calculate_cost_item_resource_value(ifc, cost_item):
|
||||
def calculate_cost_item_resource_value(ifc: tool.Ifc, cost_item: ifcopenshell.entity_instance):
|
||||
ifc.run("cost.calculate_cost_item_resource_value", cost_item=cost_item)
|
||||
|
||||
|
||||
def export_cost_schedules(cost, filepath, format, cost_schedule=None):
|
||||
def export_cost_schedules(cost: tool.Cost, filepath: str, format: str, cost_schedule=None):
|
||||
cost.play_sound()
|
||||
return cost.export_cost_schedules(filepath, format, cost_schedule)
|
||||
|
||||
|
||||
def clear_cost_item_assignments(ifc, cost, cost_item, related_object_type):
|
||||
def clear_cost_item_assignments(
|
||||
ifc: tool.Ifc, cost: tool.Cost, cost_item: ifcopenshell.entity_instance, related_object_type
|
||||
):
|
||||
products = cost.get_cost_item_assignments(cost_item, filter_by_type=related_object_type)
|
||||
if products:
|
||||
ifc.run("cost.unassign_cost_item_quantity", cost_item=cost_item, products=products)
|
||||
@@ -269,7 +320,7 @@ def clear_cost_item_assignments(ifc, cost, cost_item, related_object_type):
|
||||
cost.load_cost_schedule_tree()
|
||||
|
||||
|
||||
def select_unassigned_products(ifc, cost, spatial):
|
||||
def select_unassigned_products(ifc: tool.Ifc, cost: tool.Cost, spatial: tool.Spatial):
|
||||
spatial.deselect_objects()
|
||||
products = ifc.get().by_type("IfcElement")
|
||||
cost_schedule = cost.get_active_cost_schedule()
|
||||
@@ -277,11 +328,11 @@ def select_unassigned_products(ifc, cost, spatial):
|
||||
spatial.select_products(selection)
|
||||
|
||||
|
||||
def load_product_cost_items(cost, product):
|
||||
def load_product_cost_items(cost: tool.Cost, product):
|
||||
cost.load_product_cost_items(product)
|
||||
|
||||
|
||||
def highlight_product_cost_item(spatial, cost, cost_item):
|
||||
def highlight_product_cost_item(spatial: tool.Spatial, cost: tool.Cost, cost_item: ifcopenshell.entity_instance):
|
||||
cost_schedule = cost.get_cost_schedule(cost_item)
|
||||
is_cost_schedule_active = cost.is_cost_schedule_active(cost_schedule)
|
||||
if is_cost_schedule_active:
|
||||
@@ -290,7 +341,7 @@ def highlight_product_cost_item(spatial, cost, cost_item):
|
||||
return "Cost schedule is not active"
|
||||
|
||||
|
||||
def change_parent_cost_item(ifc, cost, new_parent):
|
||||
def change_parent_cost_item(ifc: tool.Ifc, cost: tool.Cost, new_parent):
|
||||
cost_item = cost.get_active_cost_item()
|
||||
if cost_item and cost.is_root_cost_item(cost_item):
|
||||
return "Cannot change root cost item"
|
||||
@@ -300,7 +351,7 @@ def change_parent_cost_item(ifc, cost, new_parent):
|
||||
cost.load_cost_schedule_tree()
|
||||
|
||||
|
||||
def copy_cost_item(ifc, cost):
|
||||
def copy_cost_item(ifc: tool.Ifc, cost: tool.Cost):
|
||||
cost_item = cost.get_highlighted_cost_item()
|
||||
if cost_item:
|
||||
cost_item = ifc.run("cost.copy_cost_item", cost_item=cost_item)
|
||||
@@ -308,7 +359,7 @@ def copy_cost_item(ifc, cost):
|
||||
cost.load_cost_schedule_tree()
|
||||
|
||||
|
||||
def add_currency(ifc, cost):
|
||||
def add_currency(ifc: tool.Ifc, cost: tool.Cost):
|
||||
unit = ifc.run("unit.add_monetary_unit")
|
||||
attributes = cost.get_currency_attributes()
|
||||
ifc.run("unit.edit_monetary_unit", unit=unit, attributes=attributes)
|
||||
|
||||
@@ -16,36 +16,44 @@
|
||||
# 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/>.
|
||||
|
||||
from __future__ import annotations
|
||||
from pathlib import Path
|
||||
import ifcopenshell
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import blenderbim.tool as tool
|
||||
|
||||
|
||||
def enable_editing_text(drawing, obj=None):
|
||||
def enable_editing_text(drawing: tool.Drawing, obj: bpy.types.Object) -> None:
|
||||
drawing.enable_editing_text(obj)
|
||||
drawing.import_text_attributes(obj)
|
||||
|
||||
|
||||
def disable_editing_text(drawing, obj=None):
|
||||
def disable_editing_text(drawing: tool.Drawing, obj: bpy.types.Object) -> None:
|
||||
drawing.disable_editing_text(obj)
|
||||
|
||||
|
||||
def edit_text(drawing, obj=None):
|
||||
def edit_text(drawing: tool.Drawing, obj: bpy.types.Object) -> None:
|
||||
drawing.synchronise_ifc_and_text_attributes(obj)
|
||||
drawing.update_text_size_pset(obj)
|
||||
drawing.update_text_value(obj)
|
||||
drawing.disable_editing_text(obj)
|
||||
|
||||
|
||||
def enable_editing_assigned_product(drawing, obj=None):
|
||||
def enable_editing_assigned_product(drawing: tool.Drawing, obj: bpy.types.Object) -> None:
|
||||
drawing.enable_editing_assigned_product(obj)
|
||||
drawing.import_assigned_product(obj)
|
||||
|
||||
|
||||
def disable_editing_assigned_product(drawing, obj=None):
|
||||
def disable_editing_assigned_product(drawing: tool.Drawing, obj: bpy.types.Object) -> None:
|
||||
drawing.disable_editing_assigned_product(obj)
|
||||
|
||||
|
||||
def edit_assigned_product(ifc, drawing, obj=None, product=None):
|
||||
def edit_assigned_product(
|
||||
ifc: tool.Ifc, drawing: tool.Drawing, obj: bpy.types.Object, product: Optional[ifcopenshell.entity_instance] = None
|
||||
) -> None:
|
||||
element = ifc.get_entity(obj)
|
||||
existing_product = drawing.get_assigned_product(element)
|
||||
if existing_product != product:
|
||||
@@ -58,16 +66,16 @@ def edit_assigned_product(ifc, drawing, obj=None, product=None):
|
||||
drawing.disable_editing_assigned_product(obj)
|
||||
|
||||
|
||||
def load_sheets(drawing):
|
||||
def load_sheets(drawing: tool.Drawing) -> None:
|
||||
drawing.import_sheets()
|
||||
drawing.enable_editing_sheets()
|
||||
|
||||
|
||||
def disable_editing_sheets(drawing):
|
||||
def disable_editing_sheets(drawing: tool.Drawing) -> None:
|
||||
drawing.disable_editing_sheets()
|
||||
|
||||
|
||||
def add_sheet(ifc, drawing, titleblock: ifcopenshell.entity_instance):
|
||||
def add_sheet(ifc: tool.Ifc, drawing, titleblock: ifcopenshell.entity_instance) -> None:
|
||||
sheet = ifc.run("document.add_information")
|
||||
layout = ifc.run("document.add_reference", information=sheet)
|
||||
titleblock_reference = ifc.run("document.add_reference", information=sheet)
|
||||
@@ -93,7 +101,7 @@ def add_sheet(ifc, drawing, titleblock: ifcopenshell.entity_instance):
|
||||
drawing.import_sheets()
|
||||
|
||||
|
||||
def regenerate_sheet(drawing, sheet=None):
|
||||
def regenerate_sheet(drawing: tool.Drawing, sheet: ifcopenshell.entity_instance) -> None:
|
||||
titleblock_uri = drawing.get_document_uri(sheet, "TITLEBLOCK")
|
||||
drawing.create_svg_sheet(sheet, drawing.sanitise_filename(Path(titleblock_uri).stem))
|
||||
try:
|
||||
@@ -104,11 +112,11 @@ def regenerate_sheet(drawing, sheet=None):
|
||||
drawing.delete_file(path_layout)
|
||||
|
||||
|
||||
def open_sheet(drawing, sheet=None):
|
||||
def open_sheet(drawing: tool.Drawing, sheet: ifcopenshell.entity_instance) -> None:
|
||||
drawing.open_layout_svg(drawing.get_document_uri(sheet, "LAYOUT"))
|
||||
|
||||
|
||||
def remove_sheet(ifc, drawing, sheet=None):
|
||||
def remove_sheet(ifc: tool.Ifc, drawing: tool.Drawing, sheet: ifcopenshell.entity_instance) -> None:
|
||||
for reference in drawing.get_document_references(sheet):
|
||||
if drawing.get_reference_description(reference) in ("LAYOUT", "SHEET", "REVISION", "RASTER"):
|
||||
uri = ifc.resolve_uri(drawing.get_document_uri(reference))
|
||||
@@ -118,7 +126,7 @@ def remove_sheet(ifc, drawing, sheet=None):
|
||||
drawing.import_sheets()
|
||||
|
||||
|
||||
def rename_sheet(ifc, drawing, sheet: ifcopenshell.entity_instance, identification: str, name: str) -> None:
|
||||
def rename_sheet(ifc: tool.Ifc, drawing, sheet: ifcopenshell.entity_instance, identification: str, name: str) -> None:
|
||||
if ifc.get_schema() == "IFC2X3":
|
||||
attributes = {"DocumentId": identification, "Name": name}
|
||||
else:
|
||||
@@ -144,30 +152,32 @@ def rename_sheet(ifc, drawing, sheet: ifcopenshell.entity_instance, identificati
|
||||
drawing.move_file(old_location, ifc.resolve_uri(new_location))
|
||||
|
||||
|
||||
def rename_reference(ifc, drawing, reference=None, identification=None):
|
||||
def rename_reference(
|
||||
ifc: tool.Ifc, drawing: tool.Drawing, reference: ifcopenshell.entity_instance, identification: str
|
||||
) -> None:
|
||||
attributes = drawing.generate_reference_attributes(reference, Identification=identification)
|
||||
ifc.run("document.edit_reference", reference=reference, attributes=attributes)
|
||||
|
||||
|
||||
def load_schedules(drawing):
|
||||
def load_schedules(drawing: tool.Drawing) -> None:
|
||||
drawing.import_documents("SCHEDULE")
|
||||
drawing.enable_editing_schedules()
|
||||
|
||||
|
||||
def load_references(drawing):
|
||||
def load_references(drawing: tool.Drawing) -> None:
|
||||
drawing.import_documents("REFERENCE")
|
||||
drawing.enable_editing_references()
|
||||
|
||||
|
||||
def disable_editing_schedules(drawing):
|
||||
def disable_editing_schedules(drawing: tool.Drawing) -> None:
|
||||
drawing.disable_editing_schedules()
|
||||
|
||||
|
||||
def disable_editing_references(drawing):
|
||||
def disable_editing_references(drawing: tool.Drawing) -> None:
|
||||
drawing.disable_editing_references()
|
||||
|
||||
|
||||
def add_document(ifc, drawing, document_type, uri=None):
|
||||
def add_document(ifc: tool.Ifc, drawing: tool.Drawing, document_type: tool.Drawing.DOCUMENT_TYPE, uri: str) -> None:
|
||||
document = ifc.run("document.add_information")
|
||||
reference = ifc.run("document.add_reference", information=document)
|
||||
name = drawing.get_path_filename(uri)
|
||||
@@ -180,34 +190,43 @@ def add_document(ifc, drawing, document_type, uri=None):
|
||||
drawing.import_documents(document_type)
|
||||
|
||||
|
||||
def remove_document(ifc, drawing, document_type, document=None):
|
||||
def remove_document(
|
||||
ifc: tool.Ifc,
|
||||
drawing: tool.Drawing,
|
||||
document_type: tool.Drawing.DOCUMENT_TYPE,
|
||||
document: ifcopenshell.entity_instance,
|
||||
) -> None:
|
||||
ifc.run("document.remove_information", information=document)
|
||||
drawing.import_documents(document_type)
|
||||
|
||||
|
||||
def open_schedule(drawing, schedule=None):
|
||||
def open_schedule(drawing: tool.Drawing, schedule: ifcopenshell.entity_instance) -> None:
|
||||
drawing.open_spreadsheet(drawing.get_document_uri(schedule))
|
||||
|
||||
|
||||
def open_reference(drawing, reference=None):
|
||||
def open_reference(drawing: tool.Drawing, reference: ifcopenshell.entity_instance) -> None:
|
||||
drawing.open_svg(drawing.get_document_uri(reference))
|
||||
|
||||
|
||||
def update_document_name(ifc, drawing, document=None, name=None):
|
||||
def update_document_name(
|
||||
ifc: tool.Ifc, drawing: tool.Drawing, document: ifcopenshell.entity_instance, name=None
|
||||
) -> None:
|
||||
if drawing.get_name(document) != name:
|
||||
ifc.run("document.edit_information", information=document, attributes={"Name": name})
|
||||
|
||||
|
||||
def load_drawings(drawing):
|
||||
def load_drawings(drawing: tool.Drawing) -> None:
|
||||
drawing.import_drawings()
|
||||
drawing.enable_editing_drawings()
|
||||
|
||||
|
||||
def disable_editing_drawings(drawing):
|
||||
def disable_editing_drawings(drawing: tool.Drawing) -> None:
|
||||
drawing.disable_editing_drawings()
|
||||
|
||||
|
||||
def add_drawing(ifc, collector, drawing, target_view=None, location_hint=None):
|
||||
def add_drawing(
|
||||
ifc: tool.Ifc, collector: tool.Collector, drawing: tool.Drawing, target_view=None, location_hint=None
|
||||
) -> None:
|
||||
drawing_name = drawing.ensure_unique_drawing_name(drawing.generate_drawing_name(target_view, location_hint))
|
||||
drawing_matrix = drawing.generate_drawing_matrix(target_view, location_hint)
|
||||
camera = drawing.create_camera(drawing_name, drawing_matrix, location_hint)
|
||||
@@ -265,7 +284,12 @@ def add_drawing(ifc, collector, drawing, target_view=None, location_hint=None):
|
||||
drawing.import_drawings()
|
||||
|
||||
|
||||
def duplicate_drawing(ifc, drawing_tool, drawing=None, should_duplicate_annotations=False):
|
||||
def duplicate_drawing(
|
||||
ifc: tool.Ifc,
|
||||
drawing_tool: tool.Drawing,
|
||||
drawing: ifcopenshell.entity_instance,
|
||||
should_duplicate_annotations: bool = False,
|
||||
) -> ifcopenshell.entity_instance:
|
||||
drawing_name = drawing_tool.ensure_unique_drawing_name(drawing_tool.get_name(drawing))
|
||||
new_drawing = ifc.run("root.copy_class", product=drawing)
|
||||
drawing_tool.copy_representation(drawing, new_drawing)
|
||||
@@ -302,7 +326,7 @@ def duplicate_drawing(ifc, drawing_tool, drawing=None, should_duplicate_annotati
|
||||
return new_drawing
|
||||
|
||||
|
||||
def remove_drawing(ifc, drawing_tool, drawing=None):
|
||||
def remove_drawing(ifc: tool.Ifc, drawing_tool: tool.Drawing, drawing: ifcopenshell.entity_instance) -> None:
|
||||
if drawing_tool.is_active_drawing(drawing):
|
||||
drawing_tool.run_drawing_activate_model()
|
||||
|
||||
@@ -330,7 +354,9 @@ def remove_drawing(ifc, drawing_tool, drawing=None):
|
||||
drawing_tool.import_drawings()
|
||||
|
||||
|
||||
def update_drawing_name(ifc, drawing_tool, drawing=None, name=None):
|
||||
def update_drawing_name(
|
||||
ifc: tool.Ifc, drawing_tool: tool.Drawing, drawing: ifcopenshell.entity_instance, name=None
|
||||
) -> None:
|
||||
if drawing_tool.get_name(drawing) != name:
|
||||
ifc.run("attribute.edit_attributes", product=drawing, attributes={"Name": name})
|
||||
group = drawing_tool.get_drawing_group(drawing)
|
||||
@@ -364,7 +390,13 @@ def update_drawing_name(ifc, drawing_tool, drawing=None, name=None):
|
||||
drawing_tool.import_sheets()
|
||||
|
||||
|
||||
def add_annotation(ifc, collector, drawing_tool, drawing=None, object_type=None):
|
||||
def add_annotation(
|
||||
ifc: tool.Ifc,
|
||||
collector: tool.Collector,
|
||||
drawing_tool: tool.Drawing,
|
||||
drawing: ifcopenshell.entity_instance,
|
||||
object_type: str,
|
||||
) -> None:
|
||||
target_view = drawing_tool.get_drawing_target_view(drawing)
|
||||
context = drawing_tool.get_annotation_context(target_view, object_type)
|
||||
if not context:
|
||||
@@ -387,12 +419,14 @@ def add_annotation(ifc, collector, drawing_tool, drawing=None, object_type=None)
|
||||
drawing_tool.enable_editing(obj)
|
||||
|
||||
|
||||
def build_schedule(drawing, schedule=None):
|
||||
def build_schedule(drawing: tool.Drawing, schedule: ifcopenshell.entity_instance) -> None:
|
||||
drawing.create_svg_schedule(schedule)
|
||||
drawing.open_svg(drawing.get_path_with_ext(drawing.get_document_uri(schedule), "svg"))
|
||||
|
||||
|
||||
def sync_references(ifc, collector, drawing_tool, drawing=None):
|
||||
def sync_references(
|
||||
ifc: tool.Ifc, collector: tool.Collector, drawing_tool: tool.Drawing, drawing: ifcopenshell.entity_instance
|
||||
) -> None:
|
||||
if not drawing_tool.has_annotation(drawing):
|
||||
return
|
||||
|
||||
@@ -437,13 +471,25 @@ def sync_references(ifc, collector, drawing_tool, drawing=None):
|
||||
drawing_tool.sync_object_representation(reference_obj)
|
||||
|
||||
|
||||
def select_assigned_product(drawing, context):
|
||||
def select_assigned_product(drawing: tool.Drawing, context: bpy.types.Context) -> None:
|
||||
drawing.select_assigned_product(context)
|
||||
|
||||
|
||||
def activate_drawing_view(ifc, drawing_tool, drawing):
|
||||
def activate_drawing_view(
|
||||
ifc: tool.Ifc, blender: tool.Blender, drawing_tool: tool.Drawing, drawing: ifcopenshell.entity_instance
|
||||
) -> None:
|
||||
camera = ifc.get_object(drawing)
|
||||
if not camera:
|
||||
camera = drawing_tool.import_drawing(drawing)
|
||||
drawing_tool.import_annotations_in_group(drawing_tool.get_drawing_group(drawing))
|
||||
blender.activate_camera(camera)
|
||||
drawing_tool.isolate_camera_collection(camera)
|
||||
try:
|
||||
blender.set_active_object(camera)
|
||||
except:
|
||||
raise CameraNotAvailableError()
|
||||
drawing_tool.activate_drawing(camera)
|
||||
|
||||
|
||||
class CameraNotAvailableError(Exception):
|
||||
pass
|
||||
|
||||
@@ -18,10 +18,11 @@
|
||||
|
||||
from __future__ import annotations
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
import blenderbim.core.tool as tool
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import blenderbim.tool as tool
|
||||
|
||||
|
||||
def edit_object_placement(
|
||||
@@ -37,8 +38,15 @@ def edit_object_placement(
|
||||
|
||||
|
||||
def add_representation(
|
||||
ifc, geometry, style, surveyor, obj=None, context=None, ifc_representation_class=None, profile_set_usage=None
|
||||
):
|
||||
ifc: tool.Ifc,
|
||||
geometry: tool.Geometry,
|
||||
style: tool.Style,
|
||||
surveyor: tool.Surveyor,
|
||||
obj: bpy.types.Object,
|
||||
context: ifcopenshell.entity_instance,
|
||||
ifc_representation_class: Optional[str] = None,
|
||||
profile_set_usage: Optional[ifcopenshell.entity_instance] = None,
|
||||
) -> ifcopenshell.entity_instance:
|
||||
element = ifc.get_entity(obj)
|
||||
if not element:
|
||||
return
|
||||
@@ -63,6 +71,9 @@ def add_representation(
|
||||
profile_set_usage=profile_set_usage,
|
||||
)
|
||||
|
||||
if not representation:
|
||||
raise IncompatibleRepresentationError()
|
||||
|
||||
if geometry.is_body_representation(representation):
|
||||
[geometry.run_style_add_style(obj=mat) for mat in geometry.get_object_materials_without_styles(obj)]
|
||||
ifc.run(
|
||||
@@ -86,15 +97,15 @@ def add_representation(
|
||||
|
||||
|
||||
def switch_representation(
|
||||
ifc,
|
||||
geometry,
|
||||
obj=None,
|
||||
representation=None,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
apply_openings=True,
|
||||
):
|
||||
ifc: tool.Ifc,
|
||||
geometry: tool.Geometry,
|
||||
obj: bpy.types.Object,
|
||||
representation: ifcopenshell.entity_instance,
|
||||
should_reload: bool = True,
|
||||
is_global: bool = True,
|
||||
should_sync_changes_first: bool = False,
|
||||
apply_openings: bool = True,
|
||||
) -> None:
|
||||
"""Function can switch to representation that wasn't yet assigned to that object. See #2766.
|
||||
|
||||
`should_sync_changes_first` - sync ifc representation with current state of `obj.data`;
|
||||
@@ -221,3 +232,7 @@ def edit_similar_opening_placement(geometry, opening=None, similar_openings=None
|
||||
old_placement = similar_opening.ObjectPlacement
|
||||
similar_opening.ObjectPlacement = opening.ObjectPlacement
|
||||
geometry.delete_opening_object_placement(old_placement)
|
||||
|
||||
|
||||
class IncompatibleRepresentationError(Exception):
|
||||
pass
|
||||
|
||||
@@ -21,10 +21,10 @@ def unlink_material(ifc, obj=None):
|
||||
ifc.unlink(obj=obj)
|
||||
|
||||
|
||||
def add_material(ifc, material, style, obj=None, name=None):
|
||||
def add_material(ifc, material, style, obj=None, name=None, category=None, description=None):
|
||||
if not obj:
|
||||
obj = material.add_default_material_object(name)
|
||||
ifc_material = ifc.run("material.add_material", name=material.get_name(obj))
|
||||
ifc_material = ifc.run("material.add_material", name=material.get_name(obj), category=category, description=description)
|
||||
ifc.link(ifc_material, obj)
|
||||
ifc_style = style.get_style(obj)
|
||||
if ifc_style:
|
||||
@@ -85,6 +85,7 @@ def enable_editing_material(material_tool, material):
|
||||
def edit_material(ifc, material_tool, material):
|
||||
attributes = material_tool.get_material_attributes()
|
||||
ifc.run("material.edit_material", material=material, attributes=attributes)
|
||||
material_tool.sync_blender_material_name(material)
|
||||
material_tool.disable_editing_material()
|
||||
material_type = material_tool.get_active_material_type()
|
||||
material_tool.import_material_definitions(material_type)
|
||||
|
||||
@@ -16,8 +16,16 @@
|
||||
# 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/>.
|
||||
|
||||
from __future__ import annotations
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
def create_project(ifc, project, schema=None, template=None):
|
||||
if TYPE_CHECKING:
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import blenderbim.tool as tool
|
||||
|
||||
|
||||
def create_project(ifc: tool.Ifc, project: tool.Project, schema: str, template: Optional[str] = None) -> None:
|
||||
if ifc.get():
|
||||
return
|
||||
|
||||
@@ -34,7 +42,7 @@ def create_project(ifc, project, schema=None, template=None):
|
||||
building = project.create_empty("My Building")
|
||||
storey = project.create_empty("My Storey")
|
||||
|
||||
project.run_root_assign_class(obj=project_obj, ifc_class="IfcProject")
|
||||
project.run_root_assign_class(obj=project_obj, ifc_class="IfcProject", should_add_representation=False)
|
||||
project.run_unit_assign_scene_units()
|
||||
|
||||
model = project.run_context_add_context(context_type="Model", context_identifier="", target_view="", parent=0)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user