mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-22 13:05:59 +00:00
Compare commits
240 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 | |||
| 1110f0cffb | |||
| 66e829a8aa | |||
| 984b1212a6 | |||
| bc72c927c1 | |||
| b8275f2802 | |||
| 267527f3fc | |||
| c5b4513f65 | |||
| 5da4fbb39c | |||
| 93639e9e50 | |||
| d76462ca42 | |||
| 722201a1af | |||
| 0a3dddef2f | |||
| 26434f0331 | |||
| f6c2e2c20d | |||
| 424a06f6c8 | |||
| 89c4cbeb05 | |||
| 7e13ed746e | |||
| 28d205b4a3 | |||
| dcc52fc4e4 | |||
| a9d785c28f | |||
| 54a3730c20 | |||
| 31d322a28c | |||
| 3b90086109 | |||
| 30b870b98d | |||
| b3e7975b83 | |||
| 8f7f6223de | |||
| 6c313dd25c | |||
| b718bd19bd | |||
| ab5ea4c853 | |||
| d11ec67129 | |||
| 10f894e2ea | |||
| 34bbc8f1ea | |||
| a213ab6760 | |||
| d694d8fcc2 | |||
| 5f2d451c5e | |||
| ab696b980a | |||
| 9f2df7fb9d | |||
| 9e5c3ff675 | |||
| a67108e6c1 | |||
| b1706f2eac | |||
| ac29e152bb | |||
| de690a385c | |||
| a973b62918 | |||
| 92426a640a | |||
| 93f8638a94 | |||
| 16b0b1c3a5 | |||
| 25fff3fdd0 | |||
| 0384de46a4 | |||
| 080f325557 | |||
| 86cc2bf39a | |||
| c7effa3f25 | |||
| be262eaef2 | |||
| 523e51c775 | |||
| 5410f14aa1 | |||
| 45b2411c2b | |||
| 9f12091c7f | |||
| bef5d3cca5 | |||
| 0f548eb93e | |||
| 374348bb81 | |||
| b42f6b1827 | |||
| addcf531f0 | |||
| 0ef683013a | |||
| 137a306269 | |||
| fc0ffd1c02 | |||
| 04172a87cb | |||
| 97e0c43702 | |||
| 8dde98f543 | |||
| 9962a0c905 | |||
| 4b56b34124 | |||
| f857c513c5 | |||
| 41df6410bd | |||
| dcbd6a925d | |||
| 3848a21136 | |||
| 8af37cb4ec | |||
| 44c28e9a4c | |||
| 677324927c | |||
| d212344c54 | |||
| 74da327803 | |||
| f021041b31 | |||
| c05d2df116 | |||
| 2b926f7906 | |||
| 3ac69043d6 | |||
| f726708815 | |||
| 21e153087a | |||
| 31d85b715a | |||
| 344cc1fb7d | |||
| 1cea3d21e8 | |||
| 640320f70d | |||
| 29352092aa | |||
| 5b877b549a | |||
| 721466c429 | |||
| cce2fee1fa | |||
| 087d55f02a | |||
| f1037de14d | |||
| 4711672170 | |||
| c0b4e099af | |||
| f41fbf24c3 | |||
| e14d96e6ee | |||
| 716a33f347 | |||
| c5f084a4af | |||
| 606a0b46f2 | |||
| 1c729a74d1 | |||
| db31574103 | |||
| d03b47e1e6 | |||
| ae62ac2777 | |||
| 998f9c6a1e | |||
| 3ff31b577c | |||
| cd013079f4 | |||
| b8a9674483 | |||
| 8beb1bef98 | |||
| 377676f881 | |||
| 4efe284338 | |||
| 34a4e3f37a | |||
| 889c9a9e9f | |||
| 9e79499532 | |||
| 589b98053e | |||
| fc5bb230a9 | |||
| 01361cc8f2 | |||
| e6bef16c45 | |||
| 4829c4c02b | |||
| 3bf4a8ef52 | |||
| c95a47ca97 |
@@ -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,13 @@
|
||||
|
||||
import os
|
||||
import sys
|
||||
import site
|
||||
import bpy
|
||||
import platform
|
||||
import traceback
|
||||
import subprocess
|
||||
import webbrowser
|
||||
import addon_utils
|
||||
from collections import deque
|
||||
|
||||
bl_info = {
|
||||
"name": "BlenderBIM",
|
||||
@@ -32,16 +38,163 @@ bl_info = {
|
||||
"category": "System",
|
||||
}
|
||||
|
||||
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 sys.modules.get("bpy", None):
|
||||
# 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,
|
||||
|
||||
@@ -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.);
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import annotations
|
||||
import os
|
||||
import bpy
|
||||
import json
|
||||
@@ -36,6 +37,7 @@ import blenderbim.core.style
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from mathutils import Vector
|
||||
from typing import Union
|
||||
from logging import Logger
|
||||
|
||||
|
||||
class IfcExporter:
|
||||
@@ -163,10 +165,10 @@ class IfcExporter:
|
||||
bpy.ops.bim.update_representation(obj=obj.name)
|
||||
tool.Geometry.record_object_position(obj)
|
||||
|
||||
def get_application_name(self):
|
||||
def get_application_name(self) -> str:
|
||||
return "BlenderBIM"
|
||||
|
||||
def get_application_version(self):
|
||||
def get_application_version(self) -> str:
|
||||
version = ".".join(
|
||||
[
|
||||
str(x)
|
||||
@@ -184,11 +186,13 @@ class IfcExporter:
|
||||
|
||||
class IfcExportSettings:
|
||||
def __init__(self):
|
||||
self.logger = None
|
||||
self.output_file = None
|
||||
self.logger: Logger = None
|
||||
self.output_file: str = None
|
||||
self.json_version: str = None
|
||||
self.json_compact: bool = None
|
||||
|
||||
@staticmethod
|
||||
def factory(context, output_file, logger):
|
||||
def factory(context: bpy.types.Context, output_file: str, logger: Logger) -> IfcExportSettings:
|
||||
settings = IfcExportSettings()
|
||||
settings.output_file = output_file
|
||||
settings.logger = logger
|
||||
|
||||
@@ -223,7 +223,7 @@ def redo_post(scene):
|
||||
tool.Ifc.rebuild_element_maps()
|
||||
|
||||
|
||||
def get_application(ifc):
|
||||
def get_application(ifc: ifcopenshell.file) -> ifcopenshell.entity_instance:
|
||||
# TODO: cache this for even faster application retrieval. It honestly makes a difference on long scripts.
|
||||
version = get_application_version()
|
||||
for element in ifc.by_type("IfcApplication"):
|
||||
@@ -238,7 +238,20 @@ def get_application(ifc):
|
||||
)
|
||||
|
||||
|
||||
def get_application_version():
|
||||
def get_user(ifc: ifcopenshell.file) -> Union[ifcopenshell.entity_instance, None]:
|
||||
# TODO: cache this for even faster application retrieval. It honestly makes a difference on long scripts.
|
||||
if pao := next(iter(ifc.by_type("IfcPersonAndOrganization")), None):
|
||||
return pao
|
||||
elif ifc.schema == "IFC2X3":
|
||||
if (person := next(iter(ifc.by_type("IfcPerson")), None)) is None:
|
||||
person = tool.Ifc.run("owner.add_person")
|
||||
if (organization := next(iter(ifc.by_type("IfcOrganization")), None)) is None:
|
||||
organization = tool.Ifc.run("owner.add_organisation")
|
||||
pao = tool.Ifc.run("owner.add_person_and_organisation", person=person, organisation=organization)
|
||||
return pao
|
||||
|
||||
|
||||
def get_application_version() -> str:
|
||||
return ".".join(
|
||||
[
|
||||
str(x)
|
||||
@@ -279,7 +292,7 @@ def load_post(scene):
|
||||
key=key, owner=global_subscription_owner, args=(area,), notify=viewport_shading_changed_callback
|
||||
)
|
||||
|
||||
ifcopenshell.api.owner.settings.get_user = lambda ifc: core_owner.get_user(tool.Owner)
|
||||
ifcopenshell.api.owner.settings.get_user = get_user
|
||||
ifcopenshell.api.owner.settings.get_application = get_application
|
||||
AuthoringData.type_thumbnails = {}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -34,6 +35,7 @@ import ifcopenshell.util.element
|
||||
import ifcopenshell.util.geolocation
|
||||
import ifcopenshell.util.placement
|
||||
import ifcopenshell.util.representation
|
||||
import ifcopenshell.util.shape
|
||||
import blenderbim.tool as tool
|
||||
import ifcopenshell.ifcopenshell_wrapper as ifcopenshell_wrapper
|
||||
from itertools import chain, accumulate
|
||||
@@ -199,7 +201,7 @@ class MaterialCreator:
|
||||
|
||||
|
||||
class IfcImporter:
|
||||
def __init__(self, ifc_import_settings):
|
||||
def __init__(self, ifc_import_settings: IfcImportSettings):
|
||||
self.ifc_import_settings = ifc_import_settings
|
||||
self.diff = None
|
||||
self.file: ifcopenshell.file = None
|
||||
@@ -298,25 +300,26 @@ class IfcImporter:
|
||||
if self.ifc_import_settings.should_setup_viewport_camera:
|
||||
self.setup_viewport_camera()
|
||||
self.setup_arrays()
|
||||
self.profile_code("Setup arrays")
|
||||
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):
|
||||
@@ -430,7 +433,7 @@ class IfcImporter:
|
||||
self.annotations = set([a for a in self.file.by_type("IfcAnnotation")])
|
||||
self.annotations -= drawing_annotations
|
||||
|
||||
self.elements = [e for e in self.elements if not e.is_a("IfcFeatureElement")]
|
||||
self.elements = [e for e in self.elements if not e.is_a("IfcFeatureElement") or e.is_a("IfcSurfaceFeature")]
|
||||
if self.ifc_import_settings.is_coordinating:
|
||||
self.elements = [e for e in self.elements if e.Representation]
|
||||
|
||||
@@ -601,9 +604,14 @@ class IfcImporter:
|
||||
return products
|
||||
|
||||
def predict_dense_mesh(self):
|
||||
if self.ifc_import_settings.should_use_native_meshes:
|
||||
return
|
||||
|
||||
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
|
||||
@@ -611,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
|
||||
|
||||
@@ -674,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
|
||||
@@ -697,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:
|
||||
@@ -724,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, matrix):
|
||||
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,
|
||||
@@ -743,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
|
||||
@@ -862,7 +877,6 @@ class IfcImporter:
|
||||
else:
|
||||
self.create_generic_elements(self.spatial_elements, unselectable=False)
|
||||
|
||||
|
||||
def create_elements(self) -> None:
|
||||
self.create_generic_elements(self.elements)
|
||||
tmp = self.context_settings
|
||||
@@ -953,7 +967,9 @@ class IfcImporter:
|
||||
self.create_product(element, mesh=mesh)
|
||||
|
||||
def create_products(
|
||||
self, products, settings: Optional[ifcopenshell.geom.main.settings] = None
|
||||
self,
|
||||
products: set[ifcopenshell.entity_instance],
|
||||
settings: Optional[ifcopenshell.geom.main.settings] = None,
|
||||
) -> set[ifcopenshell.entity_instance]:
|
||||
results = set()
|
||||
if not products:
|
||||
@@ -1492,7 +1508,10 @@ class IfcImporter:
|
||||
# Occurs when reloading a project
|
||||
pass
|
||||
project_collection = bpy.context.view_layer.layer_collection.children[self.project["blender"].name]
|
||||
project_collection.children[self.type_collection.name].hide_viewport = True
|
||||
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.
|
||||
obj.hide_set(True)
|
||||
|
||||
def clean_mesh(self):
|
||||
obj = None
|
||||
@@ -1627,30 +1646,34 @@ class IfcImporter:
|
||||
if self.ifc_import_settings.has_filter:
|
||||
rel_aggregates = set()
|
||||
for element in self.elements:
|
||||
if element.IsDecomposedBy:
|
||||
rel_aggregates.add(element.IsDecomposedBy[0])
|
||||
elif element.Decomposes:
|
||||
rel_aggregates.add(element.Decomposes[0])
|
||||
elif getattr(element, "IsNestedBy", []): # IFC2X3 does not have IsNestedBy
|
||||
if [e for e in element.IsNestedBy[0].RelatedObjects if not e.is_a("IfcPort")]:
|
||||
rel_aggregates.add(element.IsNestedBy[0])
|
||||
elif getattr(element, "Nests", []):
|
||||
rel_aggregates.add(element.Nests[0])
|
||||
if decomposed_by := element.IsDecomposedBy:
|
||||
rel_aggregates.add(decomposed_by[0])
|
||||
elif decomposes := element.Decomposes:
|
||||
rel_aggregates.add(decomposes[0])
|
||||
elif nested_by := getattr(element, "IsNestedBy", []): # IFC2X3 does not have IsNestedBy
|
||||
if next((e for e in nested_by[0].RelatedObjects if not e.is_a("IfcPort")), None):
|
||||
rel_aggregates.add(nested_by[0])
|
||||
elif nests := getattr(element, "Nests", []):
|
||||
rel_aggregates.add(nests[0])
|
||||
elif element.is_a("IfcSurfaceFeature") and self.file.schema == "IFC4X3":
|
||||
rel_aggregates.add(element.AdheresToElement[0])
|
||||
else:
|
||||
rel_aggregates = [
|
||||
r
|
||||
for r in self.file.by_type("IfcRelAggregates")
|
||||
if r.RelatingObject.is_a("IfcElement") or r.RelatingObject.is_a("IfcElementType")
|
||||
if (relating_obj := r.RelatingObject).is_a("IfcElement") or relating_obj.is_a("IfcElementType")
|
||||
] + [
|
||||
r
|
||||
for r in self.file.by_type("IfcRelNests")
|
||||
if (
|
||||
r.RelatingObject.is_a("IfcElement")
|
||||
or r.RelatingObject.is_a("IfcElementType")
|
||||
or (r.RelatingObject.is_a("IfcPositioningElement") and not r.RelatingObject.is_a("IfcGrid"))
|
||||
(relating_obj := r.RelatingObject).is_a("IfcElement")
|
||||
or relating_obj.is_a("IfcElementType")
|
||||
or (relating_obj.is_a("IfcPositioningElement") and not relating_obj.is_a("IfcGrid"))
|
||||
)
|
||||
and [e for e in r.RelatedObjects if not e.is_a("IfcPort")]
|
||||
]
|
||||
if self.file.schema == "IFC4X3":
|
||||
rel_aggregates += [r for r in self.file.by_type("IfcRelAdheresToElement")]
|
||||
|
||||
if len(rel_aggregates) > 10000:
|
||||
# More than 10,000 collections makes Blender unhappy
|
||||
@@ -1660,7 +1683,9 @@ class IfcImporter:
|
||||
|
||||
aggregates: dict[str, dict] = {}
|
||||
for rel_aggregate in rel_aggregates:
|
||||
element: ifcopenshell.entity_instance = rel_aggregate.RelatingObject
|
||||
element: ifcopenshell.entity_instance = getattr(rel_aggregate, "RelatingObject", None) or getattr(
|
||||
rel_aggregate, "RelatingElement"
|
||||
)
|
||||
collection = bpy.data.collections.new(tool.Loader.get_name(element))
|
||||
aggregates[element.GlobalId] = {"element": element, "collection": collection}
|
||||
self.collections[element.GlobalId] = collection
|
||||
@@ -1761,6 +1786,9 @@ class IfcImporter:
|
||||
elif getattr(element, "Nests", None) and not element.is_a("IfcPort"):
|
||||
nest = ifcopenshell.util.element.get_nest(element)
|
||||
return self.collections[nest.GlobalId].objects.link(obj)
|
||||
elif element.is_a("IfcSurfaceFeature") and self.file.schema == "IFC4X3":
|
||||
adherend = element.AdheresToElement[0].RelatingElement
|
||||
return self.collections[adherend.GlobalId].objects.link(obj)
|
||||
|
||||
return self.place_object_in_spatial_decomposition_collection(element, obj)
|
||||
|
||||
@@ -1810,7 +1838,7 @@ class IfcImporter:
|
||||
if rel.is_a("IfcRelAssignsToGroup") and rel.RelatingGroup.ObjectType == "DRAWING":
|
||||
return rel.RelatingGroup
|
||||
|
||||
def get_element_matrix(self, element: ifcopenshell.entity_instance) -> np.array:
|
||||
def get_element_matrix(self, element: ifcopenshell.entity_instance) -> np.ndarray:
|
||||
if isinstance(element, ifcopenshell.sqlite_entity):
|
||||
result = self.geometry_cache["shapes"][element.id()]["matrix"]
|
||||
else:
|
||||
@@ -1893,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),
|
||||
@@ -1905,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],
|
||||
@@ -1932,7 +1963,9 @@ class IfcImporter:
|
||||
|
||||
# See bug 3546
|
||||
# ios_edges holds true edges that aren't triangulated.
|
||||
mesh["ios_edges"] = list(set(tuple(e) for e in ifcopenshell.util.shape.get_edges(geometry)))
|
||||
#
|
||||
# we do `.tolist()` because Blender can't assign `np.int32` to it's custom attributes
|
||||
mesh["ios_edges"] = list(set(tuple(e) for e in ifcopenshell.util.shape.get_edges(geometry).tolist()))
|
||||
|
||||
mesh.vertices.add(num_vertices)
|
||||
mesh.vertices.foreach_set("co", verts)
|
||||
@@ -1959,14 +1992,14 @@ class IfcImporter:
|
||||
|
||||
print(traceback.format_exc())
|
||||
|
||||
def a2p(self, o, z, x):
|
||||
def a2p(self, o: mathutils.Vector, z: mathutils.Vector, x: mathutils.Vector) -> mathutils.Matrix:
|
||||
y = z.cross(x)
|
||||
r = mathutils.Matrix((x, y, z, o))
|
||||
r.resize_4x4()
|
||||
r.transpose()
|
||||
return r
|
||||
|
||||
def get_axis2placement(self, plc):
|
||||
def get_axis2placement(self, plc: ifcopenshell.entity_instance) -> mathutils.Matrix:
|
||||
if plc.is_a("IfcAxis2Placement3D"):
|
||||
z = mathutils.Vector(plc.Axis.DirectionRatios if plc.Axis else (0, 0, 1))
|
||||
x = mathutils.Vector(plc.RefDirection.DirectionRatios if plc.RefDirection else (1, 0, 0))
|
||||
@@ -1986,7 +2019,7 @@ class IfcImporter:
|
||||
o = plc.LocalOrigin.Coordinates
|
||||
return self.a2p(o, z, x)
|
||||
|
||||
def get_local_placement(self, plc):
|
||||
def get_local_placement(self, plc: Optional[ifcopenshell.entity_instance] = None) -> mathutils.Matrix:
|
||||
if plc is None:
|
||||
return mathutils.Matrix()
|
||||
if plc.PlacementRelTo is None:
|
||||
@@ -2001,11 +2034,11 @@ class IfcImporter:
|
||||
bpy.context.scene.BIMRootProperties.contexts = str(subcontext.id())
|
||||
break
|
||||
|
||||
def link_element(self, element, obj):
|
||||
def link_element(self, element: ifcopenshell.entity_instance, obj: IFC_CONNECTED_TYPE) -> None:
|
||||
self.added_data[element.id()] = obj
|
||||
tool.Ifc.link(element, obj)
|
||||
|
||||
def set_matrix_world(self, obj, matrix_world):
|
||||
def set_matrix_world(self, obj: bpy.types.Object, matrix_world: mathutils.Matrix) -> None:
|
||||
obj.matrix_world = matrix_world
|
||||
tool.Geometry.record_object_position(obj)
|
||||
|
||||
@@ -2028,7 +2061,7 @@ class IfcImporter:
|
||||
|
||||
class IfcImportSettings:
|
||||
def __init__(self):
|
||||
self.logger = None
|
||||
self.logger: logging.Logger = None
|
||||
self.input_file = None
|
||||
self.diff_file = None
|
||||
self.should_use_cpu_multiprocessing = True
|
||||
|
||||
@@ -96,12 +96,13 @@ class BIM_OT_aggregate_unassign_object(bpy.types.Operator, Operator):
|
||||
)
|
||||
|
||||
# Removes Pset related to Linked Aggregates
|
||||
pset = ifcopenshell.util.element.get_pset(element, "BBIM_Linked_Aggregate")
|
||||
if pset:
|
||||
pset = tool.Ifc.get().by_id(pset["id"])
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
|
||||
|
||||
if not element.is_a('IfcElementAssembly'):
|
||||
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(), product=element, pset=pset)
|
||||
|
||||
|
||||
class BIM_OT_enable_editing_aggregate(bpy.types.Operator, Operator):
|
||||
"""Enable editing aggregation relationship"""
|
||||
|
||||
@@ -132,6 +133,7 @@ class BIM_OT_add_aggregate(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
ifc_class: bpy.props.StringProperty(name="IFC Class", default="IfcElementAssembly")
|
||||
aggregate_name: bpy.props.StringProperty(name="Name", default="Default_Name")
|
||||
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
@@ -139,13 +141,15 @@ class BIM_OT_add_aggregate(bpy.types.Operator, tool.Ifc.Operator):
|
||||
def draw(self, context):
|
||||
row = self.layout
|
||||
row.prop(self, "ifc_class")
|
||||
row = self.layout
|
||||
row.prop(self, "aggregate_name")
|
||||
|
||||
def _execute(self, context):
|
||||
try:
|
||||
ifc_class = tool.Ifc.schema().declaration_by_name(self.ifc_class).name()
|
||||
except:
|
||||
return
|
||||
aggregate = self.create_aggregate(context, ifc_class)
|
||||
aggregate = self.create_aggregate(context, ifc_class, self.aggregate_name)
|
||||
|
||||
for obj in context.selected_objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
@@ -173,8 +177,8 @@ class BIM_OT_add_aggregate(bpy.types.Operator, tool.Ifc.Operator):
|
||||
)
|
||||
core.assign_object(tool.Ifc, tool.Aggregate, tool.Collector, relating_obj=aggregate, related_obj=obj)
|
||||
|
||||
def create_aggregate(self, context, ifc_class):
|
||||
aggregate = bpy.data.objects.new("Assembly", None)
|
||||
def create_aggregate(self, context, ifc_class, aggregate_name):
|
||||
aggregate = bpy.data.objects.new(aggregate_name, None)
|
||||
aggregate.location = context.scene.cursor.location
|
||||
bpy.ops.bim.assign_class(obj=aggregate.name, ifc_class=ifc_class)
|
||||
return aggregate
|
||||
|
||||
@@ -20,6 +20,7 @@ from bpy.types import Panel
|
||||
from blenderbim.bim.module.aggregate.data import AggregateData
|
||||
from blenderbim.bim.module.group.data import GroupsData, ObjectGroupsData
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
import blenderbim.tool as tool
|
||||
|
||||
|
||||
class BIM_PT_aggregate(Panel):
|
||||
@@ -72,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="")
|
||||
|
||||
@@ -131,22 +132,29 @@ class BIM_PT_linked_aggregate(Panel):
|
||||
if not AggregateData.is_loaded:
|
||||
AggregateData.load()
|
||||
|
||||
props = context.active_object.BIMObjectAggregateProperties
|
||||
obj = context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
props = obj.BIMObjectAggregateProperties
|
||||
row = layout.row(align=True)
|
||||
row.label(text="Advanced Users Only", icon="ERROR")
|
||||
row = layout.row(align=True)
|
||||
|
||||
if type(AggregateData.data['total_linked_aggregate']) is int:
|
||||
if AggregateData.data['total_linked_aggregate'] > 0:
|
||||
row.label(text=f"{AggregateData.data['total_linked_aggregate']} Linked Aggregates")
|
||||
op = row.operator("bim.select_linked_aggregates", text="", icon="OUTLINER_DATA_POINTCLOUD")
|
||||
op.select_parts = False
|
||||
op = row.operator("bim.select_linked_aggregates", text="", icon="OUTLINER_OB_POINTCLOUD")
|
||||
op.select_parts = True
|
||||
row.operator("bim.refresh_linked_aggregate", text="", icon="FILE_REFRESH")
|
||||
op = row.operator("bim.break_link_to_other_aggregates", text="", icon="X")
|
||||
|
||||
|
||||
if element.Decomposes:
|
||||
Number_Linked_Aggregates = AggregateData.data['total_linked_aggregate']
|
||||
if not Number_Linked_Aggregates:
|
||||
row.label(text="Not a Linked Aggregate")
|
||||
else:
|
||||
row.label(text=f"{Number_Linked_Aggregates} Linked Aggregates")
|
||||
op = row.operator("bim.duplicate_linked_aggregate_to_3d_cursor", text="", icon="DUPLICATE")
|
||||
if type(Number_Linked_Aggregates) is int:
|
||||
if Number_Linked_Aggregates > 0:
|
||||
op = row.operator("bim.select_linked_aggregates", text="", icon="OUTLINER_DATA_POINTCLOUD")
|
||||
op.select_parts = False
|
||||
op = row.operator("bim.select_linked_aggregates", text="", icon="OUTLINER_OB_POINTCLOUD")
|
||||
op.select_parts = True
|
||||
row.operator("bim.refresh_linked_aggregate", text="", icon="FILE_REFRESH")
|
||||
op = row.operator("bim.break_link_to_other_aggregates", text="", icon="X")
|
||||
else:
|
||||
row.label(text="No Linked Aggregates")
|
||||
row.label(text="Not an Aggregate")
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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"])
|
||||
|
||||
@@ -20,6 +20,7 @@ import bpy
|
||||
import bmesh
|
||||
import logging
|
||||
import shapely
|
||||
import shapely.ops
|
||||
import mathutils
|
||||
import numpy as np
|
||||
import multiprocessing
|
||||
|
||||
@@ -209,6 +209,10 @@ class DisableEditingClassification(bpy.types.Operator):
|
||||
class RemoveClassification(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.remove_classification"
|
||||
bl_label = "Remove Classification"
|
||||
bl_description = (
|
||||
"The classification and all of its relationships, children references, "
|
||||
"and relationships between objects and child references will be completely removed from a project"
|
||||
)
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
classification: bpy.props.IntProperty()
|
||||
|
||||
|
||||
@@ -53,6 +53,10 @@ class AddContext(bpy.types.Operator, Operator):
|
||||
class RemoveContext(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.remove_context"
|
||||
bl_label = "Remove Context"
|
||||
bl_description = (
|
||||
"Remove representation context. Any representation geometry that is assigned to the context is also removed. "
|
||||
"If a context is removed, then any subcontexts are also removed"
|
||||
)
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
context: bpy.props.IntProperty()
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -223,7 +223,7 @@ 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_options = {"REGISTER", "UNDO"}
|
||||
cost_item: bpy.props.IntProperty()
|
||||
prop_name: bpy.props.StringProperty()
|
||||
@@ -509,7 +509,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"
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -107,59 +107,88 @@ class CoveringToolUI:
|
||||
# elif element and bpy.context.selected_objects and element.is_a("IfcSpace"):
|
||||
# op. = row.operator("bim.add_istance_flooring_from_spaces"):
|
||||
|
||||
if (type_material_usage == "IfcMaterialLayerSet" and
|
||||
not bpy.context.selected_objects):
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_A")
|
||||
if tool.Ifc.get_entity(collection_obj):
|
||||
if AuthoringData.data["predefined_type"] == "FLOORING":
|
||||
op = row.operator("bim.add_instance_flooring_covering_from_cursor")
|
||||
elif AuthoringData.data["predefined_type"] == "CEILING":
|
||||
op = row.operator("bim.add_instance_ceiling_covering_from_cursor")
|
||||
else:
|
||||
op = row.operator("bim.add_constr_type_instance", text="Add")
|
||||
op.from_invoke = True
|
||||
if cls.props.relating_type_id.isnumeric():
|
||||
op.relating_type_id = int(cls.props.relating_type_id)
|
||||
|
||||
else:
|
||||
op = row.operator("bim.add_constr_type_instance", text="Add")
|
||||
op.from_invoke = True
|
||||
if cls.props.relating_type_id.isnumeric():
|
||||
op.relating_type_id = int(cls.props.relating_type_id)
|
||||
|
||||
elif (AuthoringData.data["predefined_type"] == "FLOORING" and
|
||||
type_material_usage == "IfcMaterialLayerSet" and
|
||||
element and
|
||||
bpy.context.selected_objects and
|
||||
element.is_a("IfcWall")):
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_A")
|
||||
op = row.operator("bim.add_instance_flooring_coverings_from_walls")
|
||||
|
||||
elif (AuthoringData.data["predefined_type"] == "CEILING" and
|
||||
type_material_usage == "IfcMaterialLayerSet" and
|
||||
element and
|
||||
bpy.context.selected_objects and
|
||||
element.is_a("IfcWall")):
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_A")
|
||||
op = row.operator("bim.add_instance_ceiling_coverings_from_walls")
|
||||
|
||||
elif (element and
|
||||
bpy.context.selected_objects and
|
||||
element.is_a("IfcCovering") and
|
||||
# AuthoringData.data["predefined_type"] == "FLOORING" and
|
||||
AuthoringData.data["active_material_usage"] == "LAYER3"):
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_G")
|
||||
op = row.operator("bim.regen_selected_covering_object")
|
||||
# if (type_material_usage == "IfcMaterialLayerSet" and
|
||||
# not bpy.context.selected_objects):
|
||||
# row = cls.layout.row(align=True)
|
||||
# row.label(text="", icon="EVENT_SHIFT")
|
||||
# row.label(text="", icon="EVENT_A")
|
||||
# if tool.Ifc.get_entity(collection_obj):
|
||||
# if AuthoringData.data["predefined_type"] == "FLOORING":
|
||||
# op = row.operator("bim.add_instance_flooring_covering_from_cursor")
|
||||
# elif AuthoringData.data["predefined_type"] == "CEILING":
|
||||
# op = row.operator("bim.add_instance_ceiling_covering_from_cursor")
|
||||
# else:
|
||||
# op = row.operator("bim.add_constr_type_instance", text="Add")
|
||||
# op.from_invoke = True
|
||||
# if cls.props.relating_type_id.isnumeric():
|
||||
# op.relating_type_id = int(cls.props.relating_type_id)
|
||||
#
|
||||
# else:
|
||||
# op = row.operator("bim.add_constr_type_instance", text="Add")
|
||||
# op.from_invoke = True
|
||||
# if cls.props.relating_type_id.isnumeric():
|
||||
# op.relating_type_id = int(cls.props.relating_type_id)
|
||||
#
|
||||
# elif (AuthoringData.data["predefined_type"] == "FLOORING" and
|
||||
# type_material_usage == "IfcMaterialLayerSet" and
|
||||
# element and
|
||||
# bpy.context.selected_objects and
|
||||
# element.is_a("IfcWall")):
|
||||
# row = cls.layout.row(align=True)
|
||||
# row.label(text="", icon="EVENT_SHIFT")
|
||||
# row.label(text="", icon="EVENT_A")
|
||||
# op = row.operator("bim.add_instance_flooring_coverings_from_walls")
|
||||
#
|
||||
# elif (AuthoringData.data["predefined_type"] == "CEILING" and
|
||||
# type_material_usage == "IfcMaterialLayerSet" and
|
||||
# element and
|
||||
# bpy.context.selected_objects and
|
||||
# element.is_a("IfcWall")):
|
||||
# row = cls.layout.row(align=True)
|
||||
# row.label(text="", icon="EVENT_SHIFT")
|
||||
# row.label(text="", icon="EVENT_A")
|
||||
# op = row.operator("bim.add_instance_ceiling_coverings_from_walls")
|
||||
#
|
||||
# elif (element and
|
||||
# bpy.context.selected_objects and
|
||||
# element.is_a("IfcCovering") and
|
||||
## AuthoringData.data["predefined_type"] == "FLOORING" and
|
||||
# AuthoringData.data["active_material_usage"] == "LAYER3"):
|
||||
# row = cls.layout.row(align=True)
|
||||
# row.label(text="", icon="EVENT_SHIFT")
|
||||
# row.label(text="", icon="EVENT_G")
|
||||
# op = row.operator("bim.regen_selected_covering_object")
|
||||
|
||||
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_A")
|
||||
op = row.operator("bim.add_constr_type_instance", text="Add")
|
||||
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_A")
|
||||
op = row.operator("bim.add_instance_flooring_covering_from_cursor")
|
||||
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_A")
|
||||
op = row.operator("bim.add_instance_ceiling_covering_from_cursor")
|
||||
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_A")
|
||||
op = row.operator("bim.add_instance_flooring_coverings_from_walls")
|
||||
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_A")
|
||||
op = row.operator("bim.add_instance_ceiling_coverings_from_walls")
|
||||
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_G")
|
||||
op = row.operator("bim.regen_selected_covering_object")
|
||||
|
||||
# elif AuthoringData.data["predefined_type"] == "CEILING":
|
||||
# row = cls.layout.row(align=True)
|
||||
@@ -174,14 +203,14 @@ class CoveringToolUI:
|
||||
# op.from_invoke = True
|
||||
# if cls.props.relating_type_id.isnumeric():
|
||||
# op.relating_type_id = int(cls.props.relating_type_id)
|
||||
else:
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_A")
|
||||
op = row.operator("bim.add_constr_type_instance", text="Add")
|
||||
op.from_invoke = True
|
||||
if cls.props.relating_type_id.isnumeric():
|
||||
op.relating_type_id = int(cls.props.relating_type_id)
|
||||
# else:
|
||||
# row = cls.layout.row(align=True)
|
||||
# row.label(text="", icon="EVENT_SHIFT")
|
||||
# row.label(text="", icon="EVENT_A")
|
||||
# op = row.operator("bim.add_constr_type_instance", text="Add")
|
||||
# op.from_invoke = True
|
||||
# if cls.props.relating_type_id.isnumeric():
|
||||
# op.relating_type_id = int(cls.props.relating_type_id)
|
||||
|
||||
@classmethod
|
||||
def draw_type_selection_interface(cls):
|
||||
|
||||
@@ -72,7 +72,7 @@ class ReorderCsvAttribute(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
old = context.scene.CsvProperties.csv_attributes[self.old_index]
|
||||
new = context.scene.CsvProperties.csv_attributes[self.new_index]
|
||||
props = ["name", "header", "sort", "group", "varies_value", "summary"]
|
||||
props = ["name", "header", "sort", "group", "varies_value", "summary", "formatting"]
|
||||
for prop in props:
|
||||
value = getattr(new, prop)
|
||||
setattr(new, prop, getattr(old, prop))
|
||||
@@ -246,6 +246,7 @@ class ExportIfcCsv(bpy.types.Operator):
|
||||
if props.format != "csv" and props.should_generate_svg:
|
||||
schedule_creator = scheduler.Scheduler()
|
||||
schedule_creator.schedule(self.filepath, tool.Drawing.get_path_with_ext(self.filepath, "svg"))
|
||||
self.report({"INFO"}, f"Data is exported to {props.format.upper()}.")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -285,6 +286,7 @@ class ImportIfcCsv(bpy.types.Operator):
|
||||
if not props.should_load_from_memory:
|
||||
ifc_file.write(props.csv_ifc_file)
|
||||
refresh_ui_data()
|
||||
self.report({"INFO"}, "Data is imported to IFC.")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
|
||||
@@ -35,7 +35,6 @@ classes = (
|
||||
operator.PrintUnusedElementStats,
|
||||
operator.ProfileImportIFC,
|
||||
operator.PurgeHdf5Cache,
|
||||
operator.PurgeIfcLinks,
|
||||
operator.PurgeUnusedElementsByClass,
|
||||
operator.RewindInspector,
|
||||
operator.SelectExpressFile,
|
||||
|
||||
@@ -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,7 +33,7 @@ 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
|
||||
|
||||
|
||||
@@ -44,28 +43,7 @@ class CopyDebugInformation(bpy.types.Operator):
|
||||
bl_description = "Copies debugging information to your clipboard for use in bugreports"
|
||||
|
||||
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,13 @@ 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)
|
||||
print("-" * 80)
|
||||
print(text)
|
||||
print("-" * 80)
|
||||
|
||||
context.window_manager.clipboard = text
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -104,10 +78,11 @@ class PrintIfcFile(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class PurgeIfcLinks(bpy.types.Operator):
|
||||
bl_idname = "bim.purge_ifc_links"
|
||||
bl_label = "Purge IFC Links"
|
||||
bl_description = "Purge all definitions and references from the file.\nWarning : Cannot be undone."
|
||||
class ConvertToBlender(bpy.types.Operator):
|
||||
bl_idname = "bim.convert_to_blender"
|
||||
bl_label = "Convert To Blender File"
|
||||
bl_description = "Removes all IFC data and revert to basic Blender objects.\nWarning : Cannot be undone."
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
for obj in bpy.data.objects:
|
||||
@@ -124,26 +99,6 @@ class PurgeIfcLinks(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ConvertToBlender(bpy.types.Operator):
|
||||
bl_idname = "bim.convert_to_blender"
|
||||
bl_label = "Convert To Blender File"
|
||||
bl_description = "Removes all IFC data, and converts the file to a simple Blender file."
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
for o in bpy.data.objects:
|
||||
if o.type in {"MESH", "EMPTY"}:
|
||||
o.BIMObjectProperties.ifc_definition_id = 0
|
||||
if o.data:
|
||||
o.data.BIMMeshProperties.ifc_definition_id = 0
|
||||
for m in bpy.data.materials:
|
||||
m.BIMMaterialProperties.ifc_style_id = False
|
||||
bpy.context.scene.BIMProperties.ifc_file = ""
|
||||
IfcStore.purge()
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ValidateIfcFile(bpy.types.Operator):
|
||||
bl_idname = "bim.validate_ifc_file"
|
||||
bl_label = "Validate IFC File"
|
||||
|
||||
@@ -60,9 +60,6 @@ class BIM_PT_debug(Panel):
|
||||
row = layout.row()
|
||||
row.operator("bim.purge_hdf5_cache")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.purge_ifc_links")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.update_representation", text="Manually Save Representation")
|
||||
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -29,6 +29,7 @@ import subprocess
|
||||
import numpy as np
|
||||
import multiprocessing
|
||||
import ifcopenshell
|
||||
import ifcopenshell.ifcopenshell_wrapper
|
||||
import ifcopenshell.geom
|
||||
import ifcopenshell.util.selector
|
||||
import ifcopenshell.util.representation
|
||||
@@ -219,11 +220,12 @@ class CreateDrawing(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
self.props = context.scene.DocProperties
|
||||
|
||||
active_drawing_id = context.scene.camera.BIMObjectProperties.ifc_definition_id
|
||||
if self.print_all:
|
||||
original_drawing_id = self.props.active_drawing_id
|
||||
original_drawing_id = active_drawing_id
|
||||
drawings_to_print = [d.ifc_definition_id for d in self.props.drawings if d.is_selected and d.is_drawing]
|
||||
else:
|
||||
drawings_to_print = [self.props.active_drawing_id]
|
||||
drawings_to_print = [active_drawing_id]
|
||||
|
||||
for drawing_i, drawing_id in enumerate(drawings_to_print):
|
||||
self.drawing_index = drawing_i
|
||||
@@ -1241,12 +1243,14 @@ class AddDrawingToSheet(bpy.types.Operator, Operator):
|
||||
return
|
||||
|
||||
reference = tool.Ifc.run("document.add_reference", information=sheet)
|
||||
id_attr = "ItemReference" if tool.Ifc.get_schema() == "IFC2X3" else "Identification"
|
||||
attributes = {
|
||||
id_attr: str(len([r for r in references if r.Description in ("DRAWING", "SCHEDULE")]) + 1),
|
||||
"Location": drawing_reference.Location,
|
||||
"Description": "DRAWING",
|
||||
}
|
||||
attributes = tool.Drawing.generate_reference_attributes(
|
||||
reference,
|
||||
Identification=str(
|
||||
len([r for r in references if tool.Drawing.get_reference_description(r) in ("DRAWING", "SCHEDULE")]) + 1
|
||||
),
|
||||
Location=drawing_reference.Location,
|
||||
Description="DRAWING",
|
||||
)
|
||||
tool.Ifc.run("document.edit_reference", reference=reference, attributes=attributes)
|
||||
sheet_builder = sheeter.SheetBuilder()
|
||||
sheet_builder.data_dir = context.scene.BIMProperties.data_dir
|
||||
@@ -1314,9 +1318,10 @@ class CreateSheets(bpy.types.Operator, Operator):
|
||||
|
||||
has_sheet_reference = False
|
||||
for reference in tool.Drawing.get_document_references(sheet):
|
||||
if reference.Description == "SHEET":
|
||||
reference_description = tool.Drawing.get_reference_description(reference)
|
||||
if reference_description == "SHEET":
|
||||
has_sheet_reference = True
|
||||
elif reference.Description == "RASTER":
|
||||
elif reference_description == "RASTER":
|
||||
if reference.Location in raster_references:
|
||||
raster_references.remove(reference.Location)
|
||||
else:
|
||||
@@ -1327,7 +1332,9 @@ class CreateSheets(bpy.types.Operator, Operator):
|
||||
tool.Ifc.run(
|
||||
"document.edit_reference",
|
||||
reference=reference,
|
||||
attributes={"Location": tool.Ifc.get_relative_uri(svg), "Description": "SHEET"},
|
||||
attributes=tool.Drawing.generate_reference_attributes(
|
||||
reference, Location=tool.Ifc.get_relative_uri(svg), Description="SHEET"
|
||||
),
|
||||
)
|
||||
|
||||
for raster_reference in raster_references:
|
||||
@@ -1335,7 +1342,9 @@ class CreateSheets(bpy.types.Operator, Operator):
|
||||
tool.Ifc.run(
|
||||
"document.edit_reference",
|
||||
reference=reference,
|
||||
attributes={"Location": tool.Ifc.get_relative_uri(raster_reference), "Description": "RASTER"},
|
||||
attributes=tool.Drawing.generate_reference_attributes(
|
||||
reference, Location=tool.Ifc.get_relative_uri(raster_reference), Description="RASTER"
|
||||
),
|
||||
)
|
||||
|
||||
svg2pdf_command = context.preferences.addons["blenderbim"].preferences.svg2pdf_command
|
||||
@@ -1444,13 +1453,19 @@ class ActivateModel(bpy.types.Operator):
|
||||
|
||||
CutDecorator.uninstall()
|
||||
|
||||
# save current visibility statuses for Views and Types collections
|
||||
visibility_status: dict[bpy.types.Object, bool] = {}
|
||||
for col in bpy.data.collections["Views"].children:
|
||||
for obj in col.objects:
|
||||
visibility_status[obj] = obj.hide_get()
|
||||
for obj in bpy.data.collections["Types"].objects:
|
||||
visibility_status[obj] = obj.hide_get()
|
||||
|
||||
if not bpy.app.background:
|
||||
with context.temp_override(**tool.Blender.get_viewport_context()):
|
||||
bpy.ops.object.hide_view_clear()
|
||||
bpy.ops.bim.activate_status_filters()
|
||||
|
||||
subcontext = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW")
|
||||
|
||||
for obj in context.visible_objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
@@ -1468,6 +1483,11 @@ class ActivateModel(bpy.types.Operator):
|
||||
is_global=True,
|
||||
should_sync_changes_first=True,
|
||||
)
|
||||
|
||||
# restore visibility after hide_view_clear()
|
||||
for obj, hide_status in visibility_status.items():
|
||||
obj.hide_set(hide_status)
|
||||
|
||||
tool.Blender.update_viewport()
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -1495,7 +1515,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)
|
||||
@@ -1988,12 +2015,14 @@ class AddScheduleToSheet(bpy.types.Operator, Operator):
|
||||
return
|
||||
|
||||
reference = tool.Ifc.run("document.add_reference", information=sheet)
|
||||
id_attr = "ItemReference" if tool.Ifc.get_schema() == "IFC2X3" else "Identification"
|
||||
attributes = {
|
||||
id_attr: str(len([r for r in references if r.Description in ("DRAWING", "SCHEDULE")]) + 1),
|
||||
"Location": schedule_location,
|
||||
"Description": "SCHEDULE",
|
||||
}
|
||||
attributes = tool.Drawing.generate_reference_attributes(
|
||||
reference,
|
||||
Identification=str(
|
||||
len([r for r in references if tool.Drawing.get_reference_description(r) in ("DRAWING", "SCHEDULE")]) + 1
|
||||
),
|
||||
Location=schedule_location,
|
||||
Description="SCHEDULE",
|
||||
)
|
||||
tool.Ifc.run("document.edit_reference", reference=reference, attributes=attributes)
|
||||
|
||||
sheet_builder = sheeter.SheetBuilder()
|
||||
@@ -2042,12 +2071,15 @@ class AddReferenceToSheet(bpy.types.Operator, Operator):
|
||||
return
|
||||
|
||||
reference = tool.Ifc.run("document.add_reference", information=sheet)
|
||||
id_attr = "ItemReference" if tool.Ifc.get_schema() == "IFC2X3" else "Identification"
|
||||
attributes = {
|
||||
id_attr: str(len([r for r in references if r.Description in ("DRAWING", "REFERENCE")]) + 1),
|
||||
"Location": extref_location,
|
||||
"Description": "REFERENCE",
|
||||
}
|
||||
attributes = tool.Drawing.generate_reference_attributes(
|
||||
reference,
|
||||
Identification=str(
|
||||
len([r for r in references if tool.Drawing.get_reference_description(r) in ("DRAWING", "REFERENCE")])
|
||||
+ 1
|
||||
),
|
||||
Location=extref_location,
|
||||
Description="REFERENCE",
|
||||
)
|
||||
tool.Ifc.run("document.edit_reference", reference=reference, attributes=attributes)
|
||||
|
||||
sheet_builder = sheeter.SheetBuilder()
|
||||
@@ -2392,8 +2424,8 @@ class EditSheet(bpy.types.Operator, Operator):
|
||||
if sheet.is_a("IfcDocumentInformation"):
|
||||
self.document_type = "SHEET"
|
||||
self.name = sheet.Name
|
||||
self.identification = sheet.Identification
|
||||
elif sheet.is_a("IfcDocumentReference") and sheet.Description == "TITLEBLOCK":
|
||||
self.identification = sheet.DocumentId if tool.Ifc.get_schema() == "IFC2X3" else sheet.Identification
|
||||
elif sheet.is_a("IfcDocumentReference") and tool.Drawing.get_reference_description(sheet) == "TITLEBLOCK":
|
||||
self.document_type = "TITLEBLOCK"
|
||||
else:
|
||||
self.document_type = "EMBEDDED"
|
||||
@@ -2419,7 +2451,7 @@ class EditSheet(bpy.types.Operator, Operator):
|
||||
if self.document_type == "SHEET":
|
||||
core.rename_sheet(tool.Ifc, tool.Drawing, sheet=sheet, identification=self.identification, name=self.name)
|
||||
elif self.document_type == "EMBEDDED":
|
||||
core.rename_reference(tool.Ifc, reference=sheet, identification=self.identification)
|
||||
core.rename_reference(tool.Ifc, tool.Drawing, reference=sheet, identification=self.identification)
|
||||
elif self.document_type == "TITLEBLOCK":
|
||||
titleblock = self.props.titleblock
|
||||
reference = sheet
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -41,7 +41,7 @@ class SheetBuilder:
|
||||
self.data_dir = None
|
||||
self.scale = "NTS"
|
||||
|
||||
def create(self, layout_path, titleblock_name):
|
||||
def create(self, layout_path: str, titleblock_name: str) -> None:
|
||||
root = ET.Element("svg")
|
||||
root.attrib["xmlns"] = "http://www.w3.org/2000/svg"
|
||||
root.attrib["xmlns:xlink"] = "http://www.w3.org/1999/xlink"
|
||||
@@ -76,7 +76,12 @@ class SheetBuilder:
|
||||
with open(layout_path, "w") as f:
|
||||
f.write(minidom.parseString(ET.tostring(root)).toprettyxml(indent=" "))
|
||||
|
||||
def add_drawing(self, reference, drawing, sheet):
|
||||
def add_drawing(
|
||||
self,
|
||||
reference: ifcopenshell.entity_instance,
|
||||
drawing: ifcopenshell.entity_instance,
|
||||
sheet: ifcopenshell.entity_instance,
|
||||
) -> None:
|
||||
filename = drawing.Name
|
||||
layout_path = tool.Drawing.get_document_uri(sheet, "LAYOUT")
|
||||
layout_dir = os.path.dirname(layout_path)
|
||||
@@ -131,7 +136,7 @@ class SheetBuilder:
|
||||
)
|
||||
layout_tree.write(layout_path)
|
||||
|
||||
def update_sheet_drawing_sizes(self, sheet):
|
||||
def update_sheet_drawing_sizes(self, sheet: ifcopenshell.entity_instance) -> None:
|
||||
ET.register_namespace("", "http://www.w3.org/2000/svg")
|
||||
|
||||
layout_path = tool.Drawing.get_document_uri(sheet, "LAYOUT")
|
||||
@@ -171,7 +176,7 @@ class SheetBuilder:
|
||||
|
||||
layout_tree.write(layout_path)
|
||||
|
||||
def remove_drawing(self, reference, sheet):
|
||||
def remove_drawing(self, reference: ifcopenshell.entity_instance, sheet: ifcopenshell.entity_instance) -> None:
|
||||
ET.register_namespace("", "http://www.w3.org/2000/svg")
|
||||
|
||||
layout_path = tool.Drawing.get_document_uri(sheet, "LAYOUT")
|
||||
@@ -187,7 +192,12 @@ class SheetBuilder:
|
||||
|
||||
layout_tree.write(layout_path)
|
||||
|
||||
def add_document(self, reference, document, sheet):
|
||||
def add_document(
|
||||
self,
|
||||
reference: ifcopenshell.entity_instance,
|
||||
document: ifcopenshell.entity_instance,
|
||||
sheet: ifcopenshell.entity_instance,
|
||||
) -> None:
|
||||
view_path = tool.Drawing.get_path_with_ext(tool.Drawing.get_document_uri(document), "svg")
|
||||
if not os.path.exists(view_path):
|
||||
tool.Drawing.create_svg_document(document)
|
||||
@@ -224,7 +234,7 @@ class SheetBuilder:
|
||||
)
|
||||
layout_tree.write(layout_path)
|
||||
|
||||
def add_view_title(self, x, y, parent, layout_dir):
|
||||
def add_view_title(self, x: float, y: float, parent: ET.Element, layout_dir: str) -> None:
|
||||
title_path = os.path.join(layout_dir, "assets", "view-title.svg")
|
||||
os.makedirs(os.path.dirname(title_path), exist_ok=True)
|
||||
if not os.path.exists(title_path):
|
||||
@@ -241,7 +251,7 @@ class SheetBuilder:
|
||||
title.attrib["width"] = str(self.convert_to_mm(title_root.attrib.get("width")))
|
||||
title.attrib["height"] = str(self.convert_to_mm(title_root.attrib.get("height")))
|
||||
|
||||
def build(self, sheet):
|
||||
def build(self, sheet: ifcopenshell.entity_instance) -> dict:
|
||||
self.references = {"SHEET": None, "RASTER": []}
|
||||
|
||||
layout_path = tool.Drawing.get_document_uri(sheet, "LAYOUT")
|
||||
@@ -272,7 +282,7 @@ class SheetBuilder:
|
||||
|
||||
return self.references
|
||||
|
||||
def build_titleblock(self, root, sheet):
|
||||
def build_titleblock(self, root: ET.Element, sheet: ifcopenshell.entity_instance) -> None:
|
||||
titleblock = root.findall('{http://www.w3.org/2000/svg}g[@data-type="titleblock"]')[0]
|
||||
image = titleblock.findall("{http://www.w3.org/2000/svg}image")[0]
|
||||
g = self.parse_embedded_svg(image, sheet.get_info())
|
||||
@@ -285,7 +295,7 @@ class SheetBuilder:
|
||||
titleblock.append(g)
|
||||
titleblock.remove(image)
|
||||
|
||||
def ensure_drawing_unique_styles(self, svg, drawing_id):
|
||||
def ensure_drawing_unique_styles(self, svg: ET.Element, drawing_id: int) -> ET.Element:
|
||||
"""ensures all drawing's classes and ids will be unique for the whole sheet
|
||||
by adding `drawing_id` based prefix
|
||||
"""
|
||||
@@ -313,7 +323,7 @@ class SheetBuilder:
|
||||
brackets_level -= 1
|
||||
text += l
|
||||
|
||||
def replace_urls(text):
|
||||
def replace_urls(text: str) -> str:
|
||||
"""replace urls `url(#marker)` with `url(#prefix-marker)`
|
||||
since `url(#marker.prefix)` doesn't seem to work
|
||||
"""
|
||||
@@ -343,7 +353,7 @@ class SheetBuilder:
|
||||
|
||||
return svg
|
||||
|
||||
def build_drawings(self, root, sheet):
|
||||
def build_drawings(self, root: ET.Element, sheet: ifcopenshell.entity_instance):
|
||||
for view in root.findall('{http://www.w3.org/2000/svg}g[@data-type="drawing"]'):
|
||||
drawing_id = int(view.attrib["data-id"])
|
||||
try:
|
||||
@@ -390,7 +400,7 @@ class SheetBuilder:
|
||||
for image in images:
|
||||
view.remove(image)
|
||||
|
||||
def build_documents(self, root, sheet):
|
||||
def build_documents(self, root: ET.Element, sheet: ifcopenshell.entity_instance) -> None:
|
||||
schedules = root.findall('{http://www.w3.org/2000/svg}g[@data-type="schedule"]')
|
||||
references = root.findall('{http://www.w3.org/2000/svg}g[@data-type="reference"]')
|
||||
documents = schedules + references
|
||||
@@ -427,10 +437,10 @@ class SheetBuilder:
|
||||
for image in images:
|
||||
view.remove(image)
|
||||
|
||||
def get_href(self, element):
|
||||
return urllib.parse.unquote(element.attrib.get("{http://www.w3.org/1999/xlink}href")).replace('\\','/')
|
||||
def get_href(self, element: ET.Element) -> str:
|
||||
return urllib.parse.unquote(element.attrib.get("{http://www.w3.org/1999/xlink}href")).replace("\\", "/")
|
||||
|
||||
def parse_embedded_svg(self, image, data):
|
||||
def parse_embedded_svg(self, image: ET.Element, data: dict) -> ET.Element:
|
||||
group = ET.Element("g")
|
||||
group.attrib["transform"] = "translate({},{})".format(
|
||||
self.convert_to_mm(image.attrib.get("x")), self.convert_to_mm(image.attrib.get("y"))
|
||||
@@ -466,7 +476,7 @@ class SheetBuilder:
|
||||
group.append(child)
|
||||
return group
|
||||
|
||||
def change_titleblock(self, sheet, titleblock_name):
|
||||
def change_titleblock(self, sheet: ifcopenshell.entity_instance, titleblock_name: str) -> None:
|
||||
ootb_titleblock_path = os.path.join(self.data_dir, "templates", "titleblocks", titleblock_name + ".svg")
|
||||
titleblock_path = tool.Drawing.get_default_titleblock_path(titleblock_name)
|
||||
sheet_path = tool.Drawing.get_document_uri(sheet, "LAYOUT")
|
||||
@@ -499,7 +509,7 @@ class SheetBuilder:
|
||||
|
||||
sheet_tree.write(sheet_path)
|
||||
|
||||
def convert_to_mm(self, value):
|
||||
def convert_to_mm(self, value: str) -> float:
|
||||
# CSS is what defines these possibilities
|
||||
# https://www.w3.org/TR/SVG/refs.html#ref-css-values-3
|
||||
# https://www.w3.org/TR/css-values-3/#absolute-lengths
|
||||
@@ -520,5 +530,5 @@ class SheetBuilder:
|
||||
return float(value[0:-2]) * (1 / 96) * 2.54 * 10
|
||||
return float(value)
|
||||
|
||||
def mm_to_px(self, value):
|
||||
def mm_to_px(self, value: float) -> float:
|
||||
return (value / 25.4) * 96
|
||||
|
||||
@@ -719,14 +719,15 @@ class SvgWriter:
|
||||
self.svg.text(sheet_id, insert=(text_position[0], text_position[1] + 2.5), class_="ELEVATION", **text_style)
|
||||
)
|
||||
|
||||
def get_reference_and_sheet_id_from_annotation(self, element):
|
||||
def get_reference_and_sheet_id_from_annotation(self, element: ifcopenshell.entity_instance) -> tuple[str, str]:
|
||||
reference_id = "-"
|
||||
sheet_id = "-"
|
||||
drawing = tool.Drawing.get_annotation_element(element)
|
||||
reference = tool.Drawing.get_drawing_reference(drawing)
|
||||
if reference:
|
||||
for sheet_reference in tool.Ifc.get().by_type("IfcDocumentReference"):
|
||||
if sheet_reference.Description != "DRAWING" or sheet_reference.Location != reference.Location:
|
||||
reference_description = tool.Drawing.get_reference_description(sheet_reference)
|
||||
if reference_description != "DRAWING" or sheet_reference.Location != reference.Location:
|
||||
continue
|
||||
sheet = tool.Drawing.get_reference_document(sheet_reference)
|
||||
if sheet:
|
||||
|
||||
@@ -29,6 +29,7 @@ classes = (
|
||||
operator.GetRepresentationIfcParameters,
|
||||
operator.DuplicateMoveLinkedAggregate,
|
||||
operator.DuplicateMoveLinkedAggregateMacro,
|
||||
operator.DuplicateLinkedAggregateTo3dCursor,
|
||||
operator.OverrideDelete,
|
||||
operator.OverrideDuplicateMove,
|
||||
operator.OverrideDuplicateMoveLinked,
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import ifcopenshell.util.element
|
||||
import blenderbim.tool as tool
|
||||
import ifcopenshell.util.placement
|
||||
from mathutils import Vector
|
||||
@@ -222,7 +223,7 @@ class ConnectionsData:
|
||||
@classmethod
|
||||
def is_connection_realization(cls):
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
connections = element.IsConnectionRealization
|
||||
connections = getattr(element, "IsConnectionRealization", None)
|
||||
if not connections:
|
||||
return
|
||||
|
||||
|
||||
@@ -538,7 +538,7 @@ class Helper:
|
||||
)
|
||||
position = None
|
||||
if self.file.schema == "IFC2X3":
|
||||
position = self.file.createIfcAxis2Placement2D(self.file.createIfcCartesianPoint([0.0, 0.0, 0.0]))
|
||||
position = self.file.createIfcAxis2Placement2D(self.file.createIfcCartesianPoint([0.0, 0.0]))
|
||||
curve = self.file.createIfcRectangleProfileDef("AREA", None, position, xdim, ydim)
|
||||
return {"curve_ucs": curve_ucs, "curve": curve}
|
||||
|
||||
|
||||
@@ -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):
|
||||
@@ -215,6 +236,13 @@ class SwitchRepresentation(bpy.types.Operator, Operator):
|
||||
disable_opening_subtractions: bpy.props.BoolProperty()
|
||||
should_switch_all_meshes: bpy.props.BoolProperty()
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if context.active_object.mode == "OBJECT":
|
||||
return True
|
||||
cls.poll_message_set("Only available in OBJECT mode - Press TAB in the viewport")
|
||||
return False
|
||||
|
||||
def _execute(self, context):
|
||||
target_representation = tool.Ifc.get().by_id(self.ifc_definition_id)
|
||||
target = target_representation.ContextOfItems
|
||||
@@ -223,6 +251,8 @@ class SwitchRepresentation(bpy.types.Operator, Operator):
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
continue
|
||||
if not obj.mode == "OBJECT":
|
||||
continue
|
||||
if obj == context.active_object:
|
||||
representation = target_representation
|
||||
else:
|
||||
@@ -370,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):
|
||||
[
|
||||
@@ -538,6 +572,8 @@ class OverrideDelete(bpy.types.Operator):
|
||||
row.prop(self, "is_batch", text="Enable Faster Deletion")
|
||||
|
||||
def _execute(self, context):
|
||||
start_time = time()
|
||||
|
||||
if self.is_batch:
|
||||
ifcopenshell.util.element.batch_remove_deep2(tool.Ifc.get())
|
||||
|
||||
@@ -562,6 +598,11 @@ class OverrideDelete(bpy.types.Operator):
|
||||
IfcStore.add_transaction_operation(self)
|
||||
# Required otherwise gizmos are still visible
|
||||
context.view_layer.objects.active = None
|
||||
|
||||
operator_time = time() - start_time
|
||||
if operator_time > 10:
|
||||
self.report({"INFO"}, "IFC Delete was finished in {:.2f} seconds".format(operator_time))
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
def rollback(self, data):
|
||||
@@ -843,7 +884,7 @@ class OverrideDuplicateMove(bpy.types.Operator):
|
||||
|
||||
# Recreate decompositions
|
||||
tool.Root.recreate_decompositions(decomposition_relationships, old_to_new)
|
||||
OverrideDuplicateMove.handle_linked_aggregates(old_to_new)
|
||||
OverrideDuplicateMove.remove_linked_aggregate_data(old_to_new)
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
return old_to_new
|
||||
|
||||
@@ -896,25 +937,21 @@ class OverrideDuplicateMove(bpy.types.Operator):
|
||||
if entity in old_to_new.keys():
|
||||
core.remove_connection(tool.Geometry, connection=connection)
|
||||
|
||||
@staticmethod
|
||||
def handle_linked_aggregates(old_to_new):
|
||||
def remove_linked_aggregate_data(old_to_new):
|
||||
for old, new in old_to_new.items():
|
||||
pset = ifcopenshell.util.element.get_pset(new[0], "BBIM_Linked_Aggregate")
|
||||
if pset:
|
||||
old_aggregate = ifcopenshell.util.element.get_aggregate(old)
|
||||
new_aggregate = ifcopenshell.util.element.get_aggregate(new[0])
|
||||
if old_aggregate == new_aggregate:
|
||||
parts = ifcopenshell.util.element.get_parts(new_aggregate)
|
||||
if parts:
|
||||
index = DuplicateMoveLinkedAggregate.get_max_index(parts)
|
||||
index += 1
|
||||
pset = tool.Ifc.get().by_id(pset["id"])
|
||||
ifcopenshell.api.run(
|
||||
"pset.edit_pset",
|
||||
tool.Ifc.get(),
|
||||
pset=pset,
|
||||
properties={"Index": index},
|
||||
)
|
||||
pset = tool.Ifc.get().by_id(pset["id"])
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=new[0],pset=pset)
|
||||
|
||||
if new[0].is_a("IfcElementAssembly"):
|
||||
linked_aggregate_group = [
|
||||
r.RelatingGroup
|
||||
for r in getattr(new[0], "HasAssignments", []) or []
|
||||
if r.is_a("IfcRelAssignsToGroup")
|
||||
if "BBIM_Linked_Aggregate" in r.RelatingGroup.Name
|
||||
]
|
||||
tool.Ifc.run("group.unassign_group", group=linked_aggregate_group[0], products=[new[0]])
|
||||
|
||||
|
||||
class OverrideDuplicateMoveLinkedMacro(bpy.types.Macro):
|
||||
@@ -940,14 +977,15 @@ class OverrideDuplicateMoveLinked(bpy.types.Operator):
|
||||
|
||||
|
||||
class DuplicateMoveLinkedAggregateMacro(bpy.types.Macro):
|
||||
bl_description = "Create and move a new linked aggregate"
|
||||
bl_idname = "bim.object_duplicate_move_linked_aggregate_macro"
|
||||
bl_label = "IFC Duplicate Linked Aggregate"
|
||||
bl_label = "IFC Duplicate and Move Linked Aggregate"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
|
||||
class DuplicateMoveLinkedAggregate(bpy.types.Operator):
|
||||
bl_idname = "bim.object_duplicate_move_linked_aggregate"
|
||||
bl_label = "IFC Duplicate Linked Aggregate"
|
||||
bl_label = "IFC Duplicate and Move Linked Aggregate"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
is_interactive: bpy.props.BoolProperty(name="Is Interactive", default=True)
|
||||
|
||||
@@ -962,7 +1000,7 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator):
|
||||
return DuplicateMoveLinkedAggregate.execute_ifc_duplicate_linked_aggregate_operator(self, context)
|
||||
|
||||
@staticmethod
|
||||
def execute_ifc_duplicate_linked_aggregate_operator(self, context):
|
||||
def execute_ifc_duplicate_linked_aggregate_operator(self, context, location_from_3d_cursor=False):
|
||||
self.new_active_obj = None
|
||||
self.group_name = "BBIM_Linked_Aggregate"
|
||||
self.pset_name = "BBIM_Linked_Aggregate"
|
||||
@@ -974,15 +1012,15 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator):
|
||||
obj.select_set(True)
|
||||
parts = ifcopenshell.util.element.get_parts(element)
|
||||
if parts:
|
||||
index = DuplicateMoveLinkedAggregate.get_max_index(parts)
|
||||
index = get_max_index(parts)
|
||||
add_linked_aggregate_pset(element, index)
|
||||
index += 1
|
||||
for part in parts:
|
||||
if part.is_a("IfcElementAssembly"):
|
||||
select_objects_and_add_data(part)
|
||||
else:
|
||||
add_linked_aggregate_pset(part, index)
|
||||
index += 1
|
||||
index = add_linked_aggregate_pset(part, index)
|
||||
# index += 1
|
||||
|
||||
obj = tool.Ifc.get_object(part)
|
||||
obj.select_set(True)
|
||||
@@ -999,6 +1037,8 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator):
|
||||
pset=pset,
|
||||
properties={"Index": index},
|
||||
)
|
||||
|
||||
index += 1
|
||||
else:
|
||||
pass
|
||||
|
||||
@@ -1014,10 +1054,8 @@ 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)
|
||||
ifcopenshell.api.run(
|
||||
"group.assign_group", tool.Ifc.get(), products=[element], group=linked_aggregate_group
|
||||
)
|
||||
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):
|
||||
for new in old_to_new.values():
|
||||
@@ -1042,6 +1080,49 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator):
|
||||
split_name = new_obj.name.split(".")
|
||||
new_obj.name = split_name[0] + "_" + number
|
||||
|
||||
def get_max_index(parts):
|
||||
psets = [ifcopenshell.util.element.get_pset(p, "BBIM_Linked_Aggregate") for p in parts]
|
||||
index = [i["Index"] for i in psets if i]
|
||||
if len(index) > 0:
|
||||
index = max(index)
|
||||
return index
|
||||
else:
|
||||
return 0
|
||||
|
||||
def copy_linked_aggregate_data(old_to_new):
|
||||
for old, new in old_to_new.items():
|
||||
pset = ifcopenshell.util.element.get_pset(old, "BBIM_Linked_Aggregate")
|
||||
if pset:
|
||||
new_pset = ifcopenshell.api.run(
|
||||
"pset.add_pset", tool.Ifc.get(), product=new[0], name=self.pset_name
|
||||
)
|
||||
|
||||
ifcopenshell.api.run(
|
||||
"pset.edit_pset",
|
||||
tool.Ifc.get(),
|
||||
pset=new_pset,
|
||||
properties={"Index": pset["Index"]},
|
||||
)
|
||||
|
||||
if new[0].is_a("IfcElementAssembly"):
|
||||
linked_aggregate_group = [
|
||||
r.RelatingGroup
|
||||
for r in getattr(old, "HasAssignments", []) or []
|
||||
if r.is_a("IfcRelAssignsToGroup")
|
||||
if "BBIM_Linked_Aggregate" in r.RelatingGroup.Name
|
||||
]
|
||||
tool.Ifc.run("group.assign_group", group=linked_aggregate_group[0], products=new)
|
||||
|
||||
def get_location_from_3d_cursor(old_to_new, aggregate):
|
||||
base_obj = tool.Ifc.get_object(aggregate)
|
||||
base_obj_location = base_obj.location.copy()
|
||||
|
||||
for new in old_to_new.values():
|
||||
new_obj = tool.Ifc.get_object(new[0])
|
||||
location_diff = new_obj.location - base_obj_location
|
||||
new_obj.location = context.scene.cursor.location + location_diff
|
||||
|
||||
|
||||
if len(context.selected_objects) != 1:
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -1062,27 +1143,34 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator):
|
||||
|
||||
old_to_new = OverrideDuplicateMove.execute_ifc_duplicate_operator(self, context, linked=True)
|
||||
|
||||
tool.Root.recreate_aggregate(old_to_new)
|
||||
|
||||
copy_linked_aggregate_data(old_to_new)
|
||||
|
||||
custom_incremental_naming_for_element_assembly(old_to_new)
|
||||
|
||||
# Recreate aggregate relationship
|
||||
for old in old_to_new.keys():
|
||||
if old.is_a("IfcElementAssembly"):
|
||||
tool.Root.recreate_aggregate(old_to_new)
|
||||
if location_from_3d_cursor:
|
||||
get_location_from_3d_cursor(old_to_new, selected_element)
|
||||
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
|
||||
return old_to_new
|
||||
|
||||
@staticmethod
|
||||
def get_max_index(parts):
|
||||
psets = [ifcopenshell.util.element.get_pset(p, "BBIM_Linked_Aggregate") for p in parts]
|
||||
index = [i["Index"] for i in psets if i]
|
||||
if len(index) > 0:
|
||||
index = max(index)
|
||||
return index
|
||||
else:
|
||||
return 0
|
||||
|
||||
class DuplicateLinkedAggregateTo3dCursor(bpy.types.Operator):
|
||||
bl_idname = "bim.duplicate_linked_aggregate_to_3d_cursor"
|
||||
bl_label = "IFC Duplicate Linked Aggregate to 3d Cursor"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return len(context.selected_objects) > 0
|
||||
|
||||
def execute(self, context):
|
||||
return OverrideDuplicateMove.execute_duplicate_operator(self, context, linked=False)
|
||||
|
||||
def _execute(self, context):
|
||||
return DuplicateMoveLinkedAggregate.execute_ifc_duplicate_linked_aggregate_operator(self, context, location_from_3d_cursor=True)
|
||||
|
||||
class RefreshLinkedAggregate(bpy.types.Operator):
|
||||
bl_idname = "bim.refresh_linked_aggregate"
|
||||
@@ -1216,6 +1304,22 @@ class RefreshLinkedAggregate(bpy.types.Operator):
|
||||
|
||||
return list(set(linked_aggregate_groups)), selected_parents
|
||||
|
||||
def get_original_matrix(element, base_instance):
|
||||
selected_obj = tool.Ifc.get_object(base_instance)
|
||||
selected_matrix = selected_obj.matrix_world
|
||||
object_duplicate = tool.Ifc.get_object(element)
|
||||
duplicate_matrix = object_duplicate.matrix_world.decompose()
|
||||
|
||||
return selected_matrix, duplicate_matrix
|
||||
|
||||
def set_new_matrix(selected_matrix, duplicate_matrix, old_to_new):
|
||||
for old, new in old_to_new.items():
|
||||
new_obj = tool.Ifc.get_object(new[0])
|
||||
new_base_matrix = Matrix.LocRotScale(*duplicate_matrix)
|
||||
matrix_diff = Matrix.inverted(selected_matrix) @ new_obj.matrix_world
|
||||
new_obj_matrix = new_base_matrix @ matrix_diff
|
||||
new_obj.matrix_world = new_obj_matrix
|
||||
|
||||
active_element = tool.Ifc.get_entity(context.active_object)
|
||||
if not active_element:
|
||||
self.report({"INFO"}, "Object has no Ifc metadata.")
|
||||
@@ -1255,10 +1359,7 @@ class RefreshLinkedAggregate(bpy.types.Operator):
|
||||
|
||||
element_aggregate = ifcopenshell.util.element.get_aggregate(element)
|
||||
|
||||
selected_obj = tool.Ifc.get_object(base_instance)
|
||||
selected_matrix = selected_obj.matrix_world
|
||||
object_duplicate = tool.Ifc.get_object(element)
|
||||
duplicate_matrix = object_duplicate.matrix_world.decompose()
|
||||
selected_matrix, duplicate_matrix = get_original_matrix(element, base_instance)
|
||||
|
||||
original_names = get_original_names(element)
|
||||
|
||||
@@ -1269,15 +1370,9 @@ class RefreshLinkedAggregate(bpy.types.Operator):
|
||||
|
||||
tool.Ifc.get_object(base_instance).select_set(True)
|
||||
|
||||
old_to_new = DuplicateMoveLinkedAggregate.execute_ifc_duplicate_linked_aggregate_operator(
|
||||
self, context
|
||||
)
|
||||
for old, new in old_to_new.items():
|
||||
new_obj = tool.Ifc.get_object(new[0])
|
||||
new_base_matrix = Matrix.LocRotScale(*duplicate_matrix)
|
||||
matrix_diff = Matrix.inverted(selected_matrix) @ new_obj.matrix_world
|
||||
new_obj_matrix = new_base_matrix @ matrix_diff
|
||||
new_obj.matrix_world = new_obj_matrix
|
||||
old_to_new = DuplicateMoveLinkedAggregate.execute_ifc_duplicate_linked_aggregate_operator(self, context)
|
||||
|
||||
set_new_matrix(selected_matrix, duplicate_matrix, old_to_new)
|
||||
|
||||
for old, new in old_to_new.items():
|
||||
if element_aggregate and new[0].is_a("IfcElementAssembly"):
|
||||
@@ -1409,14 +1504,13 @@ class OverridePasteBuffer(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.view3d.pastebuffer()
|
||||
if IfcStore.get_file():
|
||||
for obj in context.selected_objects:
|
||||
# Pasted objects may come from another Blender session, or even
|
||||
# from the same session where the original object has since
|
||||
# been deleted. As the source element may not exist, paste will
|
||||
# always unlink the element. If you want to duplicate an
|
||||
# element, use the duplicate commands.
|
||||
tool.Root.unlink_object(obj)
|
||||
for obj in context.selected_objects:
|
||||
# Pasted objects may come from another Blender session, or even
|
||||
# from the same session where the original object has since
|
||||
# been deleted. As the source element may not exist, paste will
|
||||
# always unlink the element. If you want to duplicate an
|
||||
# element, use the duplicate commands.
|
||||
tool.Root.unlink_object(obj)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
|
||||
@@ -138,3 +138,4 @@ class BIMGeometryProperties(PropertyGroup):
|
||||
name="IFC Interaction Mode",
|
||||
update=update_mode,
|
||||
)
|
||||
representation_from_object: PointerProperty(type=bpy.types.Object)
|
||||
|
||||
@@ -24,6 +24,7 @@ classes = (
|
||||
operator.AddLayer,
|
||||
operator.AddListItem,
|
||||
operator.AddMaterial,
|
||||
operator.DuplicateMaterial,
|
||||
operator.AddMaterialSet,
|
||||
operator.AddProfile,
|
||||
operator.AssignMaterial,
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
import os
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.doc
|
||||
import ifcopenshell.util.schema
|
||||
import blenderbim.tool as tool
|
||||
@@ -166,6 +167,9 @@ class ObjectMaterialData:
|
||||
cls.data["type_material"] = cls.type_material()
|
||||
cls.data["material_type"] = cls.material_type()
|
||||
cls.data["active_material_constituents"] = cls.active_material_constituents()
|
||||
# after material_name and type_material
|
||||
cls.data["is_type_material_overridden"] = cls.is_type_material_overridden()
|
||||
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
@@ -294,8 +298,7 @@ class ObjectMaterialData:
|
||||
|
||||
@classmethod
|
||||
def material_name(cls):
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
material = ifcopenshell.util.element.get_material(element)
|
||||
material = cls.material
|
||||
if material:
|
||||
return getattr(material, "Name", None) or "Unnamed"
|
||||
|
||||
@@ -339,3 +342,18 @@ class ObjectMaterialData:
|
||||
if not cls.material or not material.is_a("IfcMaterialConstituentSet"):
|
||||
return []
|
||||
return [m.Name for m in material.MaterialConstituents if m.Name]
|
||||
|
||||
@classmethod
|
||||
def is_type_material_overridden(cls) -> bool:
|
||||
if not cls.data["type_material"]:
|
||||
return False
|
||||
|
||||
# try to avoid accessing ifc
|
||||
if cls.data["material_name"] != cls.data["type_material"]:
|
||||
return True
|
||||
|
||||
# in theory material can be overridden by the same material
|
||||
# so we check occurrence material explicitly
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
occurrence_material = ifcopenshell.util.element.get_material(element, should_inherit=False)
|
||||
return bool(occurrence_material)
|
||||
|
||||
@@ -134,6 +134,19 @@ class AddMaterial(bpy.types.Operator, tool.Ifc.Operator):
|
||||
material_prop_purge()
|
||||
|
||||
|
||||
class DuplicateMaterial(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.duplicate_material"
|
||||
bl_label = "Diplicate Material"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
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_prop_purge()
|
||||
bpy.ops.bim.load_materials()
|
||||
|
||||
|
||||
class AddMaterialSet(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.add_material_set"
|
||||
bl_label = "Add Material Set"
|
||||
|
||||
@@ -61,6 +61,8 @@ class BIM_PT_materials(Panel):
|
||||
if self.props.materials and self.props.active_material_index < len(self.props.materials):
|
||||
material = self.props.materials[self.props.active_material_index]
|
||||
if material.ifc_definition_id:
|
||||
op = row.operator("bim.duplicate_material", text="", icon="DUPLICATE")
|
||||
op.material = material.ifc_definition_id
|
||||
op = row.operator("bim.select_by_material", text="", icon="RESTRICT_SELECT_OFF")
|
||||
op.material = material.ifc_definition_id
|
||||
op = row.operator("bim.enable_editing_material", text="", icon="GREASEPENCIL")
|
||||
@@ -175,7 +177,13 @@ class BIM_PT_object_material(Panel):
|
||||
|
||||
if ObjectMaterialData.data["type_material"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Inherited Material: " + ObjectMaterialData.data["type_material"], icon="CON_CHILDOF")
|
||||
if ObjectMaterialData.data["is_type_material_overridden"]:
|
||||
row.label(
|
||||
text=f"Inherited Material Is Occurrence Overridden",
|
||||
icon="CON_CHILDOF",
|
||||
)
|
||||
else:
|
||||
row.label(text="Inherited Material: " + ObjectMaterialData.data["type_material"], icon="CON_CHILDOF")
|
||||
|
||||
if ObjectMaterialData.data["material_class"]:
|
||||
return self.draw_material_ui()
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -32,7 +32,9 @@ class AddInstanceFlooringCoveringFromCursor(bpy.types.Operator, tool.Ifc.Operato
|
||||
def poll(cls, context):
|
||||
collection = context.view_layer.active_layer_collection.collection
|
||||
collection_obj = collection.BIMCollectionProperties.obj
|
||||
return tool.Ifc.get_entity(collection_obj)
|
||||
relating_type_id = int(bpy.data.scenes["Scene"].BIMModelProperties.relating_type_id)
|
||||
relating_type = ifcopenshell.util.element.get_predefined_type(tool.Ifc.get().by_id(relating_type_id))
|
||||
return tool.Ifc.get_entity(collection_obj) and relating_type == "FLOORING"
|
||||
|
||||
def _execute(self, context):
|
||||
|
||||
@@ -61,7 +63,9 @@ class AddInstanceCeilingCoveringFromCursor(bpy.types.Operator, tool.Ifc.Operator
|
||||
def poll(cls, context):
|
||||
collection = context.view_layer.active_layer_collection.collection
|
||||
collection_obj = collection.BIMCollectionProperties.obj
|
||||
return tool.Ifc.get_entity(collection_obj)
|
||||
relating_type_id = int(bpy.data.scenes["Scene"].BIMModelProperties.relating_type_id)
|
||||
relating_type = ifcopenshell.util.element.get_predefined_type(tool.Ifc.get().by_id(relating_type_id))
|
||||
return tool.Ifc.get_entity(collection_obj) and relating_type == "CEILING"
|
||||
|
||||
def _execute(self, context):
|
||||
|
||||
@@ -116,9 +120,11 @@ class AddInstanceFlooringCoveringsFromWalls(bpy.types.Operator, tool.Ifc.Operato
|
||||
def poll(cls, context):
|
||||
active_obj = bpy.context.active_object
|
||||
element = tool.Ifc.get_entity(active_obj)
|
||||
relating_type_id = int(bpy.data.scenes["Scene"].BIMModelProperties.relating_type_id)
|
||||
relating_type = ifcopenshell.util.element.get_predefined_type(tool.Ifc.get().by_id(relating_type_id))
|
||||
if element:
|
||||
if element.is_a("IfcWall") and tool.Model.get_usage_type(element) == "LAYER2":
|
||||
return context.selected_objects
|
||||
return context.selected_objects and relating_type == "FLOORING"
|
||||
|
||||
def _execute(self, context):
|
||||
# This only works based on a 2D plan only considering the standard
|
||||
@@ -147,7 +153,7 @@ class AddInstanceFlooringCoveringsFromWalls(bpy.types.Operator, tool.Ifc.Operato
|
||||
|
||||
class AddInstanceCeilingCoveringsFromWalls(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.add_instance_ceiling_coverings_from_walls"
|
||||
bl_label = "Add Ceilings From Walls"
|
||||
bl_label = "Add Ceiling From Walls"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Add instance ceiling coverings from selected walls. The active object must be a wall and layered vertically"
|
||||
|
||||
@@ -155,9 +161,11 @@ class AddInstanceCeilingCoveringsFromWalls(bpy.types.Operator, tool.Ifc.Operator
|
||||
def poll(cls, context):
|
||||
active_obj = bpy.context.active_object
|
||||
element = tool.Ifc.get_entity(active_obj)
|
||||
relating_type_id = int(bpy.data.scenes["Scene"].BIMModelProperties.relating_type_id)
|
||||
relating_type = ifcopenshell.util.element.get_predefined_type(tool.Ifc.get().by_id(relating_type_id))
|
||||
if element:
|
||||
if element.is_a("IfcWall") and tool.Model.get_usage_type(element) == "LAYER2":
|
||||
return context.selected_objects
|
||||
return context.selected_objects and relating_type == "CEILING"
|
||||
|
||||
def _execute(self, context):
|
||||
# This only works based on a 2D plan only considering the standard
|
||||
|
||||
@@ -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()
|
||||
@@ -251,6 +251,8 @@ class AuthoringData:
|
||||
|
||||
@classmethod
|
||||
def predefined_type(cls):
|
||||
if not tool.Blender.enum_property_has_valid_index(cls.props, "relating_type_id", cls.data["relating_type_id"]):
|
||||
return
|
||||
relating_type_id = cls.props.relating_type_id
|
||||
if not relating_type_id:
|
||||
return
|
||||
|
||||
@@ -644,6 +644,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 = None
|
||||
|
||||
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)
|
||||
@@ -145,7 +153,9 @@ class FilledOpeningGenerator:
|
||||
"geometry.assign_representation", tool.Ifc.get(), product=opening, representation=mapped_representation
|
||||
)
|
||||
else:
|
||||
representation = self.generate_opening_from_filling(filling, filling_obj)
|
||||
representation = self.generate_opening_from_filling(
|
||||
filling, filling_obj, opening_thickness_si=opening_thickness_si
|
||||
)
|
||||
opening = ifcopenshell.api.run(
|
||||
"root.create_entity", tool.Ifc.get(), ifc_class="IfcOpeningElement", predefined_type="OPENING"
|
||||
)
|
||||
@@ -187,7 +197,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 +262,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: Optional[float] = None,
|
||||
) -> 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 = 1.2 if opening_thickness_si is None else 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 +361,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)
|
||||
|
||||
@@ -39,6 +39,7 @@ from mathutils import Vector, Matrix
|
||||
from bpy_extras.object_utils import AddObjectHelper
|
||||
from . import prop
|
||||
import json
|
||||
from typing import Any, Union
|
||||
|
||||
|
||||
class EnableAddType(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -137,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))
|
||||
@@ -511,7 +515,7 @@ def regenerate_profile_usage(usecase_path, ifc_file, settings):
|
||||
)
|
||||
|
||||
|
||||
def ensure_material_assigned(usecase_path, ifc_file, settings):
|
||||
def ensure_material_assigned(usecase_path: str, ifc_file: ifcopenshell.file, settings: dict[str, Any]) -> None:
|
||||
if usecase_path == "material.assign_material":
|
||||
if not settings.get("material", None):
|
||||
return
|
||||
@@ -524,53 +528,76 @@ def ensure_material_assigned(usecase_path, ifc_file, settings):
|
||||
]:
|
||||
elements.extend(rel.RelatedObjects)
|
||||
|
||||
for element in elements:
|
||||
obj = IfcStore.get_element(element.GlobalId)
|
||||
if not obj or not obj.data:
|
||||
continue
|
||||
|
||||
element_material = ifcopenshell.util.element.get_material(element)
|
||||
material = [m for m in ifc_file.traverse(element_material) if m.is_a("IfcMaterial")]
|
||||
|
||||
object_material_ids = [
|
||||
om.BIMObjectProperties.ifc_definition_id
|
||||
for om in obj.data.materials
|
||||
if om is not None and om.BIMObjectProperties.ifc_definition_id
|
||||
]
|
||||
|
||||
if material and material[0].id() in object_material_ids:
|
||||
continue
|
||||
|
||||
if len(obj.data.materials) == 1:
|
||||
obj.data.materials.clear()
|
||||
|
||||
if not material:
|
||||
continue
|
||||
|
||||
obj.data.materials.append(IfcStore.get_element(material[0].id()))
|
||||
update_blender_ifc_materials(elements)
|
||||
|
||||
|
||||
def ensure_material_unassigned(usecase_path, ifc_file, settings):
|
||||
def ensure_material_unassigned(usecase_path: str, ifc_file: ifcopenshell.file, settings: dict[str, Any]) -> None:
|
||||
elements = settings["products"]
|
||||
if elements[0].is_a("IfcElementType"):
|
||||
elements.extend(ifcopenshell.util.element.get_types(elements[0]))
|
||||
update_blender_ifc_materials(elements)
|
||||
|
||||
|
||||
def update_blender_ifc_materials(elements: list[ifcopenshell.entity_instance]) -> None:
|
||||
"""update mesh blender materials that have ifc material connected to them
|
||||
by replacing them with `blender_material`"""
|
||||
# since different elements can share meshes (e.g. occurrecnes without openings)
|
||||
# we need to make sure not to affect them accidentally
|
||||
meshes_users: dict[bpy.types.Mesh, set[bpy.types.Object]] = dict()
|
||||
for obj in bpy.data.objects:
|
||||
if not obj.data:
|
||||
continue
|
||||
meshes_users.setdefault(obj.data, set()).add(obj)
|
||||
|
||||
objects: set[bpy.types.Object] = set()
|
||||
for element in elements:
|
||||
obj = tool.Ifc.get_object(element)
|
||||
obj: bpy.types.Object = tool.Ifc.get_object(element)
|
||||
if not obj or not obj.data:
|
||||
continue
|
||||
element_material = ifcopenshell.util.element.get_material(element)
|
||||
if element_material:
|
||||
objects.add(obj)
|
||||
|
||||
meshes: set[bpy.types.Mesh] = {obj.data for obj in objects}
|
||||
|
||||
for mesh in meshes:
|
||||
mesh_users = meshes_users[mesh]
|
||||
if not mesh_users.issubset(objects):
|
||||
continue
|
||||
to_remove = []
|
||||
for i, slot in enumerate(obj.material_slots):
|
||||
if not slot.material:
|
||||
|
||||
# NOTE: we need `obj` as removing materials and appending them to `mesh.materials`
|
||||
# will mess up mesh faces material indices
|
||||
|
||||
# NOTE: we make an assumption here that all mesh users
|
||||
# have the same material - they either inherit it from the type
|
||||
# or type doesn't have a material.
|
||||
#
|
||||
# If we add option to UI to add materials overriding type materials
|
||||
# then this assumption won't be safe anymore
|
||||
|
||||
obj = next(iter(mesh_users))
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
current_material = ifcopenshell.util.element.get_material(element)
|
||||
if current_material:
|
||||
current_material = tool.Ifc.get_object(current_material)
|
||||
|
||||
material_replaced = False
|
||||
|
||||
for material_slot in obj.material_slots:
|
||||
material = material_slot.material
|
||||
if material is None:
|
||||
continue
|
||||
material = tool.Ifc.get_entity(slot.material)
|
||||
if material:
|
||||
to_remove.append(i)
|
||||
total_removed = 0
|
||||
for i in to_remove:
|
||||
obj.active_material_index = i - total_removed
|
||||
with bpy.context.temp_override(object=obj):
|
||||
bpy.ops.object.material_slot_remove()
|
||||
total_removed += 1
|
||||
ifc_material = tool.Ifc.get_entity(material)
|
||||
# it's blender material for style, so ignore it
|
||||
if not ifc_material:
|
||||
continue
|
||||
if ifc_material == current_material:
|
||||
continue
|
||||
material_slot.material = current_material
|
||||
material_replaced = True
|
||||
|
||||
if not material_replaced and current_material:
|
||||
mesh.materials.append(current_material)
|
||||
|
||||
# clear empty slots
|
||||
for i, material in reversed(list(enumerate(mesh.materials[:]))):
|
||||
if material is None:
|
||||
mesh.materials.pop(index=i)
|
||||
|
||||
@@ -931,13 +931,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):
|
||||
|
||||
@@ -535,5 +535,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"}
|
||||
|
||||
@@ -757,7 +757,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"}
|
||||
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ import blenderbim.core.type
|
||||
|
||||
class GenerateSpace(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.generate_space"
|
||||
bl_label = "Generate Space"
|
||||
bl_label = "Generate Space from Cursor"
|
||||
bl_options = {"REGISTER"}
|
||||
bl_description = (
|
||||
"Create a space from the cursor position. "
|
||||
@@ -35,30 +35,34 @@ class GenerateSpace(bpy.types.Operator, tool.Ifc.Operator):
|
||||
"select the right space collection and run the operator"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
collection = context.view_layer.active_layer_collection.collection
|
||||
collection_obj = collection.BIMCollectionProperties.obj
|
||||
active_obj = context.active_object
|
||||
element = tool.Ifc.get_entity(active_obj)
|
||||
return tool.Ifc.get_entity(collection_obj) and not element.is_a("IfcWall")
|
||||
# @classmethod
|
||||
# def poll(cls, context):
|
||||
# print(context)
|
||||
# collection = context.view_layer.active_layer_collection.collection
|
||||
# collection_obj = collection.BIMCollectionProperties.obj
|
||||
# active_obj = context.active_object
|
||||
# element = tool.Ifc.get_entity(active_obj)
|
||||
# return tool.Ifc.get_entity(collection_obj) and not element.is_a("IfcWall")
|
||||
|
||||
def _execute(self, context):
|
||||
# This works as a 2.5 extruded polygon based on a cutting plane. Note
|
||||
# that rooms exclude walls (i.e. not to wall midpoint or exterior /
|
||||
# exterior edge.
|
||||
|
||||
def msg(self, context):
|
||||
self.layout.label(text="NO ACTIVE STOREY")
|
||||
def msg_no_collection(self, context):
|
||||
self.layout.label(text="NO ACTIVE COLLECTION. PLEASE SELECT A SPATIAL COLLECTION OBJECT OR A WALL")
|
||||
|
||||
def msg_no_active_storey(self, context):
|
||||
self.layout.label(text="NO ACTIVE STOREY. PLEASE SELECT A SPATIAL COLLECTION OBJECT")
|
||||
|
||||
collection = context.view_layer.active_layer_collection.collection
|
||||
collection_obj = collection.BIMCollectionProperties.obj
|
||||
if not collection_obj:
|
||||
context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
|
||||
context.window_manager.popup_menu(msg_no_collection, title="Error", icon="ERROR")
|
||||
return
|
||||
spatial_element = tool.Ifc.get_entity(collection_obj)
|
||||
if not spatial_element:
|
||||
context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
|
||||
if not spatial_element or not spatial_element.is_a("IfcBuildingStorey"):
|
||||
context.window_manager.popup_menu(msg_no_active_storey, title="Error", icon="ERROR")
|
||||
return
|
||||
|
||||
core.generate_space(tool.Ifc, tool.Spatial, tool.Model, tool.Type)
|
||||
@@ -70,12 +74,12 @@ class GenerateSpacesFromWalls(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Generate spaces from selected walls. The active object must be a wall"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
active_obj = context.active_object
|
||||
element = tool.Ifc.get_entity(active_obj)
|
||||
if element:
|
||||
return context.selected_objects and element.is_a("IfcWall")
|
||||
# @classmethod
|
||||
# def poll(cls, context):
|
||||
# active_obj = context.active_object
|
||||
# element = tool.Ifc.get_entity(active_obj)
|
||||
# if element:
|
||||
# return context.selected_objects and element.is_a("IfcWall")
|
||||
|
||||
def _execute(self, context):
|
||||
# This only works based on a 2D plan only considering the standard
|
||||
@@ -87,19 +91,30 @@ class GenerateSpacesFromWalls(bpy.types.Operator, tool.Ifc.Operator):
|
||||
element = tool.Ifc.get_entity(active_obj)
|
||||
container = tool.Spatial.get_container(element)
|
||||
|
||||
def msg_no_active_object(self, context):
|
||||
self.layout.label(text="No active object. Please select a wall")
|
||||
def msg_no_active_wall(self, context):
|
||||
self.layout.label(text="The active object is not a wall. Please select a wall.")
|
||||
def msg_no_container(self, context):
|
||||
self.layout.label(text="The wall is not contained. Please the selected wall in a building container")
|
||||
def msg_no_selected_objects(self, context):
|
||||
self.layout.label(text="No selected objects found. Please select walls.")
|
||||
|
||||
if not active_obj:
|
||||
self.report({"ERROR"}, "No active object. Please select a wall")
|
||||
context.window_manager.popup_menu(msg_no_active_object, title="Error", icon="ERROR")
|
||||
return
|
||||
|
||||
element = tool.Ifc.get_entity(active_obj)
|
||||
if element and not element.is_a("IfcWall"):
|
||||
return self.report({"ERROR"}, "The active object is not a wall. Please select a wall.")
|
||||
context.window_manager.popup_menu(msg_no_active_wall, title="Error", icon="ERROR")
|
||||
return
|
||||
|
||||
if not container:
|
||||
self.report({"ERROR"}, "The wall is not contained.")
|
||||
context.window_manager.popup_menu(msg_no_container, title="Error", icon="ERROR")
|
||||
return
|
||||
|
||||
if not context.selected_objects:
|
||||
self.report({"ERROR"}, "No selected objects found. Please select walls.")
|
||||
context.window_manager.popup_menu(msg_no_selected_objects, title="Error", icon="ERROR")
|
||||
return
|
||||
|
||||
core.generate_spaces_from_walls(tool.Ifc, tool.Spatial, tool.Collector)
|
||||
|
||||
@@ -337,6 +337,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"}
|
||||
|
||||
@@ -56,8 +56,11 @@ class LaunchTypeManager(bpy.types.Operator):
|
||||
ifc_class = props.ifc_class or AuthoringData.data["ifc_element_type"]
|
||||
else:
|
||||
ifc_class = AuthoringData.data["ifc_element_type"]
|
||||
props.type_class = ifc_class
|
||||
bpy.ops.bim.load_type_thumbnails(ifc_class=ifc_class, offset=0, limit=9)
|
||||
|
||||
# will be None if project has no types
|
||||
if ifc_class is not None:
|
||||
props.type_class = ifc_class
|
||||
bpy.ops.bim.load_type_thumbnails(ifc_class=ifc_class, offset=0, limit=9)
|
||||
return context.window_manager.invoke_popup(self, width=550)
|
||||
|
||||
def draw(self, context):
|
||||
|
||||
@@ -590,6 +590,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"}
|
||||
|
||||
@@ -378,6 +378,7 @@ class BimToolUI:
|
||||
op.depth = cls.props.extrusion_depth
|
||||
|
||||
add_layout_hotkey_operator(cls.layout, "Extend", "S_E", "")
|
||||
add_layout_hotkey_operator(cls.layout, "Flip", "S_F", bpy.ops.bim.flip_object.__doc__)
|
||||
|
||||
if AuthoringData.data["active_class"] in (
|
||||
"IfcCableCarrierSegment",
|
||||
@@ -385,8 +386,8 @@ class BimToolUI:
|
||||
"IfcDuctSegment",
|
||||
"IfcPipeSegment",
|
||||
):
|
||||
add_layout_hotkey_operator(cls.layout, "Add Fitting", "S_F", "")
|
||||
|
||||
add_layout_hotkey_operator(cls.layout, "Add Fitting", "S_Y", "")
|
||||
if context.region.type != "TOOL_HEADER":
|
||||
cls.layout.operator("bim.mep_add_bend")
|
||||
cls.layout.operator("bim.mep_add_transition")
|
||||
@@ -394,7 +395,6 @@ class BimToolUI:
|
||||
|
||||
else:
|
||||
add_layout_hotkey_operator(cls.layout, "Edit Axis", "A_E", "")
|
||||
add_layout_hotkey_operator(cls.layout, "Flip", "S_F", bpy.ops.bim.flip_object.__doc__)
|
||||
add_layout_hotkey_operator(cls.layout, "Butt", "S_T", "")
|
||||
add_layout_hotkey_operator(cls.layout, "Mitre", "S_Y", "")
|
||||
add_layout_hotkey_operator(cls.layout, "Rotate 90", "S_R", bpy.ops.bim.rotate_90.__doc__)
|
||||
@@ -719,10 +719,9 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bpy.ops.bim.flip_wall()
|
||||
elif self.active_class in ("IfcWindow", "IfcWindowStandardCase", "IfcDoor", "IfcDoorStandardCase"):
|
||||
bpy.ops.bim.flip_fill()
|
||||
elif self.active_class in ("IfcBeam", "IfcColumn"):
|
||||
elif self.active_material_usage == "PROFILE":
|
||||
bpy.ops.bim.flip_object(flip_local_axes="XZ")
|
||||
elif self.active_class in ("IfcDuctSegment", "IfcPipeSegment", "IfcCableCarrierSegment", "IfcCableSegment"):
|
||||
bpy.ops.bim.fit_flow_segments()
|
||||
|
||||
|
||||
def hotkey_S_G(self):
|
||||
obj = bpy.context.active_object
|
||||
@@ -808,9 +807,12 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
|
||||
return
|
||||
if self.active_material_usage == "LAYER2":
|
||||
bpy.ops.bim.join_wall(join_type="V")
|
||||
elif self.active_class in ("IfcDuctSegment", "IfcPipeSegment", "IfcCableCarrierSegment", "IfcCableSegment"):
|
||||
bpy.ops.bim.fit_flow_segments()
|
||||
elif self.active_material_usage == "PROFILE":
|
||||
bpy.ops.bim.extend_profile(join_type="V")
|
||||
|
||||
|
||||
def hotkey_S_B(self):
|
||||
bpy.ops.bim.add_boundary()
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ from . import ui, prop, operator, data
|
||||
|
||||
classes = (
|
||||
operator.AddProfileDef,
|
||||
operator.DuplicateProfileDef,
|
||||
operator.DisableEditingArbitraryProfile,
|
||||
operator.DisableEditingProfile,
|
||||
operator.DisableProfileEditingUI,
|
||||
|
||||
@@ -66,9 +66,15 @@ class RemoveProfileDef(bpy.types.Operator, tool.Ifc.Operator):
|
||||
profile: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMProfileProperties
|
||||
current_index = props.active_profile_index
|
||||
ifcopenshell.api.run("profile.remove_profile", tool.Ifc.get(), profile=tool.Ifc.get().by_id(self.profile))
|
||||
bpy.ops.bim.load_profiles()
|
||||
|
||||
# preserve selected index if possible
|
||||
if props.profiles:
|
||||
props.active_profile_index = min(current_index, len(props.profiles) - 1)
|
||||
|
||||
|
||||
class EnableEditingProfile(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.enable_editing_profile"
|
||||
@@ -126,6 +132,27 @@ class AddProfileDef(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bpy.ops.bim.load_profiles()
|
||||
|
||||
|
||||
class DuplicateProfileDef(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.duplicate_profile_def"
|
||||
bl_label = "Duplicate Profile"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
props = context.scene.BIMProfileProperties
|
||||
if len(props.profiles) > props.active_profile_index:
|
||||
return True
|
||||
cls.poll_message_set("No profile selected to duplicate.")
|
||||
return False
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMProfileProperties
|
||||
ifc_file = tool.Ifc.get()
|
||||
profile = ifc_file.by_id(props.profiles[props.active_profile_index].ifc_definition_id)
|
||||
tool.Profile.duplicate_profile(profile)
|
||||
bpy.ops.bim.load_profiles()
|
||||
|
||||
|
||||
class EnableEditingArbitraryProfile(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.enable_editing_arbitrary_profile"
|
||||
bl_label = "Enable Editing Arbitrary Profile"
|
||||
|
||||
@@ -70,6 +70,7 @@ class BIM_PT_profiles(Panel):
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.props, "profile_classes", text="")
|
||||
row.operator("bim.add_profile_def", text="", icon="ADD")
|
||||
row.operator("bim.duplicate_profile_def", icon="DUPLICATE", text="")
|
||||
|
||||
self.layout.template_list(
|
||||
"BIM_UL_profiles",
|
||||
|
||||
@@ -36,8 +36,8 @@ classes = (
|
||||
operator.EnableCulling,
|
||||
operator.EnableEditingHeader,
|
||||
operator.ExportIFC,
|
||||
operator.ExportIFCDeprecated,
|
||||
operator.FlipClippingPlane,
|
||||
operator.ImportIFC,
|
||||
operator.LinkIfc,
|
||||
operator.LoadLink,
|
||||
operator.LoadLinkedProject,
|
||||
@@ -97,7 +97,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.export_ifc", "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
|
||||
@@ -47,6 +48,7 @@ from mathutils import Vector, Matrix
|
||||
from bpy.app.handlers import persistent
|
||||
from blenderbim.bim.module.project.data import LinksData
|
||||
from blenderbim.bim.module.project.decorator import ProjectDecorator, ClippingPlaneDecorator
|
||||
from typing import Union
|
||||
|
||||
|
||||
class NewProject(bpy.types.Operator):
|
||||
@@ -301,7 +303,7 @@ class AssignLibraryDeclaration(bpy.types.Operator):
|
||||
ifcopenshell.api.run(
|
||||
"project.assign_declaration",
|
||||
self.file,
|
||||
definition=self.file.by_id(self.definition),
|
||||
definitions=[self.file.by_id(self.definition)],
|
||||
relating_context=self.file.by_type("IfcProjectLibrary")[0],
|
||||
)
|
||||
element_name = self.props.active_library_element
|
||||
@@ -337,7 +339,7 @@ class UnassignLibraryDeclaration(bpy.types.Operator):
|
||||
ifcopenshell.api.run(
|
||||
"project.unassign_declaration",
|
||||
self.file,
|
||||
definition=self.file.by_id(self.definition),
|
||||
definitions=[self.file.by_id(self.definition)],
|
||||
relating_context=self.file.by_type("IfcProjectLibrary")[0],
|
||||
)
|
||||
element_name = self.props.active_library_element
|
||||
@@ -673,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):
|
||||
@@ -867,7 +873,16 @@ class LinkIfc(bpy.types.Operator):
|
||||
except:
|
||||
pass # Perhaps on another drive or something
|
||||
new.name = filepath
|
||||
bpy.ops.bim.load_link(filepath=filepath, false_origin=self.false_origin)
|
||||
status = bpy.ops.bim.load_link(filepath=filepath, false_origin=self.false_origin)
|
||||
if status == {"CANCELLED"}:
|
||||
error_msg = (
|
||||
f'Error processing IFC file "{self.filepath}" '
|
||||
"was critical and blend file either wasn't saved or wasn't updated. "
|
||||
"See logs above in system console for details."
|
||||
)
|
||||
print(error_msg)
|
||||
self.report({"ERROR"}, error_msg)
|
||||
return {"FINISHED"}
|
||||
print(f"Finished linking {len(files)} IFCs", time.time() - start)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -946,10 +961,12 @@ class LoadLink(bpy.types.Operator):
|
||||
if self.filepath.lower().endswith(".blend"):
|
||||
self.link_blend(filepath)
|
||||
elif self.filepath.lower().endswith(".ifc"):
|
||||
self.link_ifc()
|
||||
status = self.link_ifc()
|
||||
if status:
|
||||
return status
|
||||
return {"FINISHED"}
|
||||
|
||||
def link_blend(self, filepath):
|
||||
def link_blend(self, filepath: str) -> None:
|
||||
with bpy.data.libraries.load(filepath, link=True) as (data_from, data_to):
|
||||
data_to.scenes = data_from.scenes
|
||||
for scene in bpy.data.scenes:
|
||||
@@ -962,7 +979,7 @@ class LoadLink(bpy.types.Operator):
|
||||
link = bpy.context.scene.BIMProjectProperties.links.get(self.filepath)
|
||||
link.is_loaded = True
|
||||
|
||||
def link_ifc(self):
|
||||
def link_ifc(self) -> Union[set[str], None]:
|
||||
blend_filepath = self.filepath + ".cache.blend"
|
||||
h5_filepath = self.filepath + ".cache.h5"
|
||||
|
||||
@@ -982,11 +999,14 @@ except Exception as e:
|
||||
exit(1)
|
||||
"""
|
||||
|
||||
t = time.time()
|
||||
with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as temp_file:
|
||||
temp_file.write(code)
|
||||
run = subprocess.run([bpy.app.binary_path, "-b", "--python", temp_file.name, "--python-exit-code", "1"])
|
||||
if run.returncode == 1:
|
||||
print("An error occurred while processing your IFC.")
|
||||
if not os.path.exists(blend_filepath) or os.stat(blend_filepath).st_mtime < t:
|
||||
return {"CANCELLED"}
|
||||
|
||||
self.link_blend(blend_filepath)
|
||||
|
||||
@@ -1086,8 +1106,8 @@ class ToggleLinkVisibility(bpy.types.Operator):
|
||||
]
|
||||
|
||||
|
||||
class ExportIFC(bpy.types.Operator):
|
||||
bl_idname = "export_ifc.bim"
|
||||
class ExportIFCBase:
|
||||
bl_idname = "bim.export_ifc"
|
||||
bl_label = "Save IFC"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filename_ext = ".ifc"
|
||||
@@ -1204,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):
|
||||
bl_idname = "bim.export_ifc"
|
||||
|
||||
|
||||
# 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):
|
||||
|
||||
@@ -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.export_ifc", 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.export_ifc", text="Save IFC Project As...")
|
||||
op.should_save_as = True
|
||||
self.layout.separator()
|
||||
self.layout.operator("bim.revert_project")
|
||||
|
||||
@@ -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 = {}
|
||||
@@ -341,7 +175,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,7 +206,7 @@ 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)
|
||||
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)
|
||||
bpy.ops.bim.enable_pset_editing(
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
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
|
||||
|
||||
@@ -143,7 +143,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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -185,6 +185,8 @@ class IfcClassData:
|
||||
if element:
|
||||
if element.is_a("IfcOpeningElement") or element.is_a("IfcOpeningStandardCase"):
|
||||
return False
|
||||
if element.is_a() in ("IfcWindowStyle", "IfcDoorStyle"): #see https://github.com/IfcOpenShell/IfcOpenShell/issues/4622#issuecomment-2095676368
|
||||
return True
|
||||
for product in cls.ifc_products():
|
||||
if element.is_a(product[0]):
|
||||
return True
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -30,6 +30,7 @@ classes = (
|
||||
operator.DisableAddingPresentationStyle,
|
||||
operator.DisableEditingStyle,
|
||||
operator.DisableEditingStyles,
|
||||
operator.DuplicateStyle,
|
||||
operator.EditStyle,
|
||||
operator.EditSurfaceStyle,
|
||||
operator.EnableAddingPresentationStyle,
|
||||
|
||||
@@ -23,6 +23,7 @@ import blenderbim.bim.handler
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.style as core
|
||||
import ifcopenshell.util.representation
|
||||
from blenderbim.bim.module.style.prop import switch_shading
|
||||
from pathlib import Path
|
||||
from mathutils import Vector
|
||||
|
||||
@@ -126,6 +127,10 @@ class DisableEditingStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Style.reload_material_from_ifc(material)
|
||||
props.is_editing_style = 0
|
||||
|
||||
# restore selected style type
|
||||
material = tool.Ifc.get_object(style)
|
||||
material.BIMStyleProperties.active_style_type = material.BIMStyleProperties.active_style_type
|
||||
|
||||
|
||||
class EditStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.edit_style"
|
||||
@@ -246,14 +251,15 @@ class BrowseExternalStyle(bpy.types.Operator):
|
||||
)
|
||||
|
||||
def invoke(self, context, event):
|
||||
external_style = None
|
||||
style_elements = None
|
||||
if self.active_surface_style_id:
|
||||
style = tool.Ifc.get().by_id(self.active_surface_style_id)
|
||||
external_style = tool.Style.get_style_elements(style).get("IfcExternallyDefinedSurfaceStyle", None)
|
||||
style_elements = tool.Style.get_style_elements(style)
|
||||
|
||||
# automatically select previously selected external style in file browser
|
||||
# if it exists in the file
|
||||
if external_style and self.filepath == "":
|
||||
if style_elements and self.filepath == "" and tool.Style.has_blender_external_style(style_elements):
|
||||
external_style = style_elements["IfcExternallyDefinedSurfaceStyle"]
|
||||
style_path = Path(tool.Ifc.resolve_uri(external_style.Location))
|
||||
self.directory = str(style_path.parent)
|
||||
self.filepath = str(style_path)
|
||||
@@ -310,6 +316,9 @@ class BrowseExternalStyle(bpy.types.Operator):
|
||||
attributes["Location"].string_value = filepath
|
||||
attributes["Identification"].string_value = f"{self.data_block_type}/{self.data_block}"
|
||||
attributes["Name"].string_value = self.data_block
|
||||
|
||||
style = tool.Ifc.get().by_id(self.active_surface_style_id)
|
||||
bpy.ops.bim.activate_external_style(material_name=tool.Ifc.get_object(style).name)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -325,14 +334,23 @@ class ActivateExternalStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
material = context.active_object.active_material
|
||||
else:
|
||||
material = bpy.data.materials[self.material_name]
|
||||
external_style = tool.Style.get_style_elements(material)["IfcExternallyDefinedSurfaceStyle"]
|
||||
data_block_type, data_block = external_style.Identification.split("/")
|
||||
style_path = Path(tool.Ifc.resolve_uri(external_style.Location))
|
||||
|
||||
props = context.scene.BIMStylesProperties
|
||||
if props.is_editing:
|
||||
location = props.external_style_attributes["Location"].string_value
|
||||
identification = props.external_style_attributes["Identification"].string_value
|
||||
else:
|
||||
external_style = tool.Style.get_style_elements(material)["IfcExternallyDefinedSurfaceStyle"]
|
||||
location = external_style.Location
|
||||
identification = external_style.Identification
|
||||
|
||||
data_block_type, data_block = identification.split("/")
|
||||
style_path = Path(tool.Ifc.resolve_uri(location))
|
||||
|
||||
if style_path.suffix != ".blend":
|
||||
self.report(
|
||||
{"ERROR"},
|
||||
f"Error loading external style for \"{material.name}\" - only Blender external styles are supported",
|
||||
f'Error loading external style for "{material.name}" - only Blender external styles are supported',
|
||||
)
|
||||
return {"CANCELLED"}
|
||||
|
||||
@@ -493,6 +511,22 @@ class EnableAddingPresentationStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
props.is_adding = True
|
||||
|
||||
|
||||
class DuplicateStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.duplicate_style"
|
||||
bl_label = "Duplicate Style"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
style: bpy.props.IntProperty(name="Style ID")
|
||||
|
||||
def _execute(self, context):
|
||||
style_type = context.scene.BIMStylesProperties.style_type
|
||||
ifc_file = tool.Ifc.get()
|
||||
style = ifc_file.by_id(self.style)
|
||||
tool.Style.duplicate_style(style)
|
||||
bpy.ops.bim.disable_editing_styles()
|
||||
bpy.ops.bim.load_styles(style_type=style_type)
|
||||
|
||||
|
||||
class DisableAddingPresentationStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.disable_adding_presentation_style"
|
||||
bl_label = "Disable Add Presentation Style"
|
||||
@@ -571,7 +605,8 @@ class EnableEditingSurfaceStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
props.is_editing_class = self.ifc_class
|
||||
tool.Style.set_surface_style_props()
|
||||
|
||||
surface_style = tool.Style.get_style_elements(style).get(self.ifc_class, None)
|
||||
style_elements = tool.Style.get_style_elements(style)
|
||||
surface_style = style_elements.get(self.ifc_class, None)
|
||||
attributes = tool.Style.get_style_ui_props_attributes(self.ifc_class)
|
||||
|
||||
# lighting style require special handling since Attribute doesn't support colors
|
||||
@@ -591,6 +626,17 @@ class EnableEditingSurfaceStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
attributes.clear()
|
||||
blenderbim.bim.helper.import_attributes2(surface_style or self.ifc_class, attributes, callback)
|
||||
|
||||
material = tool.Ifc.get_object(style)
|
||||
active_style_type = material.BIMStyleProperties.active_style_type
|
||||
if self.ifc_class == "IfcExternallyDefinedSurfaceStyle" and active_style_type != "External":
|
||||
if tool.Style.has_blender_external_style(style_elements):
|
||||
switch_shading(material, "External")
|
||||
elif (
|
||||
self.ifc_class in ("IfcSurfaceStyleShading", "IfcSurfaceStyleRendering", "IfcSurfaceStyleWithTextures")
|
||||
and active_style_type != "Shading"
|
||||
):
|
||||
switch_shading(material, "Shading")
|
||||
|
||||
|
||||
class EditSurfaceStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.edit_surface_style"
|
||||
@@ -615,6 +661,10 @@ class EditSurfaceStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
self.props.is_editing_style = 0
|
||||
core.load_styles(tool.Style, style_type=self.props.style_type)
|
||||
|
||||
# restore selected style type
|
||||
material = tool.Ifc.get_object(self.style)
|
||||
material.BIMStyleProperties.active_style_type = material.BIMStyleProperties.active_style_type
|
||||
|
||||
def edit_existing_style(self):
|
||||
material = tool.Ifc.get_object(self.style)
|
||||
if self.surface_style.is_a() == "IfcSurfaceStyleShading":
|
||||
|
||||
@@ -33,6 +33,8 @@ from bpy.props import (
|
||||
)
|
||||
|
||||
import gettext
|
||||
from typing import Literal
|
||||
|
||||
|
||||
_ = gettext.gettext
|
||||
|
||||
@@ -251,19 +253,15 @@ class BIMStylesProperties(PropertyGroup):
|
||||
)
|
||||
|
||||
|
||||
def update_shading_style(self, context):
|
||||
blender_material = self.id_data
|
||||
style_elements = tool.Style.get_style_elements(blender_material)
|
||||
if self.active_style_type == "External":
|
||||
if tool.Style.has_blender_external_style(style_elements):
|
||||
try:
|
||||
bpy.ops.bim.activate_external_style(material_name=blender_material.name)
|
||||
except RuntimeError as error:
|
||||
if str(error).startswith("Error: Error loading external style for "):
|
||||
return
|
||||
raise error
|
||||
|
||||
elif self.active_style_type == "Shading":
|
||||
def switch_shading(blender_material: bpy.types.Material, style_type: Literal["External", "Shading"]) -> None:
|
||||
if style_type == "External":
|
||||
try:
|
||||
bpy.ops.bim.activate_external_style(material_name=blender_material.name)
|
||||
except RuntimeError as error:
|
||||
if str(error).startswith("Error: Error loading external style for "):
|
||||
return
|
||||
raise error
|
||||
elif style_type == "Shading":
|
||||
style_elements = tool.Style.get_style_elements(blender_material)
|
||||
rendering_style = None
|
||||
texture_style = None
|
||||
@@ -279,6 +277,16 @@ def update_shading_style(self, context):
|
||||
|
||||
if rendering_style and texture_style:
|
||||
tool.Loader.create_surface_style_with_textures(blender_material, rendering_style, texture_style)
|
||||
|
||||
|
||||
def update_shading_style(self, context):
|
||||
blender_material = self.id_data
|
||||
style_elements = tool.Style.get_style_elements(blender_material)
|
||||
if self.active_style_type == "External":
|
||||
if tool.Style.has_blender_external_style(style_elements):
|
||||
switch_shading(blender_material, self.active_style_type)
|
||||
elif self.active_style_type == "Shading":
|
||||
switch_shading(blender_material, self.active_style_type)
|
||||
tool.Style.record_shading(blender_material)
|
||||
|
||||
|
||||
|
||||
@@ -44,19 +44,33 @@ class BIM_PT_styles(Panel):
|
||||
|
||||
self.props = context.scene.BIMStylesProperties
|
||||
|
||||
if self.props.is_editing:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="{} {}s".format(len(self.props.styles), self.props.style_type), icon="SHADING_RENDERED")
|
||||
if not self.props.is_adding:
|
||||
row.operator("bim.enable_adding_presentation_style", text="", icon="ADD")
|
||||
row.operator("bim.disable_editing_styles", text="", icon="CANCEL")
|
||||
else:
|
||||
if not self.props.is_editing:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="{} Styles".format(StylesData.data["total_styles"]), icon="SHADING_RENDERED")
|
||||
blenderbim.bim.helper.prop_with_search(row, self.props, "style_type", text="")
|
||||
row.operator("bim.load_styles", text="", icon="IMPORT").style_type = self.props.style_type
|
||||
return
|
||||
|
||||
active_style = self.props.styles and self.props.active_style_index < len(self.props.styles)
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="{} {}s".format(len(self.props.styles), self.props.style_type), icon="SHADING_RENDERED")
|
||||
row.operator("bim.disable_editing_styles", text="", icon="CANCEL")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.alignment = "RIGHT"
|
||||
if not self.props.is_adding:
|
||||
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.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
|
||||
|
||||
self.layout.template_list("BIM_UL_styles", "", self.props, "styles", self.props, "active_style_index")
|
||||
|
||||
# adding a new IfcSurfaceStyle
|
||||
@@ -77,17 +91,7 @@ class BIM_PT_styles(Panel):
|
||||
row.operator("bim.disable_adding_presentation_style", text="", icon="CANCEL")
|
||||
|
||||
# style ui tools
|
||||
if self.props.styles and self.props.active_style_index < len(self.props.styles):
|
||||
row = self.layout.row(align=True)
|
||||
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]
|
||||
|
||||
op = row.operator("bim.enable_editing_style", text="Edit Style", icon="GREASEPENCIL")
|
||||
op.style = style.ifc_definition_id
|
||||
row.operator("bim.select_by_style", text="", icon="RESTRICT_SELECT_OFF").style = style.ifc_definition_id
|
||||
row.operator("bim.remove_style", text="", icon="X").style = style.ifc_definition_id
|
||||
|
||||
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="")
|
||||
|
||||
@@ -146,23 +146,51 @@ class SelectType(bpy.types.Operator):
|
||||
relating_type: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
element = tool.Ifc.get().by_id(self.relating_type)
|
||||
obj = tool.Ifc.get_object(element)
|
||||
if obj:
|
||||
try:
|
||||
tool.Blender.select_and_activate_single_object(context, obj)
|
||||
except:
|
||||
self.report({"INFO"}, "Type object is hidden.")
|
||||
# IfcTypeProducts are only used for annotations and not part of the model interface.
|
||||
if element.is_a() != "IfcTypeProduct":
|
||||
try:
|
||||
context.scene.BIMModelProperties.ifc_class = element.is_a()
|
||||
context.scene.BIMModelProperties.relating_type_id = str(self.relating_type)
|
||||
except:
|
||||
# Potentially our BIM Tool is filtered to a specific element.
|
||||
pass
|
||||
|
||||
if self.relating_type: #if operator button sends a relating_type, the iterator only selects this one type
|
||||
element = tool.Ifc.get().by_id(self.relating_type)
|
||||
obj = tool.Ifc.get_object(element)
|
||||
selected_objs = [obj]
|
||||
else: #else, the iterator selects all the types of all the selected objects
|
||||
selected_objs = context.selected_objects
|
||||
active_obj = context.active_object
|
||||
selected_objs.append(active_obj) #update selected_objs so the active_obj is at the end of the list
|
||||
|
||||
last_relating_type_obj = None
|
||||
types_collection_in_view_layer = self.find_collection_in_ifcproject(context, collection_name = "Types")
|
||||
types_collection_in_view_layer.hide_viewport = False
|
||||
types_collection = bpy.data.collections.get("Types")
|
||||
for type_obj in types_collection.objects:
|
||||
type_obj.hide_set(True)
|
||||
for obj in selected_objs:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
relating_type = ifcopenshell.util.element.get_type(element)
|
||||
if relating_type:
|
||||
relating_type_obj = tool.Ifc.get_object(relating_type)
|
||||
if relating_type_obj:
|
||||
if relating_type_obj.hide_get():
|
||||
relating_type_obj.hide_set(False)
|
||||
relating_type_obj.select_set(True)
|
||||
last_relating_type_obj = relating_type_obj
|
||||
if not element.is_a("IfcTypeObject"):
|
||||
obj.select_set(False)
|
||||
|
||||
context.view_layer.objects.active = last_relating_type_obj #makes the active_obj's type the active object
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
def find_collection_in_ifcproject(self, context, collection_name):
|
||||
|
||||
ifc_project_collection = None
|
||||
for child in context.view_layer.layer_collection.children:
|
||||
if "IfcProject" in child.name:
|
||||
ifc_project_collection = child
|
||||
break
|
||||
|
||||
if ifc_project_collection:
|
||||
collection_in_view_layer = ifc_project_collection.children.get(collection_name)
|
||||
return collection_in_view_layer
|
||||
|
||||
|
||||
class SelectSimilarType(bpy.types.Operator):
|
||||
bl_idname = "bim.select_similar_type"
|
||||
@@ -484,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):
|
||||
|
||||
@@ -88,7 +88,7 @@ class BIM_PT_type(Panel):
|
||||
if TypeData.data["relating_type"]:
|
||||
row.label(text=TypeData.data["relating_type"]["name"])
|
||||
op = row.operator("bim.select_type", icon="OBJECT_DATA", text="")
|
||||
op.relating_type = TypeData.data["relating_type"]["id"]
|
||||
op.relating_type = 0 #will only select the relating types of only the selected objects
|
||||
row.operator("bim.select_similar_type", icon="RESTRICT_SELECT_OFF", text="")
|
||||
row.operator("bim.enable_editing_type", icon="GREASEPENCIL", text="")
|
||||
row.operator("bim.unassign_type", icon="X", text="")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -289,8 +291,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 +319,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 +404,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
|
||||
|
||||
|
||||
@@ -1,88 +1,117 @@
|
||||
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
|
||||
|
||||
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_schedule: ifcopenshell.entity_instance):
|
||||
ifc.run("cost.remove_cost_schedule", cost_schedule=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_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):
|
||||
def assign_cost_item_type(
|
||||
ifc: tool.Ifc, cost: tool.Cost, spatial: tool.Spatial, cost_item: ifcopenshell.entity_instance, prop_name
|
||||
):
|
||||
product_types = spatial.get_selected_product_types()
|
||||
[
|
||||
ifc.run("control.assign_control", relating_control=cost_item, related_object=product_type)
|
||||
@@ -91,7 +120,9 @@ def assign_cost_item_type(ifc, cost, spatial, cost_item, prop_name):
|
||||
cost.load_cost_item_types(cost_item)
|
||||
|
||||
|
||||
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
|
||||
):
|
||||
if not product_types:
|
||||
product_types = spatial.get_selected_product_types()
|
||||
[
|
||||
@@ -101,83 +132,85 @@ def unassign_cost_item_type(ifc, cost, spatial, cost_item, product_types):
|
||||
cost.load_cost_item_types(cost_item)
|
||||
|
||||
|
||||
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 +219,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, format, 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 +304,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 +312,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 +325,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 +335,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 +343,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)
|
||||
|
||||
@@ -46,7 +46,8 @@ def add_instance_flooring_covering_from_cursor(ifc, spatial, model, Type, geomet
|
||||
|
||||
obj = spatial.get_named_obj_from_mesh(name, mesh)
|
||||
|
||||
spatial.set_obj_origin_to_cursor_position(obj)
|
||||
spatial.set_obj_origin_to_cursor_position_and_zero_elevation(obj)
|
||||
spatial.traslate_obj_to_z_location(obj, z)
|
||||
spatial.link_obj_to_active_collection(obj)
|
||||
points = spatial.get_2d_vertices_from_obj(obj)
|
||||
points = spatial.get_scaled_2d_vertices(points)
|
||||
@@ -74,7 +75,7 @@ def add_instance_ceiling_covering_from_cursor(ifc, spatial, model, Type, geometr
|
||||
|
||||
else:
|
||||
x, y, z, h, mat = spatial.get_x_y_z_h_mat_from_cursor()
|
||||
z = covering.get_z_from_ceiling_height()
|
||||
ceiling_height = covering.get_z_from_ceiling_height()
|
||||
|
||||
space_polygon = spatial.get_space_polygon_from_context_visible_objects(x, y)
|
||||
|
||||
@@ -87,8 +88,8 @@ def add_instance_ceiling_covering_from_cursor(ifc, spatial, model, Type, geometr
|
||||
|
||||
obj = spatial.get_named_obj_from_mesh(name, mesh)
|
||||
|
||||
spatial.set_obj_origin_to_cursor_position(obj)
|
||||
spatial.traslate_obj_to_z_location(obj, z)
|
||||
spatial.set_obj_origin_to_cursor_position_and_zero_elevation(obj)
|
||||
spatial.traslate_obj_to_z_location(obj, z+ceiling_height)
|
||||
spatial.link_obj_to_active_collection(obj)
|
||||
points = spatial.get_2d_vertices_from_obj(obj)
|
||||
points = spatial.get_scaled_2d_vertices(points)
|
||||
|
||||
@@ -16,35 +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
|
||||
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:
|
||||
@@ -57,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=None):
|
||||
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)
|
||||
@@ -77,31 +86,37 @@ def add_sheet(ifc, drawing, titleblock=None):
|
||||
else:
|
||||
attributes = {"Identification": identification, "Name": "UNTITLED", "Scope": "SHEET"}
|
||||
ifc.run("document.edit_information", information=sheet, attributes=attributes)
|
||||
ifc.run(
|
||||
"document.edit_reference",
|
||||
reference=layout,
|
||||
attributes={"Location": drawing.get_default_layout_path(identification, "UNTITLED"), "Description": "LAYOUT"},
|
||||
|
||||
attributes = drawing.generate_reference_attributes(
|
||||
layout, Location=drawing.get_default_layout_path(identification, "UNTITLED"), Description="LAYOUT"
|
||||
)
|
||||
ifc.run(
|
||||
"document.edit_reference",
|
||||
reference=titleblock_reference,
|
||||
attributes={"Location": drawing.get_default_titleblock_path(titleblock), "Description": "TITLEBLOCK"},
|
||||
ifc.run("document.edit_reference", reference=layout, attributes=attributes)
|
||||
|
||||
attributes = drawing.generate_reference_attributes(
|
||||
layout, Location=drawing.get_default_titleblock_path(titleblock), Description="TITLEBLOCK"
|
||||
)
|
||||
ifc.run("document.edit_reference", reference=titleblock_reference, attributes=attributes)
|
||||
|
||||
drawing.create_svg_sheet(sheet, titleblock)
|
||||
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))
|
||||
drawing.add_drawings(sheet)
|
||||
try:
|
||||
drawing.add_drawings(sheet)
|
||||
except FileNotFoundError:
|
||||
path_layout = drawing.get_document_uri(sheet, "LAYOUT")
|
||||
if drawing.does_file_exist(path_layout):
|
||||
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))
|
||||
@@ -111,8 +126,12 @@ def remove_sheet(ifc, drawing, sheet=None):
|
||||
drawing.import_sheets()
|
||||
|
||||
|
||||
def rename_sheet(ifc, drawing, sheet=None, identification=None, name=None):
|
||||
ifc.run("document.edit_information", information=sheet, attributes={"Identification": identification, "Name": name})
|
||||
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:
|
||||
attributes = {"Identification": identification, "Name": name}
|
||||
ifc.run("document.edit_information", information=sheet, attributes=attributes)
|
||||
for reference in drawing.get_document_references(sheet):
|
||||
description = drawing.get_reference_description(reference)
|
||||
if description == "SHEET":
|
||||
@@ -133,29 +152,32 @@ def rename_sheet(ifc, drawing, sheet=None, identification=None, name=None):
|
||||
drawing.move_file(old_location, ifc.resolve_uri(new_location))
|
||||
|
||||
|
||||
def rename_reference(ifc, reference=None, identification=None):
|
||||
ifc.run("document.edit_reference", reference=reference, attributes={"Identification": identification})
|
||||
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)
|
||||
@@ -168,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)
|
||||
@@ -253,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)
|
||||
@@ -290,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()
|
||||
|
||||
@@ -318,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)
|
||||
@@ -352,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:
|
||||
@@ -375,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
|
||||
|
||||
@@ -425,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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -42,3 +42,19 @@ def add_pset(ifc, pset, blender, obj_name, obj_type):
|
||||
if not pset.is_pset_applicable(element, pset_name):
|
||||
continue
|
||||
pset.enable_pset_editing(pset_id=0, pset_name=pset_name, pset_type="PSET", obj=obj_name, obj_type=obj_type)
|
||||
|
||||
|
||||
def enable_pset_editing(pset_tool, pset, pset_name, pset_type, obj_name, obj_type):
|
||||
props = pset_tool.get_pset_props(obj_name, obj_type)
|
||||
pset_tool.clear_blender_pset_properties(props)
|
||||
|
||||
pset_template = pset_tool.get_pset_template(pset_name)
|
||||
|
||||
if pset_template:
|
||||
pset_tool.import_pset_from_template(pset_template, pset, props)
|
||||
|
||||
if pset:
|
||||
pset_tool.import_pset_from_existing(pset, props)
|
||||
pset_tool.set_active_pset(props, pset)
|
||||
else:
|
||||
pset_tool.enable_proposed_pset(props, pset_name, pset_type)
|
||||
|
||||
@@ -16,8 +16,18 @@
|
||||
# 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 copy_class(ifc, collector, geometry, root, obj=None):
|
||||
if TYPE_CHECKING:
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import blenderbim.tool as tool
|
||||
|
||||
|
||||
def copy_class(
|
||||
ifc: tool.Ifc, collector: tool.Collector, geometry: tool.Geometry, root: tool.Root, obj: bpy.types.Object
|
||||
) -> ifcopenshell.entity_instance:
|
||||
element = ifc.get_entity(obj)
|
||||
if not element:
|
||||
return
|
||||
@@ -48,16 +58,16 @@ def copy_class(ifc, collector, geometry, root, obj=None):
|
||||
|
||||
|
||||
def assign_class(
|
||||
ifc,
|
||||
collector,
|
||||
root,
|
||||
obj=None,
|
||||
ifc_class=None,
|
||||
predefined_type=None,
|
||||
should_add_representation=True,
|
||||
context=None,
|
||||
ifc_representation_class=None,
|
||||
):
|
||||
ifc: tool.Ifc,
|
||||
collector: tool.Collector,
|
||||
root: tool.Root,
|
||||
obj: bpy.types.Object,
|
||||
ifc_class: str,
|
||||
context: ifcopenshell.entity_instance,
|
||||
predefined_type: Optional[str] = None,
|
||||
should_add_representation: bool = True,
|
||||
ifc_representation_class: Optional[str] = None,
|
||||
) -> ifcopenshell.entity_instance:
|
||||
if ifc.get_entity(obj):
|
||||
return
|
||||
|
||||
|
||||
@@ -18,11 +18,11 @@
|
||||
|
||||
from __future__ import annotations
|
||||
from typing import TYPE_CHECKING, Optional, Union
|
||||
import blenderbim.core.tool as tool
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import blenderbim.tool as tool
|
||||
|
||||
|
||||
def reference_structure(
|
||||
@@ -189,7 +189,8 @@ def generate_space(ifc, spatial, model, Type):
|
||||
name = "Space"
|
||||
|
||||
obj = spatial.get_named_obj_from_mesh(name, mesh)
|
||||
spatial.set_obj_origin_to_cursor_position(obj)
|
||||
spatial.set_obj_origin_to_cursor_position_and_zero_elevation(obj)
|
||||
spatial.traslate_obj_to_z_location(obj, z)
|
||||
spatial.link_obj_to_active_collection(obj)
|
||||
spatial.assign_ifcspace_class_to_obj(obj)
|
||||
|
||||
@@ -214,7 +215,7 @@ def generate_spaces_from_walls(ifc, spatial, collector):
|
||||
|
||||
obj = spatial.get_named_obj_from_bmesh(name, bmesh=bm)
|
||||
|
||||
spatial.set_obj_origin_to_bboxcenter(obj)
|
||||
spatial.set_obj_origin_to_bboxcenter_and_zero_elevation(obj)
|
||||
spatial.traslate_obj_to_z_location(obj, z)
|
||||
|
||||
spatial.link_obj_to_active_collection(obj)
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
import abc
|
||||
import inspect
|
||||
from typing import Optional
|
||||
|
||||
# fmt: off
|
||||
# pylint: skip-file
|
||||
@@ -72,20 +73,21 @@ class Aggregate:
|
||||
|
||||
@interface
|
||||
class Blender:
|
||||
def set_active_object(cls, obj): pass
|
||||
def get_name(cls, ifc_class, name): pass
|
||||
def get_selected_objects(cls): pass
|
||||
def create_ifc_object(cls, ifc_class: str, name: str = None, data=None): pass
|
||||
def get_obj_ifc_definition_id(cls, obj=None, obj_type=None, context=None): pass
|
||||
def is_ifc_object(cls, obj): pass
|
||||
def is_ifc_class_active(cls, ifc_class): pass
|
||||
def get_viewport_context(cls): pass
|
||||
def update_viewport(cls): pass
|
||||
def get_default_selection_keypmap(cls): pass
|
||||
def get_object_bounding_box(cls, obj): pass
|
||||
def activate_camera(cls, obj): pass
|
||||
def apply_bmesh(cls, mesh, bm, obj=None): pass
|
||||
def get_bmesh_for_mesh(cls, mesh, clean=False): pass
|
||||
def bmesh_join(cls, bm_a, bm_b, callback=None): pass
|
||||
def create_ifc_object(cls, ifc_class: str, name: Optional[str] = None, data=None): pass
|
||||
def get_bmesh_for_mesh(cls, mesh, clean=False): pass
|
||||
def get_default_selection_keypmap(cls): pass
|
||||
def get_name(cls, ifc_class, name): pass
|
||||
def get_obj_ifc_definition_id(cls, obj=None, obj_type=None, context=None): pass
|
||||
def get_object_bounding_box(cls, obj): pass
|
||||
def get_selected_objects(cls): pass
|
||||
def get_viewport_context(cls): pass
|
||||
def is_ifc_class_active(cls, ifc_class): pass
|
||||
def is_ifc_object(cls, obj): pass
|
||||
def set_active_object(cls, obj): pass
|
||||
def update_viewport(cls): pass
|
||||
|
||||
|
||||
@interface
|
||||
@@ -329,6 +331,7 @@ class Drawing:
|
||||
def get_name(cls, element): pass
|
||||
def get_path_filename(cls, uri): pass
|
||||
def get_reference_description(cls, reference): pass
|
||||
def generate_reference_attributes(cls, reference, **attributes): pass
|
||||
def get_reference_document(cls, reference): pass
|
||||
def get_reference_location(cls, reference): pass
|
||||
def get_references_with_location(cls, location): pass
|
||||
@@ -484,12 +487,12 @@ class Material:
|
||||
def disable_editing_materials(cls): pass
|
||||
def enable_editing_material(cls, material): pass
|
||||
def enable_editing_materials(cls): pass
|
||||
def get_active_material_type(cls): pass
|
||||
def get_active_material(cls): pass
|
||||
def get_active_material_type(cls): pass
|
||||
def get_active_object_material(cls, obj): pass
|
||||
def get_elements_by_material(cls, material): pass
|
||||
def get_material_attributes(cls): pass
|
||||
def get_material(cls, element, should_inherit): pass
|
||||
def get_material_attributes(cls): pass
|
||||
def get_name(cls, obj): pass
|
||||
def get_type(cls, element): pass
|
||||
def has_material_profile(cls, element): pass
|
||||
@@ -500,6 +503,7 @@ class Material:
|
||||
def is_material_used_in_sets(cls, material): pass
|
||||
def load_material_attributes(cls, material): pass
|
||||
def replace_material_with_material_profile(cls, element): pass
|
||||
def sync_blender_material_name(cls, material): pass
|
||||
|
||||
|
||||
@interface
|
||||
@@ -614,9 +618,18 @@ class Profile:
|
||||
|
||||
@interface
|
||||
class Pset:
|
||||
def clear_blender_pset_properties(cls, props): pass
|
||||
def enable_proposed_pset(cls, props, pset_name, pset_type): pass
|
||||
def get_element_pset(cls, element, pset_name): pass
|
||||
def get_prop_template_primitive_type(cls, prop_template): pass
|
||||
def get_pset_name(cls, obj, obj_type): pass
|
||||
def get_pset_template(cls, name): pass
|
||||
def import_enumerated_value_from_template(cls, prop_template, data, props): pass
|
||||
def import_pset_from_existing(cls, pset, props): pass
|
||||
def import_pset_from_template(cls, pset_template, pset, props): pass
|
||||
def import_single_value_from_template(cls, pset_template, prop_template, data, props): pass
|
||||
def is_pset_applicable(cls,element, pset_name): pass
|
||||
def set_active_pset(cls, props, pset): pass
|
||||
|
||||
|
||||
@interface
|
||||
@@ -871,7 +884,8 @@ class Spatial:
|
||||
def get_transformed_mesh_from_local_to_global(cls, mesh): pass
|
||||
def edit_active_space_obj_from_mesh(cls, mesh): pass
|
||||
def set_obj_origin_to_bboxcenter(cls, obj): pass
|
||||
def set_obj_origin_to_cursor_position(cls, obj): pass
|
||||
def set_obj_origin_to_bboxcenter_and_zero_elevation(cls, obj): pass
|
||||
def set_obj_origin_to_cursor_position_and_zero_elevation(cls, obj): pass
|
||||
def get_selected_objects(cls): pass
|
||||
def get_active_obj(cls): pass
|
||||
def get_active_obj_z(cls): pass
|
||||
|
||||
@@ -49,6 +49,23 @@ class Blender(blenderbim.core.tool.Blender):
|
||||
OBJECT_TYPES_THAT_SUPPORT_EDIT_GPENCIL_MODE = ("GPENCIL",)
|
||||
TYPE_MANAGER_ICON = "LIGHTPROBE_VOLUME" if bpy.app.version >= (4, 1, 0) else "LIGHTPROBE_GRID"
|
||||
|
||||
@classmethod
|
||||
def activate_camera(cls, obj: bpy.types.Object) -> None:
|
||||
area = tool.Blender.get_view3d_area()
|
||||
is_local_view = area.spaces[0].local_view is not None
|
||||
if is_local_view:
|
||||
# Turn off local view before activating drawing, and then turn it on again.
|
||||
for a in bpy.context.screen.areas:
|
||||
if a.type == "VIEW_3D":
|
||||
override = bpy.context.copy()
|
||||
override["area"] = a
|
||||
bpy.ops.view3d.localview(override)
|
||||
bpy.context.scene.camera = obj
|
||||
bpy.ops.view3d.localview(override)
|
||||
else:
|
||||
bpy.context.scene.camera = obj
|
||||
area.spaces[0].region_3d.view_perspective = "CAMERA"
|
||||
|
||||
@classmethod
|
||||
def get_area_props(cls, context: bpy.types.Context) -> Any:
|
||||
try:
|
||||
@@ -451,15 +468,20 @@ class Blender(blenderbim.core.tool.Blender):
|
||||
active_object.select_set(True)
|
||||
|
||||
@classmethod
|
||||
def enum_property_has_valid_index(cls, props, prop_name: str, enum_items: tuple) -> bool:
|
||||
def enum_property_has_valid_index(cls, props: bpy.types.PropertyGroup, prop_name: str, enum_items: tuple) -> bool:
|
||||
"""method created for readibility and to avoid console warnings like
|
||||
`pyrna_enum_to_py: current value '17' matches no enum in 'BIMModelProperties', '', 'relating_type_id'`
|
||||
"""
|
||||
items_amount = len(enum_items)
|
||||
# If enum has no items it seems to always produce a warning.
|
||||
# E.g. if you try to get it's value directly: `BIMModelProperties.relating_type_id`.
|
||||
if items_amount == 0:
|
||||
return False
|
||||
current_value_index = props.get(prop_name, None)
|
||||
# assuming the default value is fine
|
||||
if current_value_index is None:
|
||||
return True
|
||||
return current_value_index < len(enum_items)
|
||||
return current_value_index < items_amount
|
||||
|
||||
@classmethod
|
||||
def append_data_block(cls, filepath: str, data_block_type: str, name: str, link=False, relative=False) -> dict:
|
||||
@@ -845,9 +867,7 @@ class Blender(blenderbim.core.tool.Blender):
|
||||
bpy.utils.register_tool(ws_model.PipeTool, after={"bim.duct_tool"}, separator=False, group=False)
|
||||
bpy.utils.register_tool(ws_model.BimTool, after={"bim.pipe_tool"}, separator=False, group=False)
|
||||
bpy.utils.register_tool(ws_drawing.AnnotationTool, after={"bim.bim_tool"}, separator=True, group=False)
|
||||
bpy.utils.register_tool(
|
||||
ws_spatial.SpatialTool, after={"bim.annotation_tool"}, separator=False, group=False
|
||||
)
|
||||
bpy.utils.register_tool(ws_spatial.SpatialTool, after={"bim.annotation_tool"}, separator=False, group=False)
|
||||
bpy.utils.register_tool(
|
||||
ws_structural.StructuralTool, after={"bim.spatial_tool"}, separator=False, group=False
|
||||
)
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
import os
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import ifcopenshell.guid
|
||||
import ifcopenshell.util.brick
|
||||
import blenderbim.core.tool
|
||||
import blenderbim.tool as tool
|
||||
@@ -604,4 +605,4 @@ class BrickStore:
|
||||
def set_last_saved(cls):
|
||||
save = os.path.getmtime(BrickStore.path)
|
||||
save = datetime.datetime.fromtimestamp(save)
|
||||
BrickStore.last_saved = f"{save.year}-{save.month}-{save.day} {save.hour}:{save.minute}"
|
||||
BrickStore.last_saved = f"{save.year}-{save.month}-{save.day} {save.hour}:{save.minute}"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user