mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-21 04:32:23 +00:00
Compare commits
91 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 |
@@ -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,143 @@ 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):
|
||||
layout = self.layout
|
||||
layout.label(text="BlenderBIM could not load.", icon="ERROR")
|
||||
layout.operator("wm.console_toggle", text="View the console for full logs.", icon="CONSOLE")
|
||||
box = layout.box()
|
||||
info = get_debug_info()
|
||||
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"
|
||||
|
||||
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.);
|
||||
|
||||
@@ -19,12 +19,15 @@
|
||||
import os
|
||||
import bpy
|
||||
import uuid
|
||||
import shutil
|
||||
import hashlib
|
||||
import zipfile
|
||||
import tempfile
|
||||
import traceback
|
||||
import ifcopenshell
|
||||
import ifcopenshell.geom
|
||||
import ifcopenshell.ifcopenshell_wrapper
|
||||
import blenderbim
|
||||
import blenderbim.bim.handler
|
||||
import blenderbim.tool as tool
|
||||
from pathlib import Path
|
||||
@@ -37,19 +40,19 @@ IFC_CONNECTED_TYPE = Union[bpy.types.Material, bpy.types.Object]
|
||||
|
||||
class IfcStore:
|
||||
path: str = ""
|
||||
file: ifcopenshell.file = None
|
||||
schema: ifcopenshell.ifcopenshell_wrapper.schema_definition = None
|
||||
cache: ifcopenshell.ifcopenshell_wrapper.HdfSerializer = None
|
||||
cache_path: str = None
|
||||
file: Optional[ifcopenshell.file] = None
|
||||
schema: Optional[ifcopenshell.ifcopenshell_wrapper.schema_definition] = None
|
||||
cache: Optional[ifcopenshell.ifcopenshell_wrapper.HdfSerializer] = None
|
||||
cache_path: Optional[str] = None
|
||||
id_map: dict[int, IFC_CONNECTED_TYPE] = {}
|
||||
guid_map: dict[str, IFC_CONNECTED_TYPE] = {}
|
||||
edited_objs: Set[bpy.types.Object] = set()
|
||||
pset_template_path: str = ""
|
||||
pset_template_file: ifcopenshell.file = None
|
||||
pset_template_file: Optional[ifcopenshell.file] = None
|
||||
classification_path: str = ""
|
||||
classification_file: ifcopenshell.file = None
|
||||
classification_file: Optional[ifcopenshell.file] = None
|
||||
library_path: str = ""
|
||||
library_file: ifcopenshell.file = None
|
||||
library_file: Optional[ifcopenshell.file] = None
|
||||
current_transaction = ""
|
||||
last_transaction = ""
|
||||
history = []
|
||||
@@ -119,7 +122,13 @@ class IfcStore:
|
||||
ifc_hash = hashlib.md5(ifc_key.encode("utf-8")).hexdigest()
|
||||
new_cache_path = os.path.join(bpy.context.scene.BIMProperties.data_dir, "cache", f"{ifc_hash}.h5")
|
||||
IfcStore.cache = None
|
||||
os.replace(IfcStore.cache_path, new_cache_path)
|
||||
try:
|
||||
shutil.move(IfcStore.cache_path, new_cache_path)
|
||||
except PermissionError:
|
||||
try:
|
||||
shutil.copy2(IfcStore.cache_path, new_cache_path)
|
||||
except PermissionError:
|
||||
pass # Well we tried. No cache for you!
|
||||
IfcStore.get_cache()
|
||||
|
||||
@staticmethod
|
||||
@@ -329,6 +338,7 @@ class IfcStore:
|
||||
|
||||
@staticmethod
|
||||
def execute_ifc_operator(operator: bpy.types.Operator, context: bpy.types.Context, is_invoke=False):
|
||||
blenderbim.last_actions.append({"type": "operator", "name": operator.bl_idname})
|
||||
bpy.context.scene.BIMProperties.is_dirty = True
|
||||
is_top_level_operator = not bool(IfcStore.current_transaction)
|
||||
|
||||
@@ -343,10 +353,14 @@ class IfcStore:
|
||||
else:
|
||||
operator.transaction_key = IfcStore.current_transaction
|
||||
|
||||
if is_invoke:
|
||||
result = getattr(operator, "_invoke")(context, None)
|
||||
else:
|
||||
result = getattr(operator, "_execute")(context)
|
||||
try:
|
||||
if is_invoke:
|
||||
result = getattr(operator, "_invoke")(context, None)
|
||||
else:
|
||||
result = getattr(operator, "_execute")(context)
|
||||
except:
|
||||
blenderbim.last_error = traceback.format_exc()
|
||||
raise
|
||||
|
||||
if is_top_level_operator:
|
||||
if tool.Ifc.get():
|
||||
|
||||
@@ -26,6 +26,7 @@ import bmesh
|
||||
import logging
|
||||
import mathutils
|
||||
import numpy as np
|
||||
import numpy.typing as npt
|
||||
import multiprocessing
|
||||
import ifcopenshell
|
||||
import ifcopenshell.geom
|
||||
@@ -303,22 +304,22 @@ class IfcImporter:
|
||||
self.update_progress(100)
|
||||
bpy.context.window_manager.progress_end()
|
||||
|
||||
def is_element_far_away(self, element):
|
||||
def is_element_far_away(self, element: ifcopenshell.entity_instance) -> bool:
|
||||
try:
|
||||
placement = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
|
||||
point = placement[:, 3][0:3]
|
||||
return self.is_point_far_away(point, is_meters=False)
|
||||
except:
|
||||
pass
|
||||
return False
|
||||
|
||||
def is_point_far_away(self, point, is_meters=True):
|
||||
def is_point_far_away(
|
||||
self, point: Union[ifcopenshell.entity_instance, npt.NDArray[np.float64]], is_meters: bool = True
|
||||
) -> bool:
|
||||
# Locations greater than 1km are not considered "small sites" according to the georeferencing guide
|
||||
# Users can configure this if they have to handle larger sites but beware of surveying precision
|
||||
limit = self.ifc_import_settings.distance_limit
|
||||
limit = limit if is_meters else (limit / self.unit_scale)
|
||||
coords = point
|
||||
if hasattr(point, "Coordinates"):
|
||||
coords = point.Coordinates
|
||||
coords = getattr(point, "Coordinates", point)
|
||||
return abs(coords[0]) > limit or abs(coords[1]) > limit or abs(coords[2]) > limit
|
||||
|
||||
def process_context_filter(self):
|
||||
@@ -608,7 +609,9 @@ class IfcImporter:
|
||||
|
||||
threshold = 10000 # Just from experience.
|
||||
|
||||
faces = [len(e.CfsFaces) for e in self.file.by_type("IfcClosedShell")]
|
||||
# The check for CfsFaces/Faces/CoordIndex accommodates invalid data from Cadwork
|
||||
# 0 IfcClosedShell.CfsFaces
|
||||
faces = [len(faces) for e in self.file.by_type("IfcClosedShell") if (faces := e[0])]
|
||||
if faces and max(faces) > threshold:
|
||||
self.ifc_import_settings.should_use_native_meshes = True
|
||||
return
|
||||
@@ -616,12 +619,14 @@ class IfcImporter:
|
||||
if self.file.schema == "IFC2X3":
|
||||
return
|
||||
|
||||
faces = [len(e.Faces) for e in self.file.by_type("IfcPolygonalFaceSet")]
|
||||
# 2 IfcPolygonalFaceSet.Faces
|
||||
faces = [len(faces) for e in self.file.by_type("IfcPolygonalFaceSet") if (faces := e[2])]
|
||||
if faces and max(faces) > threshold:
|
||||
self.ifc_import_settings.should_use_native_meshes = True
|
||||
return
|
||||
|
||||
faces = [len(e.CoordIndex) for e in self.file.by_type("IfcTriangulatedFaceSet")]
|
||||
# 3 IfcTriangulatedFaceSet.CoordIndex
|
||||
faces = [len(index) for e in self.file.by_type("IfcTriangulatedFaceSet") if (index := e[3])]
|
||||
if faces and max(faces) > threshold:
|
||||
self.ifc_import_settings.should_use_native_meshes = True
|
||||
|
||||
@@ -679,7 +684,7 @@ class IfcImporter:
|
||||
props.blender_orthogonal_height = str(offset_point[2])
|
||||
props.has_blender_offset = True
|
||||
|
||||
def get_offset_point(self):
|
||||
def get_offset_point(self) -> Union[npt.NDArray[np.float64], None]:
|
||||
elements_checked = 0
|
||||
# If more than these elements aren't far away, the file probably isn't absolutely positioned
|
||||
element_checking_threshold = 10
|
||||
@@ -702,17 +707,19 @@ class IfcImporter:
|
||||
mat = np.array(
|
||||
([m[0], m[3], m[6], m[9]], [m[1], m[4], m[7], m[10]], [m[2], m[5], m[8], m[11]], [0, 0, 0, 1])
|
||||
)
|
||||
point = np.array(
|
||||
point = mat @ np.array(
|
||||
(
|
||||
shape.geometry.verts[0] / self.unit_scale,
|
||||
shape.geometry.verts[1] / self.unit_scale,
|
||||
shape.geometry.verts[2] / self.unit_scale,
|
||||
shape.geometry.verts[0],
|
||||
shape.geometry.verts[1],
|
||||
shape.geometry.verts[2],
|
||||
0.0,
|
||||
)
|
||||
)
|
||||
return mat @ point
|
||||
point = point / self.unit_scale
|
||||
if self.is_point_far_away(point, is_meters=False):
|
||||
return point
|
||||
|
||||
def does_element_likely_have_geometry_far_away(self, element):
|
||||
def does_element_likely_have_geometry_far_away(self, element: ifcopenshell.entity_instance) -> bool:
|
||||
for representation in element.Representation.Representations:
|
||||
items = []
|
||||
for item in representation.Items:
|
||||
@@ -729,13 +736,14 @@ class IfcImporter:
|
||||
if subelement.is_a("IfcCartesianPoint"):
|
||||
if len(subelement.Coordinates) == 3 and self.is_point_far_away(subelement, is_meters=False):
|
||||
return True
|
||||
return False
|
||||
|
||||
def apply_blender_offset_to_matrix_world(self, obj: bpy.types.Object, matrix: np.ndarray) -> mathutils.Matrix:
|
||||
props = bpy.context.scene.BIMGeoreferenceProperties
|
||||
if props.has_blender_offset:
|
||||
if obj.data and obj.data.get("has_cartesian_point_offset", None):
|
||||
obj.BIMObjectProperties.blender_offset_type = "CARTESIAN_POINT"
|
||||
elif self.is_point_far_away((matrix[0, 3], matrix[1, 3], matrix[2, 3])):
|
||||
elif self.is_point_far_away((matrix[:3, 3])):
|
||||
obj.BIMObjectProperties.blender_offset_type = "OBJECT_PLACEMENT"
|
||||
matrix = ifcopenshell.util.geolocation.global2local(
|
||||
matrix,
|
||||
@@ -748,7 +756,9 @@ class IfcImporter:
|
||||
|
||||
return mathutils.Matrix(matrix.tolist())
|
||||
|
||||
def find_decomposed_ifc_class(self, element, ifc_class):
|
||||
def find_decomposed_ifc_class(
|
||||
self, element: ifcopenshell.entity_instance, ifc_class: str
|
||||
) -> Union[ifcopenshell.entity_instance, None]:
|
||||
if element.is_a(ifc_class):
|
||||
return element
|
||||
rel_aggregates = element.IsDecomposedBy
|
||||
@@ -1500,10 +1510,9 @@ class IfcImporter:
|
||||
project_collection = bpy.context.view_layer.layer_collection.children[self.project["blender"].name]
|
||||
types_collection = project_collection.children[self.type_collection.name]
|
||||
types_collection.hide_viewport = False
|
||||
for obj in types_collection.collection.objects: #turn off all objects inside Types collection.
|
||||
for obj in types_collection.collection.objects: # turn off all objects inside Types collection.
|
||||
obj.hide_set(True)
|
||||
|
||||
|
||||
def clean_mesh(self):
|
||||
obj = None
|
||||
last_obj = None
|
||||
@@ -1912,11 +1921,7 @@ class IfcImporter:
|
||||
and geometry.verts
|
||||
and self.is_point_far_away((geometry.verts[0], geometry.verts[1], geometry.verts[2]))
|
||||
):
|
||||
m = shape.transformation.matrix.data
|
||||
mat = np.array(
|
||||
([m[0], m[3], m[6], m[9]], [m[1], m[4], m[7], m[10]], [m[2], m[5], m[8], m[11]], [0, 0, 0, 1])
|
||||
)
|
||||
offset_point = np.linalg.inv(mat) @ np.array(
|
||||
offset_point = np.array(
|
||||
(
|
||||
float(props.blender_eastings),
|
||||
float(props.blender_northings),
|
||||
@@ -1924,8 +1929,15 @@ class IfcImporter:
|
||||
0.0,
|
||||
)
|
||||
)
|
||||
if geometry != shape:
|
||||
m = shape.transformation.matrix.data
|
||||
mat = np.array(
|
||||
([m[0], m[3], m[6], m[9]], [m[1], m[4], m[7], m[10]], [m[2], m[5], m[8], m[11]], [0, 0, 0, 1])
|
||||
)
|
||||
offset_point = np.linalg.inv(mat) @ offset_point
|
||||
verts = [None] * len(geometry.verts)
|
||||
for i in range(0, len(geometry.verts), 3):
|
||||
# Note: this enh2xyz call is crazy slow.
|
||||
verts[i], verts[i + 1], verts[i + 2] = ifcopenshell.util.geolocation.enh2xyz(
|
||||
geometry.verts[i],
|
||||
geometry.verts[i + 1],
|
||||
|
||||
@@ -100,7 +100,7 @@ class BIM_OT_aggregate_unassign_object(bpy.types.Operator, Operator):
|
||||
pset = ifcopenshell.util.element.get_pset(element, 'BBIM_Linked_Aggregate')
|
||||
if pset:
|
||||
pset = tool.Ifc.get().by_id(pset["id"])
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=element, pset=pset)
|
||||
|
||||
|
||||
class BIM_OT_enable_editing_aggregate(bpy.types.Operator, Operator):
|
||||
|
||||
@@ -73,7 +73,7 @@ class BIM_PT_aggregate(Panel):
|
||||
row.operator("bim.add_aggregate", icon="ADD", text="")
|
||||
op = row.operator("bim.aggregate_unassign_object", icon="X", text="")
|
||||
else:
|
||||
row.label(text="No Aggregate", icon="TRIA_UP")
|
||||
row.label(text="No Whole relation defined", icon="TRIA_UP")
|
||||
row.operator("bim.enable_editing_aggregate", icon="GREASEPENCIL", text="")
|
||||
row.operator("bim.add_aggregate", icon="ADD", text="")
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"}
|
||||
|
||||
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -1515,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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -136,19 +136,23 @@ class AddRepresentation(bpy.types.Operator, Operator):
|
||||
"for Profile - 2D bounding box by local XZ axes.\n"
|
||||
"For other contexts - bounding box is 3d.",
|
||||
),
|
||||
("PROJECT", "Full Representation", ""),
|
||||
("OBJECT", "From Object", "Copies geometry from another object"),
|
||||
("PROJECT", "Full Representation", "Reuses the current representation"),
|
||||
],
|
||||
name="Representation Conversion Method",
|
||||
)
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
props = obj.BIMGeometryProperties
|
||||
ifc_context = int(props.contexts or "0") or None
|
||||
props = context.scene.BIMGeometryProperties
|
||||
oprops = obj.BIMGeometryProperties
|
||||
ifc_context = int(oprops.contexts or "0") or None
|
||||
if not ifc_context:
|
||||
return
|
||||
ifc_context = tool.Ifc.get().by_id(ifc_context)
|
||||
|
||||
original_data = obj.data
|
||||
|
||||
if self.representation_conversion_method == "OUTLINE":
|
||||
if ifc_context.ContextType == "Plan":
|
||||
data = tool.Geometry.generate_outline_mesh(obj, axis="+Z")
|
||||
@@ -165,17 +169,31 @@ class AddRepresentation(bpy.types.Operator, Operator):
|
||||
else:
|
||||
data = tool.Geometry.generate_3d_box_mesh(obj)
|
||||
tool.Geometry.change_object_data(obj, data, is_global=True)
|
||||
elif (
|
||||
self.representation_conversion_method == "OBJECT"
|
||||
and props.representation_from_object
|
||||
and props.representation_from_object.data
|
||||
):
|
||||
data = tool.Geometry.duplicate_object_data(props.representation_from_object)
|
||||
tool.Geometry.change_object_data(obj, data, is_global=True)
|
||||
|
||||
core.add_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
tool.Style,
|
||||
tool.Surveyor,
|
||||
obj=obj,
|
||||
context=ifc_context,
|
||||
ifc_representation_class=None,
|
||||
profile_set_usage=None,
|
||||
)
|
||||
try:
|
||||
core.add_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
tool.Style,
|
||||
tool.Surveyor,
|
||||
obj=obj,
|
||||
context=ifc_context,
|
||||
ifc_representation_class=None,
|
||||
profile_set_usage=None,
|
||||
)
|
||||
except core.IncompatibleRepresentationError:
|
||||
if obj.data != original_data:
|
||||
tool.Geometry.change_object_data(obj, original_data, is_global=True)
|
||||
bpy.data.meshes.remove(data)
|
||||
self.report({"ERROR"}, "No compatible representation for the context could be created.")
|
||||
return {"CANCELLED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
@@ -183,6 +201,9 @@ class AddRepresentation(bpy.types.Operator, Operator):
|
||||
def draw(self, context):
|
||||
row = self.layout.row()
|
||||
row.prop(self, "representation_conversion_method", text="")
|
||||
if self.representation_conversion_method == "OBJECT":
|
||||
row = self.layout.row()
|
||||
row.prop(context.scene.BIMGeometryProperties, "representation_from_object", text="")
|
||||
|
||||
|
||||
class SelectConnection(bpy.types.Operator, Operator):
|
||||
@@ -379,7 +400,11 @@ class UpdateRepresentation(bpy.types.Operator, Operator):
|
||||
representation_data["profile_set_usage"] = tool.Geometry.get_profile_set_usage(product)
|
||||
representation_data["text_literal"] = tool.Geometry.get_text_literal(old_representation)
|
||||
|
||||
# TODO: replace with core.add_representation?
|
||||
new_representation = ifcopenshell.api.run("geometry.add_representation", self.file, **representation_data)
|
||||
if new_representation is None:
|
||||
self.report({"ERROR"}, "Error creating representation for Blender object.")
|
||||
return {"CANCELLED"}
|
||||
|
||||
if tool.Geometry.is_body_representation(new_representation):
|
||||
[
|
||||
@@ -917,7 +942,7 @@ class OverrideDuplicateMove(bpy.types.Operator):
|
||||
pset = ifcopenshell.util.element.get_pset(new[0], "BBIM_Linked_Aggregate")
|
||||
if pset:
|
||||
pset = tool.Ifc.get().by_id(pset["id"])
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=new[0],pset=pset)
|
||||
|
||||
if new[0].is_a("IfcElementAssembly"):
|
||||
linked_aggregate_group = [
|
||||
@@ -1029,7 +1054,7 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator):
|
||||
if self.group_name in product_groups_name:
|
||||
return
|
||||
|
||||
linked_aggregate_group = ifcopenshell.api.run("group.add_group", tool.Ifc.get(), Name=self.group_name)
|
||||
linked_aggregate_group = ifcopenshell.api.run("group.add_group", tool.Ifc.get(), name=self.group_name)
|
||||
ifcopenshell.api.run("group.assign_group", tool.Ifc.get(), products=[element], group=linked_aggregate_group)
|
||||
|
||||
def custom_incremental_naming_for_element_assembly(old_to_new):
|
||||
|
||||
@@ -138,3 +138,4 @@ class BIMGeometryProperties(PropertyGroup):
|
||||
name="IFC Interaction Mode",
|
||||
update=update_mode,
|
||||
)
|
||||
representation_from_object: PointerProperty(type=bpy.types.Object)
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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"}
|
||||
|
||||
@@ -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"}
|
||||
|
||||
|
||||
|
||||
@@ -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"}
|
||||
|
||||
@@ -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"}
|
||||
|
||||
@@ -21,6 +21,7 @@ import bpy
|
||||
import time
|
||||
import logging
|
||||
import tempfile
|
||||
import traceback
|
||||
import subprocess
|
||||
import numpy as np
|
||||
import ifcopenshell
|
||||
@@ -674,19 +675,23 @@ class LoadProject(bpy.types.Operator, IFCFileSelector):
|
||||
return self.finish_loading_project(context)
|
||||
|
||||
def finish_loading_project(self, context):
|
||||
if not self.is_existing_ifc_file():
|
||||
return {"FINISHED"}
|
||||
try:
|
||||
if not self.is_existing_ifc_file():
|
||||
return {"FINISHED"}
|
||||
|
||||
if tool.Blender.is_default_scene():
|
||||
for obj in bpy.data.objects:
|
||||
bpy.data.objects.remove(obj)
|
||||
if tool.Blender.is_default_scene():
|
||||
for obj in bpy.data.objects:
|
||||
bpy.data.objects.remove(obj)
|
||||
|
||||
context.scene.BIMProperties.ifc_file = self.get_filepath()
|
||||
context.scene.BIMProjectProperties.is_loading = True
|
||||
context.scene.BIMProjectProperties.total_elements = len(tool.Ifc.get().by_type("IfcElement"))
|
||||
tool.Blender.register_toolbar()
|
||||
if not self.is_advanced:
|
||||
bpy.ops.bim.load_project_elements()
|
||||
context.scene.BIMProperties.ifc_file = self.get_filepath()
|
||||
context.scene.BIMProjectProperties.is_loading = True
|
||||
context.scene.BIMProjectProperties.total_elements = len(tool.Ifc.get().by_type("IfcElement"))
|
||||
tool.Blender.register_toolbar()
|
||||
if not self.is_advanced:
|
||||
bpy.ops.bim.load_project_elements()
|
||||
except:
|
||||
blenderbim.last_error = traceback.format_exc()
|
||||
raise
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -36,10 +36,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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -29,8 +29,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 +290,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 +318,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 +403,18 @@ 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")
|
||||
box.operator("wm.console_toggle", 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
|
||||
|
||||
|
||||
@@ -441,9 +441,19 @@ def select_assigned_product(drawing, context):
|
||||
drawing.select_assigned_product(context)
|
||||
|
||||
|
||||
def activate_drawing_view(ifc, drawing_tool, drawing):
|
||||
def activate_drawing_view(ifc, blender, drawing_tool, drawing):
|
||||
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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
@@ -845,9 +862,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}"
|
||||
|
||||
@@ -1743,21 +1743,7 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
bpy.ops.bim.activate_model()
|
||||
|
||||
@classmethod
|
||||
def activate_drawing(cls, camera: 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 = camera
|
||||
bpy.ops.view3d.localview(override)
|
||||
else:
|
||||
bpy.context.scene.camera = camera
|
||||
area.spaces[0].region_3d.view_perspective = "CAMERA"
|
||||
def isolate_camera_collection(cls, camera: bpy.types.Object) -> None:
|
||||
views_collection = bpy.data.collections.get("Views")
|
||||
for collection in views_collection.children:
|
||||
# We assume the project collection is at the top level
|
||||
@@ -1775,8 +1761,9 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
camera.BIMObjectProperties.collection.name
|
||||
].hide_viewport = False
|
||||
camera.BIMObjectProperties.collection.hide_render = False
|
||||
tool.Spatial.set_active_object(camera)
|
||||
|
||||
@classmethod
|
||||
def activate_drawing(cls, camera: bpy.types.Object) -> None:
|
||||
# Sync viewport objects visibility with selectors from EPset_Drawing/Include and /Exclude
|
||||
drawing = tool.Ifc.get_entity(camera)
|
||||
|
||||
|
||||
@@ -24,7 +24,10 @@ import logging
|
||||
import numpy as np
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.geom
|
||||
import ifcopenshell.guid
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.representation
|
||||
import ifcopenshell.util.system
|
||||
import blenderbim.core.tool
|
||||
import blenderbim.core.drawing
|
||||
@@ -318,7 +321,7 @@ class Geometry(blenderbim.core.tool.Geometry):
|
||||
return new_mesh
|
||||
|
||||
@classmethod
|
||||
def get_active_representation(cls, obj):
|
||||
def get_active_representation(cls, obj: bpy.types.Object) -> Union[ifcopenshell.entity_instance, None]:
|
||||
"""< IfcShapeRepresentation or None"""
|
||||
if obj.data and hasattr(obj.data, "BIMMeshProperties") and obj.data.BIMMeshProperties.ifc_definition_id:
|
||||
return tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
|
||||
@@ -458,7 +461,9 @@ class Geometry(blenderbim.core.tool.Geometry):
|
||||
return f"{representation.ContextOfItems.id()}/{representation.id()}"
|
||||
|
||||
@classmethod
|
||||
def get_styles(cls, obj, only_assigned_to_faces=False):
|
||||
def get_styles(
|
||||
cls, obj: bpy.types.Object, only_assigned_to_faces: bool = False
|
||||
) -> list[Union[ifcopenshell.entity_instance, None]]:
|
||||
styles = [tool.Style.get_style(s.material) for s in obj.material_slots if s.material]
|
||||
if not only_assigned_to_faces:
|
||||
return styles
|
||||
@@ -466,8 +471,15 @@ class Geometry(blenderbim.core.tool.Geometry):
|
||||
usage_count = [0] * len(obj.material_slots)
|
||||
if not usage_count: # if there are no materials, polygons will still use index 0
|
||||
return []
|
||||
|
||||
for poly in obj.data.polygons:
|
||||
usage_count[poly.material_index] += 1
|
||||
|
||||
# remove usages for empty material slots
|
||||
for i, slot in reversed(list(enumerate(obj.material_slots))):
|
||||
if not slot.material:
|
||||
del usage_count[i]
|
||||
|
||||
styles = [style for style, usage in zip(styles, usage_count, strict=True) if usage > 0]
|
||||
return styles
|
||||
|
||||
@@ -559,11 +571,11 @@ class Geometry(blenderbim.core.tool.Geometry):
|
||||
new.value = element[i]
|
||||
|
||||
@classmethod
|
||||
def is_body_representation(cls, representation):
|
||||
def is_body_representation(cls, representation: ifcopenshell.entity_instance) -> bool:
|
||||
return representation.ContextOfItems.ContextIdentifier == "Body"
|
||||
|
||||
@classmethod
|
||||
def is_box_representation(cls, representation):
|
||||
def is_box_representation(cls, representation: ifcopenshell.entity_instance) -> bool:
|
||||
return representation.ContextOfItems.ContextIdentifier == "Box"
|
||||
|
||||
@classmethod
|
||||
@@ -571,11 +583,11 @@ class Geometry(blenderbim.core.tool.Geometry):
|
||||
return not all([tool.Cad.is_x(o, 1.0) for o in obj.scale]) or obj in IfcStore.edited_objs
|
||||
|
||||
@classmethod
|
||||
def is_mapped_representation(cls, representation):
|
||||
def is_mapped_representation(cls, representation: ifcopenshell.entity_instance) -> bool:
|
||||
return representation.RepresentationType == "MappedRepresentation"
|
||||
|
||||
@classmethod
|
||||
def is_meshlike(cls, representation):
|
||||
def is_meshlike(cls, representation: ifcopenshell.entity_instance) -> bool:
|
||||
if ifcopenshell.util.representation.resolve_representation(representation).RepresentationType in (
|
||||
"AdvancedBrep",
|
||||
"Annotation2D",
|
||||
@@ -655,7 +667,9 @@ class Geometry(blenderbim.core.tool.Geometry):
|
||||
bpy.data.objects.remove(obj)
|
||||
|
||||
@classmethod
|
||||
def resolve_mapped_representation(cls, representation):
|
||||
def resolve_mapped_representation(
|
||||
cls, representation: ifcopenshell.entity_instance
|
||||
) -> ifcopenshell.entity_instance:
|
||||
if representation.RepresentationType == "MappedRepresentation":
|
||||
return cls.resolve_mapped_representation(representation.Items[0].MappingSource.MappedRepresentation)
|
||||
return representation
|
||||
|
||||
@@ -97,7 +97,9 @@ class Misc(blenderbim.core.tool.Misc):
|
||||
IfcStore.edited_objs.add(obj)
|
||||
|
||||
@classmethod
|
||||
def split_objects_with_cutter(cls, objs, cutter):
|
||||
def split_objects_with_cutter(
|
||||
cls, objs: list[bpy.types.Object], cutter: bpy.types.Object
|
||||
) -> list[bpy.types.Object]:
|
||||
cutter_mesh = cutter.data
|
||||
|
||||
bm = bmesh.new()
|
||||
|
||||
@@ -551,7 +551,7 @@ class Model(blenderbim.core.tool.Model):
|
||||
data = tool.Ifc.get().createIfcText(json.dumps(data))
|
||||
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"Data": data})
|
||||
else:
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=element, pset=pset)
|
||||
|
||||
@classmethod
|
||||
def get_flow_segment_axis(cls, obj):
|
||||
|
||||
@@ -21,6 +21,7 @@ import bpy
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.unit
|
||||
import blenderbim.core.tool
|
||||
import blenderbim.core.root
|
||||
import blenderbim.bim.schema
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
@@ -29,6 +29,7 @@ import blenderbim.core.style
|
||||
import blenderbim.tool as tool
|
||||
from mathutils import Vector
|
||||
from blenderbim.bim.module.model.opening import FilledOpeningGenerator
|
||||
from typing import Union, Optional
|
||||
|
||||
|
||||
class Root(blenderbim.core.tool.Root):
|
||||
@@ -129,7 +130,7 @@ class Root(blenderbim.core.tool.Root):
|
||||
return ifcopenshell.util.representation.get_representation(element, context=context.ContextType)
|
||||
|
||||
@classmethod
|
||||
def get_element_type(cls, element):
|
||||
def get_element_type(cls, element: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]:
|
||||
return ifcopenshell.util.element.get_type(element)
|
||||
|
||||
@classmethod
|
||||
@@ -282,8 +283,12 @@ class Root(blenderbim.core.tool.Root):
|
||||
|
||||
@classmethod
|
||||
def run_geometry_add_representation(
|
||||
cls, obj=None, context=None, ifc_representation_class=None, profile_set_usage=None
|
||||
):
|
||||
cls,
|
||||
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:
|
||||
return blenderbim.core.geometry.add_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
|
||||
@@ -3,6 +3,7 @@ import json
|
||||
import lark
|
||||
import blenderbim.core.tool
|
||||
import blenderbim.tool as tool
|
||||
import ifcopenshell.guid
|
||||
import ifcopenshell.util.selector
|
||||
from ifcopenshell.util.selector import Selector
|
||||
from blenderbim.bim.prop import BIMFacet
|
||||
|
||||
@@ -1630,7 +1630,7 @@ class Sequence(blenderbim.core.tool.Sequence):
|
||||
group.Description = json.dumps(description)
|
||||
else:
|
||||
description = json.dumps({"type": "BBIM_AnimationColorScheme", "colourscheme": colour_scheme})
|
||||
group = tool.Ifc.run("group.add_group", Name=name, Description=description)
|
||||
group = tool.Ifc.run("group.add_group", name=name, description=description)
|
||||
return group[0]
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -111,16 +111,17 @@ class Spatial(blenderbim.core.tool.Spatial):
|
||||
for rel in parent.IsDecomposedBy or []:
|
||||
related_objects = []
|
||||
for element in rel.RelatedObjects:
|
||||
# skip objects without placements
|
||||
if not element.is_a("IfcProduct"):
|
||||
continue
|
||||
related_objects.append((element, ifcopenshell.util.placement.get_storey_elevation(element)))
|
||||
related_objects = sorted(related_objects, key=lambda e: e[1])
|
||||
for element in related_objects:
|
||||
element = element[0]
|
||||
for element, _ in related_objects:
|
||||
new = props.containers.add()
|
||||
new.name = element.Name or "Unnamed"
|
||||
new.long_name = element.LongName or ""
|
||||
new.has_decomposition = bool(element.IsDecomposedBy)
|
||||
new.ifc_definition_id = element.id()
|
||||
new.elevation = element[1]
|
||||
|
||||
@classmethod
|
||||
def run_root_copy_class(cls, obj=None):
|
||||
|
||||
@@ -13,14 +13,14 @@ Unstable installation
|
||||
|
||||
**Unstable installation** is almost the same as **Stable installation**, except
|
||||
that they are typically updated every day. Simply download a daily build from
|
||||
the `Github releases page
|
||||
<https://github.com/IfcOpenShell/IfcOpenShell/releases>`__, then follow the same
|
||||
instructions as the **Stable installation**.
|
||||
the `GitHub releases page
|
||||
<https://github.com/IfcOpenShell/IfcOpenShell/releases>`__, then follow the
|
||||
usual :doc:`installation instructions</users/installation>`.
|
||||
|
||||
You will need to choose which build to download.
|
||||
|
||||
- If you are on Blender >=4.1, choose py311
|
||||
- If you are on Blender >=3.1 and <=4.0, choose py10
|
||||
- If you are on Blender >=3.1 and <=4.0, choose py310
|
||||
- If you are on Blender >=2.93 and <3.1, choose py39
|
||||
- Choose ``linux``, ``macos`` (Apple Intel), ``macosm1`` (Apple Silicon), or
|
||||
``win`` depending on your operating system
|
||||
@@ -105,6 +105,7 @@ For Linux or Mac:
|
||||
$ rm -r $BLENDER_ADDON_PATH/libs/site/packages/ifccsv.py
|
||||
$ rm -r $BLENDER_ADDON_PATH/libs/site/packages/ifcdiff.py
|
||||
$ rm -r $BLENDER_ADDON_PATH/libs/site/packages/bsdd.py
|
||||
$ rm -r $BLENDER_ADDON_PATH/libs/site/packages/bcf
|
||||
$ rm -r $BLENDER_ADDON_PATH/libs/site/packages/ifc4d
|
||||
$ rm -r $BLENDER_ADDON_PATH/libs/site/packages/ifc5d
|
||||
$ rm -r $BLENDER_ADDON_PATH/libs/site/packages/ifccityjson
|
||||
@@ -117,6 +118,7 @@ For Linux or Mac:
|
||||
$ ln -s $PWD/src/ifccsv/ifccsv.py $BLENDER_ADDON_PATH/libs/site/packages/ifccsv.py
|
||||
$ ln -s $PWD/src/ifcdiff/ifcdiff.py $BLENDER_ADDON_PATH/libs/site/packages/ifcdiff.py
|
||||
$ ln -s $PWD/src/bsdd/bsdd.py $BLENDER_ADDON_PATH/libs/site/packages/bsdd.py
|
||||
$ ln -s $PWD/src/bcf/src/bcf $BLENDER_ADDON_PATH/libs/site/packages/bcf
|
||||
$ ln -s $PWD/src/ifc4d/ifc4d $BLENDER_ADDON_PATH/libs/site/packages/ifc4d
|
||||
$ ln -s $PWD/src/ifc5d/ifc5d $BLENDER_ADDON_PATH/libs/site/packages/ifc5d
|
||||
$ ln -s $PWD/src/ifccityjson/ifccityjson $BLENDER_ADDON_PATH/libs/site/packages/ifccityjson
|
||||
@@ -172,6 +174,7 @@ Before running it follow the instructions descibed after `rem` tags.
|
||||
del "%blenderbim%\libs\site\packages\ifccsv.py"
|
||||
del "%blenderbim%\libs\site\packages\ifcdiff.py"
|
||||
del "%blenderbim%\libs\site\packages\bsdd.py"
|
||||
rd /S /Q "%blenderbim%\libs\site\packages\bcf"
|
||||
rd /S /Q "%blenderbim%\libs\site\packages\ifc4d"
|
||||
rd /S /Q "%blenderbim%\libs\site\packages\ifc5d"
|
||||
rd /S /Q "%blenderbim%\libs\site\packages\ifccityjson"
|
||||
@@ -184,6 +187,7 @@ Before running it follow the instructions descibed after `rem` tags.
|
||||
mklink "%blenderbim%\libs\site\packages\ifccsv.py" "%cd%\src\ifccsv\ifccsv.py"
|
||||
mklink "%blenderbim%\libs\site\packages\ifcdiff.py" "%cd%\src\ifcdiff\ifcdiff.py"
|
||||
mklink "%blenderbim%\libs\site\packages\bsdd.py" "%cd%\src\bsdd\bsdd.py"
|
||||
mklink /D "%blenderbim%\libs\site\packages\bcf" "%cd%\src\bcf\src\bcf"
|
||||
mklink /D "%blenderbim%\libs\site\packages\ifc4d" "%cd%\src\ifc4d\ifc4d"
|
||||
mklink /D "%blenderbim%\libs\site\packages\ifc5d" "%cd%\src\ifc5d\ifc5d"
|
||||
mklink /D "%blenderbim%\libs\site\packages\ifccityjson" "%cd%\src\ifccityjson\ifccityjson"
|
||||
|
||||
@@ -16,9 +16,12 @@ You can press the edit button on the top right on any documentation page to
|
||||
quickly edit their content.
|
||||
|
||||
You can link to `external websites
|
||||
<https://docs.readthedocs.io/en/stable/guides/cross-referencing-with-sphinx.html>`_.
|
||||
You can also link to sections on the same page, like `Writing technical
|
||||
documentation`_. You can link to other pages, like :doc:`Hello
|
||||
<https://docs.readthedocs.io/en/stable/guides/cross-referencing-with-sphinx.html>`_
|
||||
(note the space between the url and the link text). You can also link to
|
||||
sections on the same page, like :ref:`devs/writing_docs:Writing technical
|
||||
documentation` or with :ref:`custom text<devs/writing_docs:writing technical
|
||||
documentation>`. Traditional references like `Writing technical documentation`_
|
||||
work too but are discouraged. You can link to other pages, like :doc:`Hello
|
||||
World<hello_world>` or sections within other pages, like
|
||||
:ref:`devs/installation:unstable installation`. We have ``autosectionlabel``
|
||||
enabled so it is not necessary to manually create labels.
|
||||
|
||||
@@ -32,6 +32,7 @@ Learn how to model a small building and create simple architectural plans, secti
|
||||
users/git_support
|
||||
users/other_addons
|
||||
users/general_usage
|
||||
users/troubleshooting
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 9.2 KiB |
@@ -58,6 +58,32 @@ You can enable add-ons permanently by using ``Save User Settings`` from the Addo
|
||||
|
||||
.. _where is the add-on installed:
|
||||
|
||||
Updating
|
||||
--------
|
||||
|
||||
First follow the `Uninstalling`_ section below, then install the latest version.
|
||||
|
||||
Uninstalling
|
||||
------------
|
||||
|
||||
Navigate to ``Edit > Preferences > Add-ons``. Due to a limitation in Blender,
|
||||
you have to **first disable the BlenderBIM Add-on in your Blender preferences**
|
||||
by pressing the checkbox next to the add-on, then **restart Blender**. It is
|
||||
critical to follow this sequence of disabling first, and then restarting.
|
||||
|
||||
After restarting, you can uninstall the BlenderBIM Add-on by pressing the
|
||||
``Remove`` button in the Blender preferences window.
|
||||
|
||||
Alternatively, you may uninstall manually by deleting the ``blenderbim``
|
||||
directory in :ref:`your Blender add-ons directory<where is the add-on
|
||||
installed>`.
|
||||
|
||||
.. warning::
|
||||
|
||||
It is important to follow the sequence of disabling, restarting, then removing.
|
||||
If you do not restart Blender, the add-on will fail to remove correctly, and you
|
||||
will need to uninstall manually.
|
||||
|
||||
Where is the add-on installed?
|
||||
------------------------------
|
||||
|
||||
@@ -66,118 +92,44 @@ Upon installation, the BlenderBIM Add-on is stored in the
|
||||
folder. However, the location of your Blender configuration folder depends on
|
||||
how you have installed Blender.
|
||||
|
||||
If you downloaded Blender as a ``.zip`` file without running an installer, you
|
||||
will find the Blender configuration folder in the following directory, where
|
||||
``X.XX`` is the Blender version:
|
||||
If you downloaded Blender as a ``.zip`` file without running an installer, the
|
||||
BlenderBIM Add-on will be installed in the following directory, where ``X.XX``
|
||||
is the Blender version:
|
||||
|
||||
::
|
||||
|
||||
/path/to/blender/X.XX/
|
||||
/path/to/blender/X.XX/scripts/addons/blenderbim/
|
||||
|
||||
Otherwise, if you installed Blender using an installation package, the Blender
|
||||
configuration folder depends on which operating system you use.
|
||||
|
||||
On Linux, if you are installing the add-on as a user:
|
||||
|
||||
::
|
||||
|
||||
~/.config/blender/X.XX/
|
||||
~/.config/blender/X.XX/scripts/addons/blenderbim/
|
||||
|
||||
On Linux, if you are deploying the add-on system-wide (this may also depend on
|
||||
your Linux distribution):
|
||||
|
||||
::
|
||||
|
||||
/usr/share/blender/X.XX/
|
||||
/usr/share/blender/X.XX/scripts/addons/blenderbim/
|
||||
|
||||
On Mac, if you are installing the add-on as a user:
|
||||
|
||||
::
|
||||
|
||||
/Users/{YOUR_USER}/Library/Application Support/Blender/X.XX/
|
||||
/Users/{YOUR_USER}/Library/Application Support/Blender/X.XX/scripts/addons/blenderbim/
|
||||
|
||||
On Mac, if you are deploying the add-on system-wide:
|
||||
|
||||
::
|
||||
|
||||
/Library/Application Support/Blender/X.XX/
|
||||
/Library/Application Support/Blender/X.XX/scripts/addons/blenderbim/
|
||||
|
||||
On Windows:
|
||||
|
||||
::
|
||||
|
||||
C:\Users\{YOUR_USER}\AppData\Roaming\Blender Foundation\X.XX\
|
||||
|
||||
Updating
|
||||
--------
|
||||
|
||||
First uninstall the current BlenderBIM add-on, then install the latest version.
|
||||
|
||||
Uninstalling
|
||||
------------
|
||||
|
||||
Navigate to ``Edit > Preferences > Add-ons``. Due to a limitation in Blender,
|
||||
you have to first disable the BlenderBIM Add-on in your Blender preferences by
|
||||
pressing the checkbox next to the add-on, then restart Blender. After
|
||||
restarting, you can uninstall the BlenderBIM Add-on by pressing the ``Remove``
|
||||
button in the Blender preferences window.
|
||||
|
||||
Alternatively, you may uninstall manually by deleting the ``blenderbim/``
|
||||
directory in your Blender add-ons directory.
|
||||
|
||||
.. warning::
|
||||
|
||||
It is important to follow the sequence of disabling, restarting, then removing.
|
||||
If you do not restart Blender, the add-on will fail to remove correctly, and you
|
||||
will need to uninstall manually.
|
||||
|
||||
|
||||
FAQ
|
||||
---
|
||||
|
||||
1. **Some other error prevents me from installing or doing basic functions with
|
||||
the add-on. Is it specific to my environment?**
|
||||
|
||||
Sometimes it is helpful to try installing and using the BlenderBIM Add-on on
|
||||
a "clean environment". A clean environment is defined as a fresh Blender
|
||||
installation with no other add-ons enabled with factory settings.
|
||||
|
||||
To quickly test in a clean environment, find your Blender configuration
|
||||
folder based on the `where is the add-on installed`_ section. Rename the
|
||||
folder from ``X.XX`` to something else like ``X.XX_backup``, then restart
|
||||
Blender and try follow the installation instructions again.
|
||||
|
||||
If this fixes your issue, consider disabling other add-ons one by one until
|
||||
you find a conflict as a next step to isolating the issue.
|
||||
|
||||
2. **I get an error similar to "ImportError: IfcOpenShell not built for 'linux/64bit/python3.10'"**
|
||||
|
||||
If you are using a Mac, be sure to use the Mac Silicon version if you have a
|
||||
newer Mac. The only exception is if you have installed Blender using Steam
|
||||
on a Mac, in which case you need to use the Mac Intel download.
|
||||
|
||||
For all other scenarios, check the BlenderBIM Add-on zip file which you
|
||||
downloaded. The zip will have either ``py39``, ``py310``, or ``py311`` in
|
||||
the name. See the instructions in the :ref:`devs/installation:unstable
|
||||
installation` section to check that you have installed the correct version.
|
||||
|
||||
3. **I am on Ubuntu and get an error similar to "ImportError:
|
||||
/lib/x86_64-linux-gnu/libm.so.6: version GLIBC_2.29 not found"**
|
||||
|
||||
Our latest package which uses IfcOpenShell v0.7.0 is built using Ubuntu 20 LTS.
|
||||
If you have an older Ubuntu version, you can either upgrade to 19.10 or above,
|
||||
or you'll need to compile IfcOpenShell yourself.
|
||||
|
||||
4. **I get an error saying "ModuleNotFoundError: No module named 'numpy'"**"
|
||||
|
||||
If you have installed Blender from another source instead of from
|
||||
`Blender.org <https://www.blender.org/download/>`__, such as from your
|
||||
distro's package repositories, then you may be missing some modules like
|
||||
``numpy``. Try installing it manually like ``apt install python-numpy``.
|
||||
|
||||
5. **I get an error similar to RuntimeError: Instance #1234 not found**
|
||||
|
||||
Blender saves and loads projects to a ``.blend`` file. However. the
|
||||
BlenderBIM Add-on works with native IFC, and this means instead of saving
|
||||
and loading ``.blend`` files, you should instead save and load the ``.ifc``
|
||||
project.
|
||||
|
||||
If you have opened a ``.blend`` file, there is a risk that the contents of
|
||||
the ``.blend`` session do not correlate to the contents of the ``.ifc``,
|
||||
which can cause this error. Unless you are an advanced user, only save and
|
||||
load ``.ifc`` files.
|
||||
C:\Users\{YOUR_USER}\AppData\Roaming\Blender Foundation\X.XX\scripts\addons\blenderbim\
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
Troubleshooting
|
||||
===============
|
||||
|
||||
The BlenderBIM Add-on is alpha software. There are many bugs! When something
|
||||
goes wrong, you may see some computer code flash up on your screen. You may
|
||||
also see an error message:
|
||||
|
||||
.. image:: images/error-message.png
|
||||
|
||||
**Don't panic!** Click on the button that says **Copy Error Message To
|
||||
Clipboard**. You will need to paste this text in a bug report.
|
||||
|
||||
If you do not have a GitHub account, you will need to sign up to report a bug.
|
||||
In addition to pasting the error message text, please also describe what you
|
||||
were doing, and attach your IFC file or screenshots if relevant.
|
||||
|
||||
.. container:: blockbutton
|
||||
|
||||
`Report a bug <https://github.com/IfcOpenShell/IfcOpenShell/issues/new>`__
|
||||
|
||||
If your issue is particularly complex, you can also chat live with developers
|
||||
or other powerusers.
|
||||
|
||||
.. container:: blockbutton
|
||||
|
||||
`Chat live with a developer <https://osarch.org/chat>`_
|
||||
|
||||
Installation issues
|
||||
-------------------
|
||||
|
||||
If you are unable to install the BlenderBIM Add-on, make sure you are using
|
||||
**Blender 4.1** installed from https://blender.org/ and are installing the
|
||||
latest version from https://blenderbim.org.
|
||||
|
||||
Other common solutions are listed below. If none of these fix the problem, you
|
||||
can `report a bug <https://github.com/ifcopenshell/ifcopenshell/issues>`_ or
|
||||
`live chat with a developer <https://osarch.org/chat/>`_.
|
||||
|
||||
1. **Some other error prevents me from installing or doing basic functions with
|
||||
the add-on. Is it specific to my environment?**
|
||||
|
||||
Try installing and using the BlenderBIM Add-on on a "clean environment". A
|
||||
clean environment is a fresh Blender installation with no other add-ons
|
||||
enabled with factory settings.
|
||||
|
||||
To quickly test in a clean environment, first :ref:`find your Blender
|
||||
configuration folder<users/installation:where is the add-on installed?>`.
|
||||
Rename the folder from ``X.XX`` to something else like ``X.XX_backup``, then
|
||||
restart Blender and try follow the :doc:`installation
|
||||
instructions<installation>` again.
|
||||
|
||||
If this fixes your issue, consider disabling other add-ons one by one until
|
||||
you find a conflict as a next step to isolating the issue.
|
||||
|
||||
2. **I get an error similar to "ImportError: IfcOpenShell not built for 'linux/64bit/python3.10'"**
|
||||
|
||||
If you are using a Mac, be sure to use the Mac Silicon version if you have a
|
||||
newer Mac. The only exception is if you have installed Blender using Steam
|
||||
on a Mac, in which case you need to use the Mac Intel download.
|
||||
|
||||
For all other scenarios, check the BlenderBIM Add-on zip file which you
|
||||
downloaded. The zip will have either ``py39``, ``py310``, or ``py311`` in
|
||||
the name. See the instructions in the :ref:`devs/installation:unstable
|
||||
installation` section to check that you have installed the correct version.
|
||||
|
||||
3. **I am on Ubuntu and get an error similar to "ImportError:
|
||||
/lib/x86_64-linux-gnu/libm.so.6: version GLIBC_2.29 not found"**
|
||||
|
||||
Our latest package which uses IfcOpenShell v0.7.0 is built using Ubuntu 20 LTS.
|
||||
If you have an older Ubuntu version, you can either upgrade to 19.10 or above,
|
||||
or you'll need to compile IfcOpenShell yourself.
|
||||
|
||||
4. **I get an error saying "ModuleNotFoundError: No module named 'numpy'"**"
|
||||
|
||||
If you have installed Blender from another source instead of from
|
||||
`Blender.org <https://www.blender.org/download/>`__, such as from your
|
||||
distro's package repositories, then you may be missing some modules like
|
||||
``numpy``. Try installing it manually like ``apt install python-numpy``.
|
||||
|
||||
Common issues
|
||||
-------------
|
||||
|
||||
1. **I get an error similar to RuntimeError: Instance #1234 not found**
|
||||
|
||||
Blender saves and loads projects to a ``.blend`` file. However. the
|
||||
BlenderBIM Add-on works with native IFC, and this means instead of saving
|
||||
and loading ``.blend`` files, you should instead save and load the ``.ifc``
|
||||
project.
|
||||
|
||||
If you have opened a ``.blend`` file, there is a risk that the contents of
|
||||
the ``.blend`` session do not correlate to the contents of the ``.ifc``,
|
||||
which can cause this error. Unless you are an advanced user, only save and
|
||||
load ``.ifc`` files.
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
import xml.etree.ElementTree as ET
|
||||
import ifcopenshell
|
||||
import ifcopenshell.guid
|
||||
import os
|
||||
|
||||
class IFC4Extractor:
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.guid
|
||||
import ezdxf
|
||||
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import pymeshlab
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.api.owner.settings
|
||||
import ifcopenshell.guid
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
|
||||
@@ -23,6 +23,7 @@ import pywavefront
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.api.owner.settings
|
||||
import ifcopenshell.guid
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
|
||||
@@ -22,6 +22,7 @@ import brickschema
|
||||
import brickschema.persistent
|
||||
from brickschema.namespaces import REF, A
|
||||
import ifcopenshell
|
||||
import ifcopenshell.guid
|
||||
import blenderbim.core.tool
|
||||
import blenderbim.tool as tool
|
||||
from rdflib.namespace import RDF
|
||||
|
||||
@@ -21,6 +21,7 @@ from pathlib import Path
|
||||
import bpy
|
||||
import mathutils
|
||||
import ifcopenshell
|
||||
import ifcopenshell.guid
|
||||
import blenderbim.core.tool
|
||||
import blenderbim.tool as tool
|
||||
from test.bim.bootstrap import NewFile
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
import bpy
|
||||
import math
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import blenderbim.core.tool
|
||||
import blenderbim.tool as tool
|
||||
from mathutils import Vector
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
import json
|
||||
import ifcopenshell
|
||||
import ifcopenshell.guid
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
import os
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.guid
|
||||
from datetime import datetime
|
||||
|
||||
from .geometry import GeometryIO
|
||||
|
||||
@@ -72,8 +72,8 @@ def get_facility_data(ifc_file, element):
|
||||
"ModelProjectID": ifc_file.by_type("IfcProject")[0].GlobalId,
|
||||
"ModelSiteID": getattr(get_facility_parent(element, "IfcSite"), "GlobalId", None),
|
||||
"ModelBuildingID": element.GlobalId,
|
||||
"LinearUnits": "millimeters",
|
||||
"AreaUnits": "square meters",
|
||||
"LengthUnit": "millimeters",
|
||||
"AreaUnit": "square meters",
|
||||
"Phase": ifc_file.by_type("IfcProject")[0].Phase,
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ def get_space_data(ifc_file, element):
|
||||
"Description": element.LongName,
|
||||
"ClassificationIdentification": get_classification_identification(element),
|
||||
"ClassificationName": get_classification_name(element),
|
||||
"LevelName": getattr(get_facility_parent(element, "IfcBuildingStorey"), "Name", None),
|
||||
"StoreyName": getattr(get_facility_parent(element, "IfcBuildingStorey"), "Name", None),
|
||||
"OrganizationName": get_owner_name(element),
|
||||
"CreationDate": get_owner_creation_date(element),
|
||||
"ModelSoftware": get_owner_application(element),
|
||||
@@ -261,8 +261,8 @@ config = {
|
||||
"ModelProjectID",
|
||||
"ModelSiteID",
|
||||
"ModelBuildingID",
|
||||
"LinearUnits",
|
||||
"AreaUnits",
|
||||
"LengthUnit",
|
||||
"AreaUnit",
|
||||
"Phase",
|
||||
],
|
||||
"colours": "ppppreeeeesss",
|
||||
@@ -295,7 +295,7 @@ config = {
|
||||
"Description",
|
||||
"ClassificationIdentification",
|
||||
"ClassificationName",
|
||||
"LevelName",
|
||||
"StoreyName",
|
||||
"OrganizationName",
|
||||
"CreationDate",
|
||||
"ModelSoftware",
|
||||
@@ -305,7 +305,7 @@ config = {
|
||||
"NetFloorArea",
|
||||
],
|
||||
"colours": "ppprreeess",
|
||||
"sort": [{"name": "LevelName", "order": "ASC"}, {"name": "Name", "order": "ASC"}],
|
||||
"sort": [{"name": "StoreyName", "order": "ASC"}, {"name": "Name", "order": "ASC"}],
|
||||
"get_category_elements": get_spaces,
|
||||
"get_element_data": get_space_data,
|
||||
},
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
# along with IfcFM. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.guid
|
||||
import ifcopenshell.util.fm
|
||||
import ifcopenshell.util.date
|
||||
import ifcopenshell.util.system
|
||||
|
||||
@@ -59,7 +59,6 @@
|
||||
#include <GeomAPI_ProjectPointOnSurf.hxx>
|
||||
#include <Geom_Plane.hxx>
|
||||
#include <IntTools_FaceFace.hxx>
|
||||
#include <STEPConstruct_PointHasher.hxx>
|
||||
#include "clash_utils.h"
|
||||
|
||||
#ifdef WITH_HDF5
|
||||
|
||||
@@ -186,6 +186,7 @@ Valid keys are:
|
||||
"``material`` or ``mat``", "Gets the assigned material, which may be a material set."
|
||||
"``item`` or ``i``", "If the previous key returns a material set, gets the relevant material set items"
|
||||
"``materials`` or ``mats``", "Gets a list of IfcMaterials assigned directly or indirectly (such as via a material set) to the element"
|
||||
"``profiles``", "Gets a list of IfcProfileDefs assigned (such as via a material profile) or used (such as in an extrusion) in the element"
|
||||
"``x``", "Gets the X coordinate of the element's placement"
|
||||
"``y``", "Gets the Y coordinate of the element's placement"
|
||||
"``z``", "Gets the Z coordinate of the element's placement"
|
||||
|
||||
@@ -86,6 +86,18 @@ from .file import file
|
||||
from .entity_instance import entity_instance, register_schema_attributes
|
||||
from .sql import sqlite, sqlite_entity
|
||||
|
||||
# explicitly specify available imported symbols
|
||||
# (it's a requirement for a typed library)
|
||||
__all__ = [
|
||||
"ifcopenshell_wrapper",
|
||||
"file",
|
||||
"entity_instance",
|
||||
"sqlite",
|
||||
"sqlite_entity",
|
||||
"stream",
|
||||
"stream_entity",
|
||||
]
|
||||
|
||||
try:
|
||||
from .stream import stream, stream_entity
|
||||
except:
|
||||
|
||||
@@ -23,11 +23,23 @@ the rules of the IFC schema. This API module provides simple to use authoring
|
||||
functions that hide this complexity from you. Things like managing differences
|
||||
between IFC versions, tracking owernship changes, or cleaning up after orphaned
|
||||
relationships are all handled automatically.
|
||||
|
||||
If you're new to IFC authoring, start by looking at the following APIs:
|
||||
|
||||
- See :func:`ifcopenshell.api.project.create_file` to create a new IFC.
|
||||
- See :func:`ifcopenshell.api.root.create_entity` to create new entities, like
|
||||
the mandatory IfcProject, and then an IfcSite, IfcWall, etc.
|
||||
- See :func:`ifcopenshell.api.aggregate.assign_object` to create a spatial
|
||||
hierarchy.
|
||||
- See :func:`ifcopenshell.api.spatial.assign_container` to place physical
|
||||
elements (e.g. walls) inside spatial elements (e.g. building storeys).
|
||||
|
||||
Also see how to `create a simple model from scratch
|
||||
<https://docs.ifcopenshell.org/ifcopenshell-python/code_examples.html#create-a-simple-model-from-scratch>`_.
|
||||
"""
|
||||
|
||||
import json
|
||||
import numpy
|
||||
import pkgutil
|
||||
import inspect
|
||||
import importlib
|
||||
import ifcopenshell
|
||||
@@ -55,6 +67,20 @@ def batching_argument_deprecation(
|
||||
return (replace_usecase or usecase_path, settings)
|
||||
|
||||
|
||||
def renamed_arguments_deprecation(
|
||||
usecase_path: str, settings: dict, arguments_remapped: dict[str, str]
|
||||
) -> tuple[str, dict]:
|
||||
for prev_argument, new_argument in arguments_remapped.items():
|
||||
if prev_argument in settings:
|
||||
print(
|
||||
f"WARNING. `{prev_argument}` argument is deprecated for API method "
|
||||
f'"{usecase_path}" and should be replaced with `{new_argument}`.'
|
||||
)
|
||||
settings = settings | {new_argument: settings[prev_argument]}
|
||||
settings.pop(prev_argument)
|
||||
return (usecase_path, settings)
|
||||
|
||||
|
||||
ARGUMENTS_DEPRECATION = {
|
||||
"spatial.assign_container": partial(
|
||||
batching_argument_deprecation, prev_argument="product", new_argument="products"
|
||||
@@ -131,6 +157,10 @@ ARGUMENTS_DEPRECATION = {
|
||||
"project.unassign_declaration": partial(
|
||||
batching_argument_deprecation, prev_argument="definition", new_argument="definitions"
|
||||
),
|
||||
"group.add_group": partial(
|
||||
renamed_arguments_deprecation, arguments_remapped={"Name": "name", "Description": "description"}
|
||||
),
|
||||
"layer.add_layer": partial(renamed_arguments_deprecation, arguments_remapped={"Name": "name"}),
|
||||
}
|
||||
|
||||
|
||||
@@ -158,31 +188,6 @@ def run(
|
||||
for listener in pre_listeners.get(usecase_path, {}).values():
|
||||
listener(usecase_path, ifc_file, settings)
|
||||
|
||||
|
||||
# TODO: settings serialization for client-server systems
|
||||
# def serialise_entity_instance(entity):
|
||||
# return {"cast_type": "entity_instance", "value": entity.id(), "Name": getattr(entity, "Name", None)}
|
||||
# vcs_settings = settings.copy()
|
||||
# for key, value in settings.items():
|
||||
# if isinstance(value, ifcopenshell.entity_instance):
|
||||
# vcs_settings[key] = serialise_entity_instance(value)
|
||||
# elif isinstance(value, numpy.ndarray):
|
||||
# vcs_settings[key] = {"cast_type": "ndarray", "value": value.tolist()}
|
||||
# elif isinstance(value, list) and value and isinstance(value[0], ifcopenshell.entity_instance):
|
||||
# vcs_settings[key] = [serialise_entity_instance(i) for i in value]
|
||||
if "add_representation" in usecase_path:
|
||||
pass
|
||||
# print(usecase_path, "{ ... settings too complex right now ... }")
|
||||
elif "owner." in usecase_path:
|
||||
pass
|
||||
else:
|
||||
pass
|
||||
# print(vcs_settings)
|
||||
# try:
|
||||
# print(usecase_path, json.dumps(vcs_settings))
|
||||
# except:
|
||||
# print(usecase_path, vcs_settings)
|
||||
|
||||
usecase_class = CACHED_USECASE_CLASSES.get(usecase_path)
|
||||
if usecase_class is None:
|
||||
importlib.import_module(f"ifcopenshell.api.{usecase_path}")
|
||||
@@ -296,6 +301,29 @@ def extract_docs(module, usecase):
|
||||
return node_data
|
||||
|
||||
|
||||
def serialise_settings(settings):
|
||||
def serialise_entity_instance(entity):
|
||||
return {"cast_type": "entity_instance", "value": entity.id(), "Name": getattr(entity, "Name", None)}
|
||||
|
||||
vcs_settings = settings.copy()
|
||||
for key, value in settings.items():
|
||||
if isinstance(value, ifcopenshell.entity_instance):
|
||||
vcs_settings[key] = serialise_entity_instance(value)
|
||||
elif isinstance(value, numpy.ndarray):
|
||||
vcs_settings[key] = {"cast_type": "ndarray", "value": value.tolist()}
|
||||
elif isinstance(value, list) and value and isinstance(value[0], ifcopenshell.entity_instance):
|
||||
vcs_settings[key] = [serialise_entity_instance(i) for i in value]
|
||||
else:
|
||||
try:
|
||||
vcs_settings[key] = str(value)
|
||||
except:
|
||||
vcs_settings[key] = "n/a"
|
||||
try:
|
||||
return json.dumps(vcs_settings)
|
||||
except:
|
||||
return str(vcs_settings)
|
||||
|
||||
|
||||
def wrap_usecase(usecase_path, usecase):
|
||||
"""Wraps an API function in pre/post listeners."""
|
||||
|
||||
@@ -303,7 +331,9 @@ def wrap_usecase(usecase_path, usecase):
|
||||
ifc_file = args[0] if args else None
|
||||
nonlocal usecase_path
|
||||
if should_run_listeners:
|
||||
for listener in pre_listeners.get(usecase_path, {}).values():
|
||||
listeners = list(pre_listeners.get(usecase_path, {}).values())
|
||||
listeners += pre_listeners.get("*", {}).values()
|
||||
for listener in listeners:
|
||||
listener(usecase_path, ifc_file, settings)
|
||||
|
||||
# see #4531
|
||||
@@ -312,12 +342,24 @@ def wrap_usecase(usecase_path, usecase):
|
||||
|
||||
try:
|
||||
result = usecase(*args, **settings)
|
||||
except TypeError as e:
|
||||
msg = f"Incorrect function arguments provided for {usecase_path}\n{str(e)}. You specified args {args} and settings {settings}\n\nCorrect signature is {inspect.signature(usecase)}\nSee help(ifcopenshell.api.{usecase_path}) for documentation."
|
||||
except NotImplementedError as e:
|
||||
if not e.args[0].startswith(f"{usecase.__name__}()"):
|
||||
# signature errors typically start with function name
|
||||
# e.g. "TypeError: edit_library() got an unexpected keyword argument 'test'"
|
||||
# otherwise it's an error inside api call and we shouldn't get in the way
|
||||
raise e
|
||||
msg = (
|
||||
f"Incorrect function arguments provided for {usecase_path}\n{str(e)}. "
|
||||
f"You specified args {args} and settings {settings}\n\n"
|
||||
f"Correct signature is {inspect.signature(usecase)}\n"
|
||||
f"See help(ifcopenshell.api.{usecase_path}) for documentation."
|
||||
)
|
||||
raise TypeError(msg) from e
|
||||
|
||||
if should_run_listeners:
|
||||
for listener in post_listeners.get(usecase_path, {}).values():
|
||||
listeners = list(post_listeners.get(usecase_path, {}).values())
|
||||
listeners += post_listeners.get("*", {}).values()
|
||||
for listener in listeners:
|
||||
listener(usecase_path, ifc_file, settings)
|
||||
|
||||
return result
|
||||
@@ -328,50 +370,16 @@ def wrap_usecase(usecase_path, usecase):
|
||||
return wrapper
|
||||
|
||||
|
||||
# Expose all submodules. This means that the user can just type `import ifcopenshell.api`.
|
||||
import ifcopenshell.api.aggregate as aggregate
|
||||
import ifcopenshell.api.attribute as attribute
|
||||
import ifcopenshell.api.boundary as boundary
|
||||
import ifcopenshell.api.classification as classification
|
||||
import ifcopenshell.api.constraint as constraint
|
||||
import ifcopenshell.api.context as context
|
||||
import ifcopenshell.api.control as control
|
||||
import ifcopenshell.api.cost as cost
|
||||
import ifcopenshell.api.document as document
|
||||
import ifcopenshell.api.drawing as drawing
|
||||
import ifcopenshell.api.geometry as geometry
|
||||
import ifcopenshell.api.georeference as georeference
|
||||
import ifcopenshell.api.grid as grid
|
||||
import ifcopenshell.api.group as group
|
||||
import ifcopenshell.api.layer as layer
|
||||
import ifcopenshell.api.library as library
|
||||
import ifcopenshell.api.material as material
|
||||
import ifcopenshell.api.nest as nest
|
||||
import ifcopenshell.api.owner as owner
|
||||
import ifcopenshell.api.profile as profile
|
||||
import ifcopenshell.api.project as project
|
||||
import ifcopenshell.api.pset as pset
|
||||
import ifcopenshell.api.pset_template as pset_template
|
||||
import ifcopenshell.api.resource as resource
|
||||
import ifcopenshell.api.root as root
|
||||
import ifcopenshell.api.sequence as sequence
|
||||
import ifcopenshell.api.spatial as spatial
|
||||
import ifcopenshell.api.structural as structural
|
||||
import ifcopenshell.api.style as style
|
||||
import ifcopenshell.api.system as system
|
||||
import ifcopenshell.api.type as type # Whoohoo!
|
||||
import ifcopenshell.api.unit as unit
|
||||
import ifcopenshell.api.void as void
|
||||
def wrap_usecases(path, name):
|
||||
"""This developer feature wraps an API module's usecases with listeners."""
|
||||
import sys
|
||||
import pkgutil
|
||||
|
||||
# Wrap all submodule usecases with listeners.
|
||||
# This for loop also conveniently ensures that the above imports are comprehensive.
|
||||
for loader, module_name, is_pkg in pkgutil.iter_modules(__path__, __name__ + "."):
|
||||
# Check if it's a direct child (only one level deep)
|
||||
if module_name.count(".") == __name__.count(".") + 1:
|
||||
module_name = module_name.split(".")[-1]
|
||||
module = globals()[module_name]
|
||||
for usecase_name in vars(module):
|
||||
usecase = getattr(module, usecase_name)
|
||||
if callable(usecase):
|
||||
usecase_path = f"{module_name}.{usecase_name}"
|
||||
setattr(module, usecase_name, wrap_usecase(usecase_path, usecase))
|
||||
module_name = name.split(".")[-1]
|
||||
module = sys.modules[name]
|
||||
for loader, usecase_name, is_pkg in pkgutil.iter_modules(path):
|
||||
# We may not be able to get the usecase if we are missing a dependency.
|
||||
usecase = getattr(module, usecase_name, None)
|
||||
if callable(usecase):
|
||||
usecase_path = f"{module_name}.{usecase_name}"
|
||||
setattr(module, usecase_name, wrap_usecase(usecase_path, usecase))
|
||||
|
||||
@@ -16,12 +16,15 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Aggregates are the concept of breaking down larger wholes into smaller parts.
|
||||
"""Aggregates is the concept of breaking down larger wholes into smaller parts.
|
||||
|
||||
One common use is spatial elements, such as how a site has multiple buildings,
|
||||
and a building has multiple storeys. Another is for regular elements, such as
|
||||
how a wall is made out of members and coverings.
|
||||
For example, spatial elements such as sites are broken down into one or more
|
||||
buildings, and a building is broken down into storeys. Another example is for
|
||||
physical elements, such as how a wall is made out of members and coverings.
|
||||
"""
|
||||
|
||||
from .. import wrap_usecases
|
||||
from .assign_object import assign_object
|
||||
from .unassign_object import unassign_object
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.guid
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.placement
|
||||
from typing import Union
|
||||
|
||||
@@ -16,4 +16,14 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Basic modification of the attributes of an element.
|
||||
|
||||
All IFC entities have attributes. Some of these attributes contain rules about
|
||||
inheritance and what they are allowed to contain. These usecases make sure that
|
||||
any editing complies with these rules.
|
||||
"""
|
||||
|
||||
from .. import wrap_usecases
|
||||
from .edit_attributes import edit_attributes
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
@@ -17,9 +17,11 @@
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.element
|
||||
from typing import Any
|
||||
|
||||
|
||||
def edit_attributes(file, product=None, attributes=None) -> None:
|
||||
def edit_attributes(file: ifcopenshell.file, product: ifcopenshell.entity_instance, attributes: dict[str, Any]) -> None:
|
||||
"""Edit the attributes of a product
|
||||
|
||||
All IFC entities have attributes. Normally they can be edited directly,
|
||||
@@ -31,7 +33,7 @@ def edit_attributes(file, product=None, attributes=None) -> None:
|
||||
entity.
|
||||
:type product: ifcopenshell.entity_instance
|
||||
:param attributes: a dictionary of attribute names and values.
|
||||
:type attributes: dict, optional
|
||||
:type attributes: dict
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
|
||||
@@ -18,9 +18,15 @@
|
||||
|
||||
"""Boundaries are primarily used for representing virtual interfaces between
|
||||
spaces for energy analysis.
|
||||
|
||||
Boundaries may be associated with spaces or physical elements that enclose
|
||||
spaces such as walls, doors, and windows.
|
||||
"""
|
||||
|
||||
from .. import wrap_usecases
|
||||
from .assign_connection_geometry import assign_connection_geometry
|
||||
from .copy_boundary import copy_boundary
|
||||
from .edit_attributes import edit_attributes
|
||||
from .remove_boundary import remove_boundary
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
@@ -17,17 +17,18 @@
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell.util.unit
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def assign_connection_geometry(
|
||||
file,
|
||||
rel_space_boundary=None,
|
||||
outer_boundary=None,
|
||||
inner_boundaries=None,
|
||||
location=None,
|
||||
axis=None,
|
||||
ref_direction=None,
|
||||
unit_scale=None,
|
||||
file: ifcopenshell.file,
|
||||
rel_space_boundary: ifcopenshell.entity_instance,
|
||||
outer_boundary: list[tuple[float, float]],
|
||||
location: tuple[float, float, float],
|
||||
axis: tuple[float, float, float],
|
||||
ref_direction: tuple[float, float, float],
|
||||
inner_boundaries: Optional[list[list[tuple[float, float]]]] = None,
|
||||
unit_scale: Optional[float] = None,
|
||||
) -> None:
|
||||
"""Create and assign a connection geometry to a space boundary relationship
|
||||
|
||||
@@ -44,24 +45,24 @@ def assign_connection_geometry(
|
||||
polyline. The last point will connect to the first point. Each
|
||||
point is represented by an interable of 2 floats. The coordinates of
|
||||
the points are relative to the positional matrix arguments.
|
||||
:type outer_boundary: list[list[float]]
|
||||
:type outer_boundary: list[tuple[float, float]]
|
||||
:param inner_boundaries: A list of zero or more inner boundaries to use
|
||||
for the plane. Each boundary is represented by an open polyline, as
|
||||
defined by the outer_boundary argument.
|
||||
:type inner_boundaries: list[list[list[float]]], optional
|
||||
:type inner_boundaries: list[list[tuple[float, float]]], optional
|
||||
:param location: The local origin of the connection geometry, defined as
|
||||
an XYZ coordinate relative to the placement of the space that is
|
||||
being bounded.
|
||||
:type location: list[float]
|
||||
:type location: tuple[float, float, float]
|
||||
:param axis: The local X axis of the connection geometry, defined as an
|
||||
XYZ vector relative to the placement of the space that is being
|
||||
bounded.
|
||||
:type axis: list[float]
|
||||
:type axis: tuple[float, float, float]
|
||||
:param ref_direction: The local Z axis of the connection geometry,
|
||||
defined as an XYZ vector relative to the placement of the space that
|
||||
is being bounded. The Y vector is automatically derived using the
|
||||
right hand rule.
|
||||
:type ref_direction: list[float]
|
||||
:type ref_direction: tuple[float, float, float]
|
||||
:param unit_scale: The unit scale as calculated by
|
||||
ifcopenshell.util.unit.calculate_unit_scale. If not provided, it
|
||||
will be automatically calculated for you.
|
||||
@@ -88,7 +89,6 @@ def assign_connection_geometry(
|
||||
usecase.axis = axis
|
||||
usecase.ref_direction = ref_direction
|
||||
usecase.unit_scale = unit_scale
|
||||
usecase.ifc_vertices = []
|
||||
return usecase.execute()
|
||||
|
||||
|
||||
|
||||
@@ -19,13 +19,13 @@
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
def copy_boundary(file, boundary=None) -> None:
|
||||
def copy_boundary(file: ifcopenshell.file, boundary: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
|
||||
"""Copies a space boundary
|
||||
|
||||
:param boundary: The IfcRelSpaceBoundary you want to copy.
|
||||
:type boundary: ifcopenshell.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
:return: Duplicate of the IfcRelSpaceBoundary
|
||||
:rtype: ifcopenshell.entity_instance
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
@@ -15,15 +15,17 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
import ifcopenshell
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def edit_attributes(
|
||||
file,
|
||||
entity=None,
|
||||
relating_space=None,
|
||||
related_building_element=None,
|
||||
parent_boundary=None,
|
||||
corresponding_boundary=None,
|
||||
file: ifcopenshell.file,
|
||||
entity: ifcopenshell.entity_instance,
|
||||
relating_space: ifcopenshell.entity_instance,
|
||||
related_building_element: ifcopenshell.entity_instance,
|
||||
parent_boundary: Optional[ifcopenshell.entity_instance] = None,
|
||||
corresponding_boundary: Optional[ifcopenshell.entity_instance] = None,
|
||||
) -> None:
|
||||
"""Modify the relationships of a space boundary relationship
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ import ifcopenshell
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
def remove_boundary(file, boundary=None) -> None:
|
||||
def remove_boundary(file: ifcopenshell.file, boundary: ifcopenshell.entity_instance) -> None:
|
||||
"""Removes a space boundary
|
||||
|
||||
The relating space or related building element is untouched. Only the
|
||||
|
||||
@@ -16,9 +16,23 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Classification systems are a way of categorising objects
|
||||
|
||||
Although IFC itself comes with a built-in classification hierarchy (e.g.
|
||||
IfcWall and its predefined types of PARTITIONING, etc), there are many external
|
||||
or custom classification systems such as Uniclass, Omniclass and more. IFC is
|
||||
able to integrate with any external classification system.
|
||||
|
||||
This API allows you to manage and assign external classification systems and
|
||||
references.
|
||||
"""
|
||||
|
||||
from .. import wrap_usecases
|
||||
from .add_classification import add_classification
|
||||
from .add_reference import add_reference
|
||||
from .edit_classification import edit_classification
|
||||
from .edit_reference import edit_reference
|
||||
from .remove_classification import remove_classification
|
||||
from .remove_reference import remove_reference
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.guid
|
||||
import ifcopenshell.util.schema
|
||||
import ifcopenshell.util.date
|
||||
from typing import Union
|
||||
@@ -109,7 +110,9 @@ class Usecase:
|
||||
"IfcCalendarDate", **ifcopenshell.util.date.datetime2ifc(edition_date, "IfcCalendarDate")
|
||||
)
|
||||
else:
|
||||
result.EditionDate = ifcopenshell.util.date.datetime2ifc(edition_date, "IfcDate")
|
||||
if edition_date:
|
||||
edition_date = ifcopenshell.util.date.datetime2ifc(edition_date, "IfcDate")
|
||||
result.EditionDate = edition_date
|
||||
|
||||
self.relate_to_project(result)
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.guid
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.schema
|
||||
from typing import Optional, Union
|
||||
|
||||
@@ -15,9 +15,13 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
import ifcopenshell
|
||||
from typing import Any
|
||||
|
||||
|
||||
def edit_classification(file, classification=None, attributes=None) -> None:
|
||||
def edit_classification(
|
||||
file: ifcopenshell.file, classification: ifcopenshell.entity_instance, attributes: dict[str, Any]
|
||||
) -> None:
|
||||
"""Edits the attributes of an IfcClassification
|
||||
|
||||
For more information about the attributes and data types of an
|
||||
@@ -26,7 +30,7 @@ def edit_classification(file, classification=None, attributes=None) -> None:
|
||||
:param classification: The IfcClassification entity you want to edit
|
||||
:type classification: ifcopenshell.entity_instance
|
||||
:param attributes: a dictionary of attribute names and values.
|
||||
:type attributes: dict, optional
|
||||
:type attributes: dict
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
|
||||
@@ -15,9 +15,13 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
import ifcopenshell
|
||||
from typing import Any
|
||||
|
||||
|
||||
def edit_reference(file, reference=None, attributes=None) -> None:
|
||||
def edit_reference(
|
||||
file: ifcopenshell.file, reference: ifcopenshell.entity_instance, attributes: dict[str, Any]
|
||||
) -> None:
|
||||
"""Edits the attributes of an IfcClassificationReference
|
||||
|
||||
For more information about the attributes and data types of an
|
||||
@@ -26,7 +30,7 @@ def edit_reference(file, reference=None, attributes=None) -> None:
|
||||
:param reference: The IfcClassificationReference entity you want to edit
|
||||
:type reference: ifcopenshell.entity_instance
|
||||
:param attributes: a dictionary of attribute names and values.
|
||||
:type attributes: dict, optional
|
||||
:type attributes: dict
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ import ifcopenshell
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
def remove_classification(file: ifcopenshell.entity_instance, classification: ifcopenshell.entity_instance) -> None:
|
||||
def remove_classification(file: ifcopenshell.file, classification: ifcopenshell.entity_instance) -> None:
|
||||
"""Removes an IfcClassification from the project and all references
|
||||
|
||||
The classification and all of its relationships, children references,
|
||||
@@ -58,15 +58,20 @@ class Usecase:
|
||||
self.file.remove(rel)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(self.file, history)
|
||||
for rel in self.file.by_type("IfcExternalReferenceRelationship"):
|
||||
if not rel.RelatingReference:
|
||||
self.file.remove(rel)
|
||||
|
||||
def get_references(self, classification):
|
||||
if self.file.schema != "IFC2X3":
|
||||
for rel in self.file.by_type("IfcExternalReferenceRelationship"):
|
||||
if not rel.RelatingReference:
|
||||
self.file.remove(rel)
|
||||
|
||||
def get_references(self, classification: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
|
||||
results = []
|
||||
if not classification.HasReferences:
|
||||
return results
|
||||
for reference in classification.HasReferences:
|
||||
results.append(reference)
|
||||
results.extend(self.get_references(reference))
|
||||
if self.file.schema == "IFC2X3":
|
||||
for reference in self.file.by_type("IfcClassificationReference"):
|
||||
if reference.ReferencedSource == classification:
|
||||
results.append(reference)
|
||||
else:
|
||||
for reference in classification.HasReferences:
|
||||
results.append(reference)
|
||||
results.extend(self.get_references(reference))
|
||||
return results
|
||||
|
||||
@@ -16,6 +16,13 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Constraints are an advanced feature allowing you to specify parametric
|
||||
limits on properties
|
||||
|
||||
Warning: usage of constraints are mostly untested in real life applications.
|
||||
"""
|
||||
|
||||
from .. import wrap_usecases
|
||||
from .add_metric import add_metric
|
||||
from .add_metric_reference import add_metric_reference
|
||||
from .add_objective import add_objective
|
||||
@@ -25,3 +32,5 @@ from .edit_objective import edit_objective
|
||||
from .remove_constraint import remove_constraint
|
||||
from .remove_metric import remove_metric
|
||||
from .unassign_constraint import unassign_constraint
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
import ifcopenshell
|
||||
|
||||
|
||||
def add_metric(file, objective=None) -> None:
|
||||
def add_metric(file: ifcopenshell.file, objective: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
|
||||
"""Add a new metric benchmark
|
||||
|
||||
Qualitative constraints may have a series of quantitative benchmarks
|
||||
@@ -50,7 +50,7 @@ def add_metric(file, objective=None) -> None:
|
||||
"Name": "Unnamed",
|
||||
"ConstraintGrade": "NOTDEFINED",
|
||||
"Benchmark": "EQUALTO",
|
||||
}
|
||||
},
|
||||
)
|
||||
if settings["objective"]:
|
||||
benchmark_values = list(settings["objective"].BenchmarkValues or [])
|
||||
|
||||
@@ -19,16 +19,18 @@
|
||||
import ifcopenshell
|
||||
|
||||
|
||||
def add_metric_reference(file, metric=None, reference_path=None) -> None:
|
||||
def add_metric_reference(
|
||||
file: ifcopenshell.file, metric: ifcopenshell.entity_instance, reference_path: str
|
||||
) -> list[ifcopenshell.entity_instance]:
|
||||
"""
|
||||
Adds a chain of references to a metric. The reference path is a string of the form "attribute.attribute.attribute"
|
||||
Used to reference a value of an attribute of an instance through a metric objective entity.
|
||||
"""
|
||||
settings = {"metric": metric, "reference_path": reference_path}
|
||||
|
||||
references_created = []
|
||||
if settings["reference_path"]:
|
||||
attributes = settings["reference_path"].split(".")
|
||||
references_created = []
|
||||
for i in range(len(attributes)):
|
||||
if i == 0:
|
||||
reference = file.create_entity("IfcReference")
|
||||
@@ -40,4 +42,4 @@ def add_metric_reference(file, metric=None, reference_path=None) -> None:
|
||||
reference.AttributeIdentifier = attributes[i]
|
||||
references_created[i - 1].InnerReference = reference
|
||||
references_created.append(reference)
|
||||
return references_created
|
||||
return references_created
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
import ifcopenshell
|
||||
|
||||
|
||||
def add_objective(file) -> None:
|
||||
def add_objective(file: ifcopenshell.file) -> ifcopenshell.entity_instance:
|
||||
"""Add a new objective constraint
|
||||
|
||||
Parametric constraints may be defined by the user. The constraint is defined
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.guid
|
||||
from typing import Union
|
||||
|
||||
|
||||
|
||||
@@ -15,9 +15,11 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
import ifcopenshell
|
||||
from typing import Any
|
||||
|
||||
|
||||
def edit_metric(file, metric=None, attributes=None) -> None:
|
||||
def edit_metric(file: ifcopenshell.file, metric: ifcopenshell.entity_instance, attributes: dict[str, Any]) -> None:
|
||||
"""Edit the attributes of a metric
|
||||
|
||||
For more information about the attributes and data types of an
|
||||
@@ -26,7 +28,7 @@ def edit_metric(file, metric=None, attributes=None) -> None:
|
||||
:param metric: The IfcMetric you want to edit.
|
||||
:type metric: ifcopenshell.entity_instance
|
||||
:param attributes: a dictionary of attribute names and values.
|
||||
:type attributes: dict, optional
|
||||
:type attributes: dict
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
|
||||
@@ -15,9 +15,13 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
import ifcopenshell
|
||||
from typing import Any
|
||||
|
||||
|
||||
def edit_objective(file, objective=None, attributes=None) -> None:
|
||||
def edit_objective(
|
||||
file: ifcopenshell.file, objective: ifcopenshell.entity_instance, attributes: dict[str, Any]
|
||||
) -> None:
|
||||
"""Edit the attributes of a objective
|
||||
|
||||
For more information about the attributes and data types of an
|
||||
@@ -26,7 +30,7 @@ def edit_objective(file, objective=None, attributes=None) -> None:
|
||||
:param objective: The IfcObjective you want to edit.
|
||||
:type objective: ifcopenshell.entity_instance
|
||||
:param attributes: a dictionary of attribute names and values.
|
||||
:type attributes: dict, optional
|
||||
:type attributes: dict
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ import ifcopenshell
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
def remove_constraint(file, constraint=None) -> None:
|
||||
def remove_constraint(file: ifcopenshell.file, constraint: ifcopenshell.entity_instance) -> None:
|
||||
"""Remove a constraint (typically an objective)
|
||||
|
||||
Removes a constraint definition and all of its associations to any
|
||||
|
||||
@@ -15,9 +15,10 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
import ifcopenshell
|
||||
|
||||
|
||||
def remove_metric(file, metric=None) -> None:
|
||||
def remove_metric(file: ifcopenshell.file, metric: ifcopenshell.entity_instance) -> None:
|
||||
"""Remove a metric benchmark
|
||||
|
||||
Removes a metric benchmark and all of its associations to any products
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user