mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-08 08:51:35 +00:00
Compare commits
56 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b4740b6949 | |||
| d66dd1be18 | |||
| adfe56c777 | |||
| dcf8329b04 | |||
| d49a3486b9 | |||
| 78759a63f2 | |||
| 913076de94 | |||
| e318cbd9e6 | |||
| 2f1de2f104 | |||
| 0e74ac8eea | |||
| 31ec4820fc | |||
| 917e7f9f15 | |||
| 2f0014a489 | |||
| 92340f159d | |||
| a392a1fbfb | |||
| 728f9e2c04 | |||
| 2edfe1e5d8 | |||
| 0b48120a66 | |||
| 577d46eb70 | |||
| 83a3813f53 | |||
| dd14f33c0b | |||
| b305e2354b | |||
| afb2742c2f | |||
| 55d533309e | |||
| 00f7871386 | |||
| f15a23483e | |||
| f14b4c467d | |||
| 4a9cf433d1 | |||
| 866362c6ed | |||
| 0dc17a2141 | |||
| d55e7faf2a | |||
| 52762f828c | |||
| 1d0868eb18 | |||
| 9461c6369e | |||
| 0f3b570800 | |||
| 30cbfad871 | |||
| 262739f37a | |||
| 03fa850ce2 | |||
| 8d2c0d0785 | |||
| 742a9df311 | |||
| c95a0e8ed5 | |||
| a28478c193 | |||
| b521901c90 | |||
| 20f8737fb3 | |||
| f18c93c9ee | |||
| a461281b1e | |||
| 2d3d747d49 | |||
| d165cd2692 | |||
| 3f0e30245b | |||
| c111aafd3a | |||
| 465bd23d6f | |||
| c7d3e00dca | |||
| 72af88a4b3 | |||
| 73b76ba666 | |||
| e93a66be49 | |||
| a4dd1c3463 |
@@ -104,7 +104,7 @@ jobs:
|
||||
# Ensure Bonsai and ifcsverchok enable/disable works before uploading to extensions repo.
|
||||
|
||||
# Download Blender.
|
||||
wget -q -O blender.tar.xz https://download.blender.org/release/Blender4.4/blender-4.4.0-linux-x64.tar.xz
|
||||
wget -q -O blender.tar.xz https://download.blender.org/release/Blender4.5/blender-4.5.0-linux-x64.tar.xz
|
||||
tar -xf blender.tar.xz
|
||||
|
||||
# Setup Blender.
|
||||
|
||||
@@ -145,7 +145,7 @@ classes = [
|
||||
prop.BIMSnapGroups,
|
||||
ui.BIM_UL_clipping_plane,
|
||||
ui.BIM_UL_generic,
|
||||
ui.BIM_UL_topics,
|
||||
ui.DocPreferences,
|
||||
ui.BIM_ADDON_preferences,
|
||||
# Tabs panel
|
||||
ui.BIM_PT_tabs,
|
||||
|
||||
@@ -139,6 +139,10 @@ class IfcExporter:
|
||||
|
||||
|
||||
class IfcExportSettings:
|
||||
"""
|
||||
Initialize only using `IfcExportSettings.factory()`.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.logger: Logger = None
|
||||
self.output_file: str = None
|
||||
|
||||
@@ -417,3 +417,5 @@ def load_post(scene):
|
||||
scene.tool_settings.use_snap = True
|
||||
# Match default Bonsai snaps
|
||||
scene.tool_settings.snap_elements_base = {"EDGE", "EDGE_PERPENDICULAR", "VERTEX", "EDGE_MIDPOINT", "FACE"}
|
||||
|
||||
tool.Blender.sync_old_preferences()
|
||||
|
||||
@@ -27,6 +27,7 @@ import ifcopenshell.util.element
|
||||
import ifcopenshell.util.unit
|
||||
from ifcopenshell.util.doc import get_attribute_doc, get_predefined_type_doc, get_property_doc
|
||||
import bonsai.tool as tool
|
||||
from types import EllipsisType
|
||||
from typing import Optional, Any, Union, TYPE_CHECKING
|
||||
from collections.abc import Callable, Iterable
|
||||
|
||||
@@ -56,7 +57,7 @@ def draw_attributes(
|
||||
popup_active_attribute: Optional[bonsai.bim.prop.Attribute] = None,
|
||||
callback: Optional[Callable[[bonsai.bim.prop.Attribute, bpy.types.UILayout], None]] = None,
|
||||
*,
|
||||
enable_search: bool = False,
|
||||
enable_search: Union[bool, EllipsisType] = ...,
|
||||
) -> None:
|
||||
"""Draw editable UI for prop.Attributes.
|
||||
|
||||
@@ -65,6 +66,13 @@ def draw_attributes(
|
||||
on it first
|
||||
|
||||
:param enable_search: Add search button to string, integer, and float attributes
|
||||
Possible values:
|
||||
|
||||
- ``...`` (default value) -
|
||||
add search if possible. If it's not possible, there will be no warnings or errors.
|
||||
- ``True`` - always add search, if it's not possible it will result in errors.
|
||||
- ``False`` - never add search.
|
||||
|
||||
"""
|
||||
for attribute in props:
|
||||
row = layout.row(align=True)
|
||||
@@ -79,7 +87,7 @@ def draw_attribute(
|
||||
attribute: bonsai.bim.prop.Attribute,
|
||||
layout: bpy.types.UILayout,
|
||||
copy_operator: Optional[str] = None,
|
||||
enable_search: bool = False,
|
||||
enable_search: Union[bool, EllipsisType] = ...,
|
||||
) -> None:
|
||||
value_name = attribute.get_value_name(display_only=True)
|
||||
|
||||
@@ -131,7 +139,9 @@ def draw_attribute(
|
||||
op.target_prop = attribute.path_from_id("string_value")
|
||||
op.include_time = attribute.special_type == "DATETIME"
|
||||
|
||||
if enable_search and attribute.data_type in ("string", "integer", "float"):
|
||||
if attribute.data_type in ("string", "integer", "float") and (
|
||||
enable_search is True or (enable_search is ... and attribute.ifc_class)
|
||||
):
|
||||
op = layout.operator("bim.attribute_search_values", text="", icon="VIEWZOOM")
|
||||
op.attribute_name = attribute.name
|
||||
op.attribute_ifc_class = attribute.ifc_class
|
||||
@@ -156,8 +166,7 @@ def draw_attribute(
|
||||
op.name = attribute.name
|
||||
|
||||
|
||||
# TODO: rename to 'import_attributes'.
|
||||
def import_attributes2(
|
||||
def import_attributes(
|
||||
element: Union[str, ifcopenshell.entity_instance],
|
||||
props: bpy.types.bpy_prop_collection_idprop[Attribute],
|
||||
callback: Optional[ImportCallback] = None,
|
||||
|
||||
@@ -105,7 +105,7 @@ class IfcStore:
|
||||
IfcStore.session_files = {}
|
||||
|
||||
@staticmethod
|
||||
def get_file():
|
||||
def get_file() -> ifcopenshell.file | None:
|
||||
if IfcStore.file is None:
|
||||
props = tool.Blender.get_bim_props()
|
||||
IfcStore.set_path(props.ifc_file)
|
||||
@@ -117,20 +117,20 @@ class IfcStore:
|
||||
return IfcStore.file
|
||||
|
||||
@staticmethod
|
||||
def set_path(value):
|
||||
def set_path(value: str) -> None:
|
||||
IfcStore.path = value
|
||||
# Interpret relative paths as relative to .blend file.
|
||||
if IfcStore.path and not os.path.isabs(IfcStore.path):
|
||||
IfcStore.path = os.path.abspath(os.path.join(bpy.path.abspath("//"), IfcStore.path))
|
||||
|
||||
@staticmethod
|
||||
def get_cache():
|
||||
def get_cache() -> ifcopenshell.geom.serializers.hdf5 | None:
|
||||
if IfcStore.cache is None and IfcStore.path:
|
||||
props = tool.Blender.get_bim_props()
|
||||
prefs = tool.Blender.get_addon_preferences()
|
||||
ifc_key = IfcStore.path + IfcStore.file.wrapped_data.header.file_name.time_stamp
|
||||
ifc_hash = hashlib.md5(ifc_key.encode("utf-8")).hexdigest()
|
||||
os.makedirs(props.cache_dir, exist_ok=True)
|
||||
IfcStore.cache_path = os.path.join(props.cache_dir, f"{ifc_hash}.h5")
|
||||
os.makedirs(prefs.cache_dir, exist_ok=True)
|
||||
IfcStore.cache_path = os.path.join(prefs.cache_dir, f"{ifc_hash}.h5")
|
||||
cache_path = Path(IfcStore.cache_path)
|
||||
cache_settings = ifcopenshell.geom.settings()
|
||||
serializer_settings = ifcopenshell.geom.serializer_settings()
|
||||
@@ -163,15 +163,15 @@ class IfcStore:
|
||||
return IfcStore.cache
|
||||
|
||||
@staticmethod
|
||||
def update_cache():
|
||||
def update_cache() -> None:
|
||||
if not IfcStore.cache:
|
||||
return
|
||||
assert IfcStore.cache_path
|
||||
assert IfcStore.file
|
||||
ifc_key = IfcStore.path + IfcStore.file.wrapped_data.header.file_name.time_stamp
|
||||
ifc_hash = hashlib.md5(ifc_key.encode("utf-8")).hexdigest()
|
||||
props = tool.Blender.get_bim_props()
|
||||
new_cache_path = os.path.join(props.cache_dir, f"{ifc_hash}.h5")
|
||||
prefs = tool.Blender.get_addon_preferences()
|
||||
new_cache_path = os.path.join(prefs.cache_dir, f"{ifc_hash}.h5")
|
||||
IfcStore.cache = None
|
||||
try:
|
||||
shutil.move(IfcStore.cache_path, new_cache_path)
|
||||
@@ -183,11 +183,11 @@ class IfcStore:
|
||||
IfcStore.get_cache()
|
||||
|
||||
@staticmethod
|
||||
def load_file(path) -> None:
|
||||
def load_file(path: str) -> None:
|
||||
if not os.path.isfile(path):
|
||||
return
|
||||
extension = path.split(".")[-1]
|
||||
props = tool.Project.get_project_props()
|
||||
prefs = tool.Blender.get_addon_preferences()
|
||||
if extension.lower() == "ifczip":
|
||||
with tempfile.TemporaryDirectory() as unzipped_path:
|
||||
with zipfile.ZipFile(path, "r") as zip_ref:
|
||||
@@ -197,7 +197,7 @@ class IfcStore:
|
||||
return
|
||||
elif extension.lower() == "ifcxml":
|
||||
IfcStore.file = ifcopenshell.file(ifcopenshell.ifcopenshell_wrapper.parse_ifcxml(path))
|
||||
elif props.should_stream:
|
||||
elif prefs.should_stream:
|
||||
IfcStore.file = ifcopenshell.open(path, should_stream=True)
|
||||
else:
|
||||
IfcStore.file = ifcopenshell.open(path)
|
||||
|
||||
@@ -231,7 +231,8 @@ class IfcImporter:
|
||||
self.progress = 0
|
||||
|
||||
self.material_creator = MaterialCreator(ifc_import_settings, self)
|
||||
classes_to_wireframe_str = tool.Drawing.get_document_props().classes_to_wireframe
|
||||
prefs = tool.Blender.get_addon_preferences()
|
||||
classes_to_wireframe_str = prefs.doc.classes_to_wireframe
|
||||
self.classes_to_wireframe_list = [word.strip() for word in classes_to_wireframe_str.split(",")]
|
||||
|
||||
def profile_code(self, message: str) -> None:
|
||||
@@ -1176,8 +1177,12 @@ class IfcImporter:
|
||||
|
||||
|
||||
class IfcImportSettings:
|
||||
"""
|
||||
Initialize only using `IfcImportSettings.factory()`.
|
||||
"""
|
||||
|
||||
input_file: Union[str, None] = None
|
||||
logger: Union[logging.Logger, None] = None
|
||||
logger: logging.Logger
|
||||
|
||||
def __init__(self):
|
||||
self.diff_file = None
|
||||
|
||||
@@ -71,11 +71,14 @@ class AggregateData:
|
||||
@classmethod
|
||||
def total_linked_aggregate(cls) -> Union[int, None]:
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
aggregate = ifcopenshell.util.element.get_aggregate(element)
|
||||
if not aggregate:
|
||||
return
|
||||
if not element:
|
||||
return
|
||||
aggregate = ifcopenshell.util.element.get_aggregate(element)
|
||||
parts = ifcopenshell.util.element.get_parts(element)
|
||||
if not aggregate and not parts:
|
||||
return
|
||||
if parts:
|
||||
aggregate = element
|
||||
|
||||
product_linked_agg_group = next(
|
||||
(
|
||||
|
||||
@@ -136,7 +136,7 @@ class BIM_PT_linked_aggregate(Panel):
|
||||
assert (element := tool.Ifc.get_entity(obj))
|
||||
row = layout.row(align=True)
|
||||
|
||||
if element.Decomposes:
|
||||
if element.Decomposes or element.IsDecomposedBy:
|
||||
Number_Linked_Aggregates = AggregateData.data["total_linked_aggregate"]
|
||||
if not Number_Linked_Aggregates:
|
||||
row.label(text="Not a Linked Aggregate")
|
||||
|
||||
@@ -107,7 +107,7 @@ class EnableEditingAttributes(bpy.types.Operator, AttributesOperator):
|
||||
if value is not None:
|
||||
new.enum_value = str(value.id())
|
||||
|
||||
bonsai.bim.helper.import_attributes2(element, props.attributes, callback=callback)
|
||||
bonsai.bim.helper.import_attributes(element, props.attributes, callback=callback)
|
||||
props.is_editing_attributes = True
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
@@ -71,6 +71,7 @@ classes = (
|
||||
ui.BIM_PT_bcf,
|
||||
ui.BIM_PT_bcf_metadata,
|
||||
ui.BIM_PT_bcf_comments,
|
||||
ui.BIM_UL_topics,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -1208,9 +1208,11 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
self.file = tool.Ifc.get()
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
assert bcfxml
|
||||
assert context.scene
|
||||
|
||||
props = tool.Bcf.get_bcf_props()
|
||||
blender_topic = props.active_topic
|
||||
assert blender_topic
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
if self.viewpoint_guid:
|
||||
viewpoint_guid = self.viewpoint_guid
|
||||
@@ -1234,6 +1236,7 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
cam_height = context.scene.render.resolution_y
|
||||
cam_aspect = cam_width / cam_height
|
||||
|
||||
assert isinstance(obj.data, bpy.types.Camera)
|
||||
obj.data.background_images.clear()
|
||||
if viewpoint.snapshot:
|
||||
obj.data.show_background_images = True
|
||||
@@ -1273,7 +1276,7 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
if tool.Bcf.get_viewpoint_bitmaps(viewpoint):
|
||||
self.create_bitmaps(bcfxml, viewpoint, topic)
|
||||
|
||||
self.setup_camera(viewpoint, obj, cam_aspect, context)
|
||||
self.setup_camera(viewpoint, obj, cam_aspect, context, cam_height, cam_width)
|
||||
return {"FINISHED"}
|
||||
|
||||
def setup_camera(
|
||||
@@ -1282,6 +1285,8 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
obj: bpy.types.Object,
|
||||
cam_aspect: float,
|
||||
context: bpy.types.Context,
|
||||
cam_height: float,
|
||||
cam_width: float,
|
||||
) -> None:
|
||||
assert isinstance(obj.data, bpy.types.Camera)
|
||||
if viewpoint.visualization_info.orthogonal_camera:
|
||||
|
||||
@@ -16,11 +16,16 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import annotations
|
||||
import os
|
||||
import bpy
|
||||
import bonsai.tool as tool
|
||||
from . import bcfstore
|
||||
from bpy.types import Panel
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bonsai.bim.module.bcf.prop import BcfTopic, BCFProperties
|
||||
|
||||
|
||||
class BIM_PT_bcf(Panel):
|
||||
@@ -33,6 +38,7 @@ class BIM_PT_bcf(Panel):
|
||||
bl_parent_id = "BIM_PT_tab_collaboration"
|
||||
|
||||
def draw(self, context):
|
||||
assert self.layout
|
||||
layout = self.layout
|
||||
layout.use_property_split = True
|
||||
layout.use_property_decorate = False
|
||||
@@ -125,6 +131,7 @@ class BIM_PT_bcf_metadata(Panel):
|
||||
# without adding them to specific topic.
|
||||
# Currently we just handle it the same way as in v2
|
||||
# documents are accessed and managed from topics.
|
||||
assert self.layout
|
||||
layout = self.layout
|
||||
layout.use_property_split = True
|
||||
layout.use_property_decorate = False
|
||||
@@ -139,6 +146,7 @@ class BIM_PT_bcf_metadata(Panel):
|
||||
bcf_v3 = bcf_verison.startswith("3")
|
||||
|
||||
topic = props.active_topic
|
||||
assert topic
|
||||
bcf_topic = bcfxml.topics[topic.name]
|
||||
|
||||
layout.label(text="Header Files:")
|
||||
@@ -303,6 +311,7 @@ class BIM_PT_bcf_comments(Panel):
|
||||
row.prop(props, "comment_text_width")
|
||||
|
||||
topic = props.active_topic
|
||||
assert topic
|
||||
for comment in topic.comments:
|
||||
box = self.layout.box()
|
||||
|
||||
@@ -341,3 +350,20 @@ class BIM_PT_bcf_comments(Panel):
|
||||
row.prop(props, "has_related_viewpoint")
|
||||
row = layout.row()
|
||||
row.operator("bim.add_bcf_comment")
|
||||
|
||||
|
||||
class BIM_UL_topics(bpy.types.UIList):
|
||||
def draw_item(
|
||||
self,
|
||||
context,
|
||||
layout: bpy.types.UILayout,
|
||||
data: BCFProperties,
|
||||
item: BcfTopic,
|
||||
icon,
|
||||
active_data,
|
||||
active_propname,
|
||||
) -> None:
|
||||
if item:
|
||||
layout.prop(item, "title", text="", emboss=False)
|
||||
else:
|
||||
layout.label(text="", translate=False)
|
||||
|
||||
@@ -38,8 +38,7 @@ class BSDDData:
|
||||
cls.data["active_dictionary"] = cls.active_dictionary()
|
||||
|
||||
@classmethod
|
||||
def active_dictionary(cls):
|
||||
props = tool.Bsdd.get_bsdd_props()
|
||||
def active_dictionary(cls) -> tool.Blender.BLENDER_ENUM_ITEMS:
|
||||
results = [("ALL", "All Dictionaries", "All active dictionaries")]
|
||||
results.extend([(d.uri, d.name, f"{d.status} - {d.version}") for d in props.dictionaries if d.is_active])
|
||||
results.extend(tool.Bsdd.get_active_bsdd_enum_items())
|
||||
return results
|
||||
|
||||
@@ -42,6 +42,8 @@ def get_active_dictionary(self: "BIMBSDDProperties", context: object) -> tool.Bl
|
||||
|
||||
|
||||
def update_is_active(self: "BSDDDictionary", context: bpy.types.Context) -> None:
|
||||
tool.Bsdd.save_active_bsdd_to_ifc()
|
||||
|
||||
BSDDData.data["active_dictionary"] = BSDDData.active_dictionary()
|
||||
if ClassificationsData.is_loaded:
|
||||
props = tool.Classification.get_classification_props()
|
||||
@@ -158,15 +160,6 @@ class BIMBSDDProperties(PropertyGroup):
|
||||
description="Whether to display and assign only properties from IFC dictionary",
|
||||
default=False,
|
||||
)
|
||||
load_preview_dictionaries: BoolProperty(
|
||||
name="Load Preview Dictionaries", description="Load dictionaries marked as Preview status", default=False
|
||||
)
|
||||
load_inactive_dictionaries: BoolProperty(
|
||||
name="Load Inactive Dictionaries", description="Load dictionaries marked as Inactive status", default=False
|
||||
)
|
||||
load_test_dictionaries: BoolProperty(
|
||||
name="Load Test Dictionaries", description="Load dictionaries that are for testing only", default=False
|
||||
)
|
||||
classification_psets: CollectionProperty(name="Classification Psets", type=BSDDPset)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -186,9 +179,6 @@ class BIMBSDDProperties(PropertyGroup):
|
||||
keyword: str
|
||||
should_filter_ifc_class: bool
|
||||
use_only_ifc_properties: bool
|
||||
load_preview_dictionaries: bool
|
||||
load_inactive_dictionaries: bool
|
||||
load_test_dictionaries: bool
|
||||
classification_psets: bpy.types.bpy_prop_collection_idprop[BSDDPset]
|
||||
|
||||
@property
|
||||
|
||||
@@ -33,7 +33,7 @@ def refresh():
|
||||
|
||||
|
||||
class ClassificationsData:
|
||||
data = {}
|
||||
data: dict[str, Any] = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
@@ -65,13 +65,12 @@ class ClassificationsData:
|
||||
return [(str(e.id()), e.Name, "") for e in IfcStore.classification_file.by_type("IfcClassification")]
|
||||
|
||||
@classmethod
|
||||
def classification_source(cls):
|
||||
def classification_source(cls) -> tool.Blender.BLENDER_ENUM_ITEMS:
|
||||
items = [
|
||||
("FILE", "IFC File", ""),
|
||||
("MANUAL", "Manual Entry", ""),
|
||||
]
|
||||
bprops = tool.Bsdd.get_bsdd_props()
|
||||
dictionaries = [(d.uri, f"bSDD: {d.name}", "") for d in bprops.dictionaries if d.is_active]
|
||||
dictionaries = tool.Bsdd.get_active_bsdd_enum_items()
|
||||
if dictionaries:
|
||||
items.append(("BSDD", "All Active bSDDs", ""))
|
||||
items.extend(dictionaries)
|
||||
@@ -94,7 +93,7 @@ class ReferencesData:
|
||||
|
||||
|
||||
class ClassificationReferencesData(ReferencesData):
|
||||
data = {}
|
||||
data: dict[str, Any] = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
@@ -118,7 +117,7 @@ class ClassificationReferencesData(ReferencesData):
|
||||
|
||||
|
||||
class MaterialClassificationsData(ReferencesData):
|
||||
data = {}
|
||||
data: dict[str, Any] = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
@@ -134,8 +133,7 @@ class MaterialClassificationsData(ReferencesData):
|
||||
results = []
|
||||
|
||||
props = tool.Material.get_material_props()
|
||||
if props.materials and props.active_material_index < len(props.materials):
|
||||
material = props.materials[props.active_material_index]
|
||||
if material := props.active_material:
|
||||
if material.ifc_definition_id:
|
||||
element = tool.Ifc.get().by_id(material.ifc_definition_id)
|
||||
for reference in ifcopenshell.util.classification.get_references(element):
|
||||
@@ -146,7 +144,7 @@ class MaterialClassificationsData(ReferencesData):
|
||||
|
||||
|
||||
class CostClassificationsData(ReferencesData):
|
||||
data = {}
|
||||
data: dict[str, Any] = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -146,7 +146,7 @@ class EnableAddingManualClassification(bpy.types.Operator):
|
||||
props.is_adding = True
|
||||
props.active_classification_id = 0
|
||||
props.classification_attributes.clear()
|
||||
bonsai.bim.helper.import_attributes2("IfcClassification", props.classification_attributes)
|
||||
bonsai.bim.helper.import_attributes("IfcClassification", props.classification_attributes)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -170,7 +170,7 @@ class EnableAddingManualClassificationReference(bpy.types.Operator):
|
||||
props = tool.Classification.get_classification_reference_props()
|
||||
props.is_adding = True
|
||||
props.reference_attributes.clear()
|
||||
bonsai.bim.helper.import_attributes2("IfcClassificationReference", props.reference_attributes)
|
||||
bonsai.bim.helper.import_attributes("IfcClassificationReference", props.reference_attributes)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -205,7 +205,7 @@ class EnableEditingClassification(bpy.types.Operator):
|
||||
|
||||
props = tool.Classification.get_classification_props()
|
||||
props.classification_attributes.clear()
|
||||
bonsai.bim.helper.import_attributes2(
|
||||
bonsai.bim.helper.import_attributes(
|
||||
tool.Ifc.get().by_id(self.classification), props.classification_attributes, callback
|
||||
)
|
||||
props.active_classification_id = self.classification
|
||||
@@ -274,7 +274,7 @@ class EnableEditingClassificationReference(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
props = tool.Classification.get_classification_reference_props()
|
||||
props.reference_attributes.clear()
|
||||
bonsai.bim.helper.import_attributes2(tool.Ifc.get().by_id(self.reference), props.reference_attributes)
|
||||
bonsai.bim.helper.import_attributes(tool.Ifc.get().by_id(self.reference), props.reference_attributes)
|
||||
props.active_reference_id = self.reference
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ from bonsai.bim.module.classification.data import (
|
||||
MaterialClassificationsData,
|
||||
CostClassificationsData,
|
||||
)
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, Any, Union
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bonsai.bim.module.classification.prop import BIMClassificationProperties, ClassificationReference
|
||||
@@ -113,9 +113,15 @@ class BIM_PT_classifications(Panel):
|
||||
|
||||
class ReferenceUI:
|
||||
layout: bpy.types.UILayout
|
||||
data: type[
|
||||
Union[
|
||||
ClassificationReferencesData,
|
||||
MaterialClassificationsData,
|
||||
CostClassificationsData,
|
||||
]
|
||||
]
|
||||
|
||||
def draw_ui(self, context):
|
||||
obj = context.active_object
|
||||
def draw_ui(self, context) -> None:
|
||||
self.sprops = tool.Classification.get_classification_props()
|
||||
self.bprops = tool.Bsdd.get_bsdd_props()
|
||||
self.props = tool.Classification.get_classification_reference_props()
|
||||
@@ -133,7 +139,7 @@ class ReferenceUI:
|
||||
else:
|
||||
self.draw_reference_ui(reference)
|
||||
|
||||
def draw_add_ui(self, context):
|
||||
def draw_add_ui(self, context) -> None:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Source", icon="OUTLINER")
|
||||
row.prop(self.sprops, "classification_source", text="")
|
||||
@@ -145,7 +151,7 @@ class ReferenceUI:
|
||||
else:
|
||||
self.draw_add_bsdd_ui(context)
|
||||
|
||||
def draw_add_manual_ui(self, context):
|
||||
def draw_add_manual_ui(self, context) -> None:
|
||||
row = self.layout.row()
|
||||
row.prop(self.props, "classifications", text="")
|
||||
if self.props.is_adding:
|
||||
@@ -158,7 +164,7 @@ class ReferenceUI:
|
||||
row = self.layout.row()
|
||||
row.operator("bim.enable_adding_manual_classification_reference", text="Add Reference", icon="ADD")
|
||||
|
||||
def draw_add_bsdd_ui(self, context):
|
||||
def draw_add_bsdd_ui(self, context) -> None:
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.bprops, "keyword", text="")
|
||||
row.prop(self.bprops, "should_filter_ifc_class", text="", icon="FILTER")
|
||||
@@ -185,7 +191,7 @@ class ReferenceUI:
|
||||
op.obj = self.obj
|
||||
op.obj_type = self.obj_type
|
||||
|
||||
def draw_add_file_ui(self, context):
|
||||
def draw_add_file_ui(self, context) -> None:
|
||||
if not self.data.data["active_classification_library"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="No Active Classification Library", icon="ERROR")
|
||||
@@ -219,13 +225,13 @@ class ReferenceUI:
|
||||
"active_library_reference_index",
|
||||
)
|
||||
|
||||
def draw_editable_ui(self):
|
||||
def draw_editable_ui(self) -> None:
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.edit_classification_reference", text="Save changes", icon="CHECKMARK")
|
||||
row.operator("bim.disable_editing_classification_reference", text="", icon="CANCEL")
|
||||
bonsai.bim.helper.draw_attributes(self.props.reference_attributes, self.layout)
|
||||
|
||||
def draw_reference_ui(self, reference):
|
||||
def draw_reference_ui(self, reference: dict[str, Any]) -> None:
|
||||
row = self.layout.row(align=True)
|
||||
if self.file.schema == "IFC2X3":
|
||||
name = reference["ItemReference"] or "No Identification"
|
||||
|
||||
@@ -81,7 +81,7 @@ class EnableEditingConstraint(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
props = tool.Blender.get_constraint_props()
|
||||
props.constraint_attributes.clear()
|
||||
bonsai.bim.helper.import_attributes2(tool.Ifc.get().by_id(self.constraint), props.constraint_attributes)
|
||||
bonsai.bim.helper.import_attributes(tool.Ifc.get().by_id(self.constraint), props.constraint_attributes)
|
||||
props.active_constraint_id = self.constraint
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -369,13 +369,13 @@ class CostItemQuantitiesData:
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def product_quantity_names(cls):
|
||||
elements = tool.Spatial.get_selected_products()
|
||||
def product_quantity_names(cls) -> tool.Blender.BLENDER_ENUM_ITEMS:
|
||||
elements = list(tool.Spatial.get_selected_products())
|
||||
names = ifcopenshell.util.cost.get_product_quantity_names(elements)
|
||||
return [(n, n, "") for n in names]
|
||||
|
||||
@classmethod
|
||||
def process_quantity_names(cls):
|
||||
def process_quantity_names(cls) -> tool.Blender.BLENDER_ENUM_ITEMS:
|
||||
props = tool.Sequence.get_work_schedule_props()
|
||||
active_task_index = props.active_task_index
|
||||
tprops = tool.Sequence.get_task_tree_props()
|
||||
@@ -391,14 +391,11 @@ class CostItemQuantitiesData:
|
||||
return [(n, n, "") for n in names if n != "id"]
|
||||
|
||||
@classmethod
|
||||
def resource_quantity_names(cls):
|
||||
active_resource_index = bpy.context.scene.BIMResourceProperties.active_resource_index
|
||||
total_resources = len(bpy.context.scene.BIMResourceTreeProperties.resources)
|
||||
if not total_resources or active_resource_index >= total_resources:
|
||||
def resource_quantity_names(cls) -> tool.Blender.BLENDER_ENUM_ITEMS:
|
||||
active_resource = tool.Resource.get_resource_props().active_resource
|
||||
if not active_resource:
|
||||
return []
|
||||
ifc_definition_id = bpy.context.scene.BIMResourceTreeProperties.resources[
|
||||
active_resource_index
|
||||
].ifc_definition_id
|
||||
ifc_definition_id = active_resource.ifc_definition_id
|
||||
element = tool.Ifc.get().by_id(ifc_definition_id)
|
||||
names = set()
|
||||
qtos = ifcopenshell.util.element.get_psets(element, qtos_only=True)
|
||||
|
||||
@@ -871,11 +871,82 @@ class ExportCostSchedulesToPDF(bpy.types.Operator, ExportHelper):
|
||||
items=get_cost_schedules_enum_items,
|
||||
)
|
||||
|
||||
nested_structure_depth: bpy.props.IntProperty(
|
||||
name="Nested structure depth: ",
|
||||
description="Define till which level of the structure the parent cost items are displayed.\n0: display the full structure.",
|
||||
default=0,
|
||||
min=0,
|
||||
max=9,
|
||||
)
|
||||
parent_to_new_page_up_to_depth: bpy.props.IntProperty(
|
||||
name="Parents to new page up to depth: ",
|
||||
description="Define till which level of the structure the parent is printed to a new page.\n0: no parent is split to a new page.",
|
||||
default=0,
|
||||
min=0,
|
||||
max=9,
|
||||
)
|
||||
show_only_parents: bpy.props.BoolProperty(
|
||||
name="Show only parent cost items",
|
||||
description="Hide cost items and show only container costs",
|
||||
default=False,
|
||||
)
|
||||
should_print_cover: bpy.props.BoolProperty(
|
||||
name="Cover page",
|
||||
description="Create a cover page with project data",
|
||||
default=False,
|
||||
)
|
||||
should_print_description: bpy.props.BoolProperty(
|
||||
name="Full Cost Items Description",
|
||||
description="Export the full description if present",
|
||||
default=True,
|
||||
)
|
||||
should_print_cost_ids: bpy.props.BoolProperty(
|
||||
name="Print Cost Identification",
|
||||
description="Print Cost Identification under Cost Name if present",
|
||||
default=True,
|
||||
)
|
||||
should_print_each_quantity: bpy.props.BoolProperty(
|
||||
name="Show each quantity",
|
||||
description="Export the full list of quantities",
|
||||
default=False,
|
||||
)
|
||||
should_print_each_cost_value: bpy.props.BoolProperty(
|
||||
name="Show each cost value",
|
||||
description="Export the full list of cost values\nassociated with each cost item\nin the schedule of rates",
|
||||
default=False,
|
||||
)
|
||||
should_print_rates: bpy.props.BoolProperty(
|
||||
name="Rates and totals",
|
||||
description="Print rates and totals for each voice",
|
||||
default=True,
|
||||
)
|
||||
should_print_summary: bpy.props.BoolProperty(
|
||||
name="Should print summary",
|
||||
name="Final Summary",
|
||||
description="Print summary at the end of the document",
|
||||
default=True,
|
||||
)
|
||||
force_schedule_type: bpy.props.EnumProperty(
|
||||
name="Force output type",
|
||||
description="Force the output to this type\nalso if it is not coincident with the cost schedule Predefined Type",
|
||||
items=[
|
||||
(
|
||||
"OFF",
|
||||
"Off",
|
||||
"Uses Cost Schedule Predefined Type",
|
||||
),
|
||||
(
|
||||
"PRICEDBILLOFQUANTITIES",
|
||||
"Priced Bill of Quantities",
|
||||
"Forces the output as a priced bill of quantities",
|
||||
),
|
||||
(
|
||||
"SCHEDULEOFRATES",
|
||||
"Schedule of Rates",
|
||||
"Forces the output as a schedule of rates",
|
||||
),
|
||||
],
|
||||
default="OFF",
|
||||
)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
@@ -884,8 +955,18 @@ class ExportCostSchedulesToPDF(bpy.types.Operator, ExportHelper):
|
||||
box.prop(self, "cost_schedules_enum", text="")
|
||||
layout.separator()
|
||||
box = layout.box()
|
||||
box.label(text="Export properties:")
|
||||
box.label(text="Nested cost structure:")
|
||||
box.prop(self, "nested_structure_depth")
|
||||
layout.separator()
|
||||
box = layout.box()
|
||||
box.label(text="PDF Document properties:")
|
||||
box.prop(self, "should_print_cover")
|
||||
box.prop(self, "should_print_cost_ids")
|
||||
box.prop(self, "should_print_description")
|
||||
box.prop(self, "should_print_each_quantity")
|
||||
box.prop(self, "should_print_rates")
|
||||
box.prop(self, "should_print_summary")
|
||||
box.prop(self, "force_schedule_type")
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -920,9 +1001,25 @@ class ExportCostSchedulesToPDF(bpy.types.Operator, ExportHelper):
|
||||
file = tool.Ifc.get()
|
||||
self.props = tool.Cost.get_cost_props()
|
||||
cost_schedule = file.by_id(int(self.cost_schedules_enum))
|
||||
options = {"should_print_summary": self.should_print_summary}
|
||||
options = {
|
||||
"nested_structure_depth": self.nested_structure_depth,
|
||||
"parent_to_new_page_up_to_depth": self.parent_to_new_page_up_to_depth,
|
||||
"show_only_parents": self.show_only_parents,
|
||||
"should_print_cover": self.should_print_cover,
|
||||
"should_print_cost_ids": self.should_print_cost_ids,
|
||||
"should_print_description": self.should_print_description,
|
||||
"should_print_each_quantity": self.should_print_each_quantity,
|
||||
"should_print_each_cost_value": self.should_print_each_cost_value,
|
||||
"should_print_rates": self.should_print_rates,
|
||||
"should_print_summary": self.should_print_summary,
|
||||
}
|
||||
|
||||
core.export_cost_schedules_to_pdf(
|
||||
tool.Cost, filepath=self.filepath, cost_schedule=cost_schedule, options=options
|
||||
tool.Cost,
|
||||
filepath=self.filepath,
|
||||
cost_schedule=cost_schedule,
|
||||
options=options,
|
||||
force_schedule_type=self.force_schedule_type,
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -393,7 +393,7 @@ class BIM_PT_cost_item_types(Panel):
|
||||
op.cost_item = cost_item.ifc_definition_id
|
||||
|
||||
rtprops = context.scene.BIMResourceTreeProperties
|
||||
rprops = context.scene.BIMResourceProperties
|
||||
rprops = tool.Resource.get_resource_props()
|
||||
if rtprops.resources and rprops.active_resource_index < len(rtprops.resources):
|
||||
if has_quantity_names:
|
||||
op = row2.operator("bim.assign_cost_item_quantity", text="", icon="PROPERTIES")
|
||||
@@ -552,9 +552,8 @@ class BIM_PT_cost_item_quantities(Panel):
|
||||
op = row2.operator("bim.calculate_cost_item_resource_value", text="", icon="DISC")
|
||||
op.cost_item = cost_item.ifc_definition_id
|
||||
|
||||
rtprops = context.scene.BIMResourceTreeProperties
|
||||
rprops = context.scene.BIMResourceProperties
|
||||
if rtprops.resources and rprops.active_resource_index < len(rtprops.resources):
|
||||
active_resource = tool.Resource.get_resource_props().active_resource
|
||||
if active_resource:
|
||||
if has_quantity_names:
|
||||
op = row2.operator("bim.assign_cost_item_quantity", text="", icon="PROPERTIES")
|
||||
op.related_object_type = "RESOURCE"
|
||||
|
||||
@@ -20,6 +20,7 @@ import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.ChangeLogLevel,
|
||||
operator.ConvertToBlender,
|
||||
operator.CopyDebugInformation,
|
||||
operator.CreateAllShapes,
|
||||
|
||||
@@ -183,6 +183,9 @@ class ValidateIfcAssets(bpy.types.Operator):
|
||||
|
||||
ifc_classes = {
|
||||
"IfcMaterial": "Name",
|
||||
"IfcMaterialLayerSet": "LayerSetName",
|
||||
"IfcMaterialConstituentSet": "Name",
|
||||
"IfcMaterialProfileSet": "Name",
|
||||
"IfcProfileDef": "ProfileName",
|
||||
"IfcPresentationStyle": "Name",
|
||||
}
|
||||
@@ -239,7 +242,7 @@ class ProfileImportIFC(bpy.types.Operator):
|
||||
import cProfile
|
||||
import pstats
|
||||
|
||||
profile_file = Path(profile_filename)
|
||||
profile_file = Path(self.profile_filename)
|
||||
cProfile.run("import bpy; bpy.ops.bim.load_project_elements()", str(profile_file))
|
||||
p = pstats.Stats(str(profile_file))
|
||||
p.sort_stats("cumulative").print_stats(50)
|
||||
@@ -251,6 +254,21 @@ class CreateAllShapes(bpy.types.Operator):
|
||||
bl_idname = "bim.create_all_shapes"
|
||||
bl_label = "Test All Shapes"
|
||||
bl_description = "Look for errors in all the shapes contained in the file"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
geometry_library: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
|
||||
name="Geometry Library",
|
||||
items=[(i, i, "") for i in get_args(ifcopenshell.geom.GEOMETRY_LIBRARY)],
|
||||
default="opencascade",
|
||||
)
|
||||
custom_geometry_library: bpy.props.StringProperty( # pyright: ignore[reportRedeclaration]
|
||||
name="Custom Geometry Library",
|
||||
description="Provide a custom geometry library name, will override the 'geometry library' property.",
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
geometry_library: ifcopenshell.geom.GEOMETRY_LIBRARY
|
||||
custom_geometry_library: str
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -258,6 +276,7 @@ class CreateAllShapes(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
self.file = tool.Ifc.get()
|
||||
geometry_library = self.custom_geometry_library or self.geometry_library
|
||||
elements = self.file.by_type("IfcElement") + self.file.by_type("IfcSpace")
|
||||
|
||||
total = len(elements)
|
||||
@@ -276,10 +295,10 @@ class CreateAllShapes(bpy.types.Operator):
|
||||
start = time.time()
|
||||
shape = None
|
||||
try:
|
||||
shape = ifcopenshell.geom.create_shape(settings, element)
|
||||
shape = ifcopenshell.geom.create_shape(settings, element, geometry_library=geometry_library)
|
||||
except:
|
||||
try:
|
||||
shape = ifcopenshell.geom.create_shape(settings_2d, element)
|
||||
shape = ifcopenshell.geom.create_shape(settings_2d, element, geometry_library=geometry_library)
|
||||
except:
|
||||
failures.append(element)
|
||||
print("***** FAILURE *****")
|
||||
@@ -302,6 +321,7 @@ class CreateShapeFromStepId(bpy.types.Operator):
|
||||
bl_label = "Create Shape From STEP ID"
|
||||
bl_description = "Recreate a mesh object from a STEP ID"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
should_include_curves: bpy.props.BoolProperty()
|
||||
step_id: bpy.props.IntProperty(default=0)
|
||||
geometry_library: bpy.props.EnumProperty(
|
||||
@@ -314,6 +334,12 @@ class CreateShapeFromStepId(bpy.types.Operator):
|
||||
description="Provide a custom geometry library name, will override the 'geometry library' property.",
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
should_include_curves: bool
|
||||
step_id: int
|
||||
geometry_library: ifcopenshell.geom.GEOMETRY_LIBRARY
|
||||
custom_geometry_library: str
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if not tool.Ifc.get():
|
||||
@@ -973,7 +999,7 @@ class DebugActiveDrawing(bpy.types.Operator):
|
||||
class ToggleDetailedIOSLogs(bpy.types.Operator):
|
||||
bl_idname = "bim.toggle_detailed_ios_logs"
|
||||
bl_label = "Toggle Detailed IfcOpenShell Logs"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_options = {"REGISTER"}
|
||||
bl_description = (
|
||||
"Turn on detailed IfcOpenShell logs in the system console.\n"
|
||||
+ "Could be useful debugging issues "
|
||||
@@ -1003,6 +1029,31 @@ class ToggleDetailedIOSLogs(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
LogLevelType = Literal["NOTSET", "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
|
||||
|
||||
|
||||
class ChangeLogLevel(bpy.types.Operator):
|
||||
bl_idname = "bim.change_log_level"
|
||||
bl_label = "Change Log Level "
|
||||
bl_options = {"REGISTER"}
|
||||
bl_description = "Change general log level across all Python code in Blender"
|
||||
|
||||
log_level: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
|
||||
name="Log Level",
|
||||
items=((i, i, "") for i in get_args(LogLevelType)),
|
||||
default="WARNING",
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
log_level: LogLevelType
|
||||
|
||||
def execute(self, context):
|
||||
root = logging.getLogger()
|
||||
root.setLevel(self.log_level)
|
||||
self.report({"INFO"}, f"Log level changed to {self.log_level}.")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RestartBlender(bpy.types.Operator):
|
||||
bl_idname = "bim.restart_blender"
|
||||
bl_label = "Restart Blender"
|
||||
|
||||
@@ -33,6 +33,7 @@ class BIM_PT_debug(Panel):
|
||||
def draw(self, context):
|
||||
assert self.layout
|
||||
layout = self.layout
|
||||
assert (wm := bpy.context.window_manager)
|
||||
|
||||
props = tool.Debug.get_debug_props()
|
||||
bim_props = tool.Blender.get_bim_props()
|
||||
@@ -83,6 +84,10 @@ class BIM_PT_debug(Panel):
|
||||
row = layout.row()
|
||||
row.operator("bim.toggle_detailed_ios_logs")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(wm.operator_properties_last(bpy.ops.bim.change_log_level.idname()), "log_level", text="")
|
||||
row.operator("bim.change_log_level")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.restart_blender")
|
||||
|
||||
|
||||
@@ -86,8 +86,8 @@ class SheetsData:
|
||||
project = tool.Ifc.get().by_type("IfcProject")[0]
|
||||
titleblocks_dir = ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "TitleblocksDir")
|
||||
if not titleblocks_dir:
|
||||
props = tool.Drawing.get_document_props()
|
||||
titleblocks_dir = props.titleblocks_dir
|
||||
prefs = tool.Blender.get_addon_preferences()
|
||||
titleblocks_dir = prefs.doc.titleblocks_dir
|
||||
titleblocks_dir = tool.Ifc.resolve_uri(titleblocks_dir)
|
||||
if os.path.exists(titleblocks_dir):
|
||||
files.extend([str(f.stem) for f in Path(titleblocks_dir).glob("*.svg")])
|
||||
|
||||
@@ -426,8 +426,8 @@ class BaseDecorator:
|
||||
# font_size = 16 <-- this is a good default
|
||||
# TODO: need to synchronize it better with svg
|
||||
|
||||
props = tool.Drawing.get_document_props()
|
||||
magic_font_scale = props.magic_font_scale
|
||||
prefs = tool.Blender.get_addon_preferences()
|
||||
magic_font_scale = prefs.doc.magic_font_scale
|
||||
font_size_px = int(magic_font_scale * mm_to_px) * font_size_mm / 2.5
|
||||
pos = pos - line_no * font_size_px * rotation_matrix[1]
|
||||
|
||||
@@ -1661,7 +1661,7 @@ class CutDecorator:
|
||||
all_vertex_i_offset = 0
|
||||
selected_vertex_i_offset = 0
|
||||
|
||||
classes_no_cut_str = tool.Drawing.get_document_props().classes_no_cut
|
||||
classes_no_cut_str = self.addon_prefs.doc.classes_no_cut
|
||||
classes_no_cut = [word.strip() for word in classes_no_cut_str.split(",")]
|
||||
|
||||
for obj in [o for o in bpy.context.visible_objects if o.type == "MESH"]:
|
||||
@@ -1959,8 +1959,8 @@ 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"]
|
||||
props = tool.Drawing.get_document_props()
|
||||
if drawing_font := props.drawing_font:
|
||||
prefs = tool.Blender.get_addon_preferences()
|
||||
if drawing_font := prefs.doc.drawing_font:
|
||||
drawing_font_path = tool.Blender.get_data_dir_path(Path("fonts") / drawing_font)
|
||||
if drawing_font_path.is_file():
|
||||
font_id = blf.load(drawing_font_path.__str__())
|
||||
|
||||
@@ -334,7 +334,15 @@ class CreateDrawing(bpy.types.Operator):
|
||||
annotation_svg = None
|
||||
|
||||
with profile("Generate underlay"):
|
||||
underlay_svg = self.generate_underlay(context)
|
||||
if ifcopenshell.util.element.get_pset(self.drawing, "EPset_Drawing", "HasUnderlay"):
|
||||
drawing_style = self.cprops.get_active_drawing_style()
|
||||
if not drawing_style:
|
||||
self.report(
|
||||
{"ERROR"},
|
||||
f"Failed to create drawing '{self.drawing.Name}' - drawing has underlay but there's no active drawing underlay style.",
|
||||
)
|
||||
return {"FINISHED"}
|
||||
underlay_svg = self.generate_underlay(context)
|
||||
|
||||
with profile("Generate linework"):
|
||||
if tool.Drawing.is_camera_orthographic():
|
||||
@@ -874,6 +882,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
cached_linework -= edited_guids
|
||||
|
||||
bim_props = tool.Blender.get_bim_props()
|
||||
prefs = tool.Blender.get_addon_preferences()
|
||||
files = {bim_props.ifc_file: tool.Ifc.get()}
|
||||
|
||||
props = tool.Project.get_project_props()
|
||||
@@ -890,7 +899,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
# Don't use draw.main() just whilst we're prototyping and experimenting
|
||||
# TODO: hash paths are never used
|
||||
ifc_hash = hashlib.md5(ifc_path.encode("utf-8")).hexdigest()
|
||||
ifc_cache_path = os.path.join(bim_props.cache_dir, f"{ifc_hash}.h5")
|
||||
ifc_cache_path = os.path.join(prefs.cache_dir, f"{ifc_hash}.h5")
|
||||
|
||||
self.serialiser.setFile(ifc)
|
||||
drawing_elements = tool.Drawing.get_drawing_elements(self.camera_element, ifc_file=ifc)
|
||||
@@ -1848,8 +1857,8 @@ class AddDrawingToSheet(bpy.types.Operator, tool.Ifc.Operator):
|
||||
def poll(cls, context):
|
||||
props = tool.Drawing.get_document_props()
|
||||
# Won't be visible in UI anyway.
|
||||
bim_props = tool.Blender.get_bim_props()
|
||||
if not props.sheets or not bim_props.data_dir:
|
||||
prefs = tool.Blender.get_addon_preferences()
|
||||
if not props.sheets or not prefs.data_dir:
|
||||
return False
|
||||
if not tool.Drawing.get_active_drawing_item():
|
||||
cls.poll_message_set("No drawing selected.")
|
||||
@@ -1954,8 +1963,8 @@ class CreateSheets(bpy.types.Operator, tool.Ifc.Operator):
|
||||
if not tool.Drawing.get_active_sheet_item(is_sheet=True):
|
||||
cls.poll_message_set("No sheet selected.")
|
||||
return False
|
||||
bim_props = tool.Blender.get_bim_props()
|
||||
return props.sheets and bim_props.data_dir
|
||||
prefs = tool.Blender.get_addon_preferences()
|
||||
return props.sheets and prefs.data_dir
|
||||
|
||||
def invoke(self, context, event):
|
||||
# opening all sheets on shift+click
|
||||
@@ -2741,8 +2750,8 @@ class AddScheduleToSheet(bpy.types.Operator, tool.Ifc.Operator):
|
||||
if not props.schedules:
|
||||
cls.poll_message_set("No schedule selected.")
|
||||
return False
|
||||
bim_props = tool.Blender.get_bim_props()
|
||||
return props.schedules and props.sheets and bim_props.data_dir
|
||||
prefs = tool.Blender.get_addon_preferences()
|
||||
return props.schedules and props.sheets and prefs.data_dir
|
||||
|
||||
def _execute(self, context):
|
||||
props = tool.Drawing.get_document_props()
|
||||
@@ -3045,7 +3054,7 @@ class AddTextLiteral(bpy.types.Operator):
|
||||
"Path": "RIGHT",
|
||||
"BoxAlignment": "bottom_left",
|
||||
}
|
||||
# emulates `bonsai.bim.helper.import_attributes2(ifc_literal, literal_props.attributes)`
|
||||
# emulates `bonsai.bim.helper.import_attributes(ifc_literal, literal_props.attributes)`
|
||||
for attr_name in literal_attr_values:
|
||||
attr = literal_attributes.add()
|
||||
attr.name = attr_name
|
||||
|
||||
@@ -423,39 +423,6 @@ class DocProperties(PropertyGroup):
|
||||
active_sheet_index: IntProperty(name="Active Sheet Index")
|
||||
drawing_styles: CollectionProperty(name="Drawing Styles", type=DrawingStyle)
|
||||
should_draw_decorations: BoolProperty(name="Should Draw Decorations", update=update_should_draw_decorations)
|
||||
sheets_dir: StringProperty(default=os.path.join("sheets") + os.path.sep, name="Default Sheets Directory")
|
||||
layouts_dir: StringProperty(default=os.path.join("layouts") + os.path.sep, name="Default Layouts Directory")
|
||||
titleblocks_dir: StringProperty(
|
||||
default=os.path.join("layouts", "titleblocks") + os.path.sep, name="Default Titleblocks Directory"
|
||||
)
|
||||
drawings_dir: StringProperty(default=os.path.join("drawings") + os.path.sep, name="Default Drawings Directory")
|
||||
stylesheet_path: StringProperty(
|
||||
default=os.path.join("drawings", "assets", "default.css"), name="Default Stylesheet"
|
||||
)
|
||||
schedules_stylesheet_path: StringProperty(
|
||||
default=os.path.join("drawings", "assets", "schedule.css"), name="Default Stylesheet for Schedules"
|
||||
)
|
||||
markers_path: StringProperty(default=os.path.join("drawings", "assets", "markers.svg"), name="Default Markers")
|
||||
symbols_path: StringProperty(default=os.path.join("drawings", "assets", "symbols.svg"), name="Default Symbols")
|
||||
patterns_path: StringProperty(default=os.path.join("drawings", "assets", "patterns.svg"), name="Default Patterns")
|
||||
shadingstyles_path: StringProperty(
|
||||
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")
|
||||
magic_font_scale: bpy.props.FloatProperty(default=0.004118616, name="Font Scale Factor")
|
||||
imperial_precision: StringProperty(default="1/32", name="Imperial Precision")
|
||||
tolerance: bpy.props.FloatProperty(default=0.00001, name="A tolerance used when selecting objects")
|
||||
classes_to_wireframe: StringProperty(
|
||||
default="IfcVirtualElement",
|
||||
name="Classes to Wireframe",
|
||||
description="Upon import, these classes will display as wireframe.\nEx: IfcVirtualelement, IfcSpace",
|
||||
)
|
||||
classes_no_cut: StringProperty(
|
||||
default="IfcVirtualElement, IfcSpace",
|
||||
name="Classes that are not cut",
|
||||
description="The cut decoractor will be turned off for these classes\nEx: IfcVirtualelement, IfcSpace",
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
should_use_underlay_cache: bool
|
||||
@@ -480,23 +447,6 @@ class DocProperties(PropertyGroup):
|
||||
active_sheet_index: int
|
||||
drawing_styles: bpy.types.bpy_prop_collection_idprop[DrawingStyle]
|
||||
should_draw_decorations: bool
|
||||
sheets_dir: str
|
||||
layouts_dir: str
|
||||
titleblocks_dir: str
|
||||
drawings_dir: str
|
||||
stylesheet_path: str
|
||||
schedules_stylesheet_path: str
|
||||
markers_path: str
|
||||
symbols_path: str
|
||||
patterns_path: str
|
||||
shadingstyles_path: str
|
||||
shadingstyle_default: str
|
||||
drawing_font: str
|
||||
magic_font_scale: float
|
||||
imperial_precision: str
|
||||
tolerance: float
|
||||
classes_to_wireframe: str
|
||||
classes_no_cut: str
|
||||
|
||||
def get_active_drawing(self) -> Union[ifcopenshell.entity_instance, None]:
|
||||
drawing_id = self.active_drawing_id
|
||||
|
||||
@@ -74,10 +74,11 @@ class Scheduler:
|
||||
self.schedule_xlsx(infile, outfile)
|
||||
|
||||
def parse_css(self, infile: str) -> None:
|
||||
props = tool.Drawing.get_document_props()
|
||||
prefs = tool.Blender.get_addon_preferences()
|
||||
|
||||
stylesheet_path = os.path.splitext(infile)[0] + ".css"
|
||||
if not os.path.exists(stylesheet_path):
|
||||
stylesheet_rel_path = props.schedules_stylesheet_path
|
||||
stylesheet_rel_path = prefs.doc.schedules_stylesheet_path
|
||||
ifc_file_path = os.path.dirname(tool.Ifc.get_path())
|
||||
stylesheet_path = ifc_file_path + "\\" + stylesheet_rel_path
|
||||
if not os.path.exists(stylesheet_path):
|
||||
|
||||
@@ -1315,6 +1315,10 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator):
|
||||
if location_from_3d_cursor:
|
||||
get_location_from_3d_cursor(old_to_new, selected_element)
|
||||
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
new_aggregate = old_to_new[selected_element][0]
|
||||
tool.Blender.set_active_object(tool.Ifc.get_object(new_aggregate))
|
||||
|
||||
bonsai.bim.handler.refresh_ui_data()
|
||||
|
||||
return old_to_new
|
||||
|
||||
@@ -31,6 +31,8 @@ class LoadGroups(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_label = "Load Groups"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
expanded_groups: list[int]
|
||||
|
||||
def _execute(self, context):
|
||||
self.props = tool.Blender.get_group_props()
|
||||
self.expanded_groups = json.loads(self.props.expanded_groups_json)
|
||||
@@ -82,6 +84,7 @@ class ToggleGroup(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
def _execute(self, context):
|
||||
props = tool.Blender.get_group_props()
|
||||
expanded_groups: set[int]
|
||||
expanded_groups = set(json.loads(props.expanded_groups_json))
|
||||
if self.option == "Expand":
|
||||
expanded_groups.add(self.ifc_definition_id)
|
||||
@@ -162,7 +165,7 @@ class EnableEditingGroup(bpy.types.Operator, tool.Ifc.Operator):
|
||||
def _execute(self, context):
|
||||
props = tool.Blender.get_group_props()
|
||||
props.group_attributes.clear()
|
||||
bonsai.bim.helper.import_attributes2(tool.Ifc.get().by_id(self.group), props.group_attributes)
|
||||
bonsai.bim.helper.import_attributes(tool.Ifc.get().by_id(self.group), props.group_attributes)
|
||||
props.active_group_id = self.group
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -70,6 +70,7 @@ class BIMGroupProperties(PropertyGroup):
|
||||
active_group_index: IntProperty(name="Active Group Index", update=update_active_group_index)
|
||||
active_group_id: IntProperty(name="Active Group Id")
|
||||
expanded_groups_json: StringProperty(name="JSON String", default="[]")
|
||||
"""JSON serialized list[group_id]."""
|
||||
|
||||
if TYPE_CHECKING:
|
||||
group_attributes: bpy.types.bpy_prop_collection_idprop[Attribute]
|
||||
|
||||
@@ -82,7 +82,7 @@ class EnableEditingLayer(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
props = tool.Layer.get_layer_props()
|
||||
props.layer_attributes.clear()
|
||||
bonsai.bim.helper.import_attributes2(tool.Ifc.get().by_id(self.layer), props.layer_attributes)
|
||||
bonsai.bim.helper.import_attributes(tool.Ifc.get().by_id(self.layer), props.layer_attributes)
|
||||
props.active_layer_id = self.layer
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -25,20 +25,7 @@ from pathlib import Path
|
||||
import importlib
|
||||
import importlib.util
|
||||
import traceback
|
||||
|
||||
# There are unresolved problems with pyradiance import that we couldn't narrow down.
|
||||
# So import is optional not to make it a showstopper for random users,
|
||||
# who don't even need pyradiance.
|
||||
# See #6633.
|
||||
try:
|
||||
import pyradiance
|
||||
except ImportError as e:
|
||||
print(traceback.format_exc())
|
||||
print(
|
||||
"PyRadiance is not available. Pyradiance Rendering functionality will be disabled. "
|
||||
"See above for more detailed import traceback."
|
||||
)
|
||||
pyradiance = None
|
||||
import pyradiance
|
||||
|
||||
|
||||
def get_pyradiance_path():
|
||||
|
||||
@@ -18,11 +18,7 @@
|
||||
|
||||
import os
|
||||
|
||||
try:
|
||||
import pyradiance as pr
|
||||
except ImportError:
|
||||
pr = None
|
||||
|
||||
import pyradiance as pr
|
||||
from datetime import datetime
|
||||
import bpy
|
||||
import bonsai.tool as tool
|
||||
@@ -137,10 +133,6 @@ class RadianceRender(bpy.types.Operator):
|
||||
bl_description = "Renders the scene using Radiance"
|
||||
|
||||
def execute(self, context):
|
||||
if pr is None:
|
||||
self.report({"ERROR"}, "PyRadiance is not available. Cannot perform rendering.")
|
||||
return {"CANCELLED"}
|
||||
|
||||
print("Starting Radiance rendering process...")
|
||||
props = tool.Blender.get_radiance_exporter_props()
|
||||
resolution_x, resolution_y = props.radiance_resolution_x, props.radiance_resolution_y
|
||||
|
||||
@@ -21,11 +21,6 @@ import bonsai.tool as tool
|
||||
from typing import TYPE_CHECKING
|
||||
from bonsai.bim.module.light.data import SolarData
|
||||
|
||||
try:
|
||||
import pyradiance
|
||||
except ImportError:
|
||||
pyradiance = None
|
||||
|
||||
|
||||
class BIM_PT_radiance_exporter(bpy.types.Panel):
|
||||
"""Creates a Panel in the render properties window"""
|
||||
@@ -43,13 +38,6 @@ class BIM_PT_radiance_exporter(bpy.types.Panel):
|
||||
layout = self.layout
|
||||
props = tool.Blender.get_radiance_exporter_props()
|
||||
|
||||
if pyradiance is None:
|
||||
box = layout.box()
|
||||
box.label(text="There was a problem importing Pyradiance", icon="ERROR")
|
||||
box.label(text="and it's functionality is not available.")
|
||||
box.label(text="See system console logs at Bonsai startup.")
|
||||
return
|
||||
|
||||
if tool.Ifc.get():
|
||||
row = self.layout.row()
|
||||
row.prop(props, "should_load_from_memory")
|
||||
|
||||
@@ -293,9 +293,9 @@ class ObjectMaterialData:
|
||||
if item.is_a("IfcMaterialLayer"):
|
||||
total_thickness = item.LayerThickness
|
||||
unit_system = bpy.context.scene.unit_settings.system
|
||||
props = tool.Drawing.get_document_props()
|
||||
prefs = tool.Blender.get_addon_preferences()
|
||||
if unit_system == "IMPERIAL":
|
||||
precision = props.imperial_precision
|
||||
precision = prefs.doc.imperial_precision
|
||||
else:
|
||||
precision = None
|
||||
formatted_thickness = format_distance(
|
||||
@@ -330,11 +330,12 @@ class ObjectMaterialData:
|
||||
elif cls.material.is_a("IfcMaterialLayerSet"):
|
||||
layers = cls.material.MaterialLayers
|
||||
thickness = sum([l.LayerThickness for l in layers or []])
|
||||
props = tool.Drawing.get_document_props()
|
||||
prefs = tool.Blender.get_addon_preferences()
|
||||
assert bpy.context.scene
|
||||
unit_system = bpy.context.scene.unit_settings.system
|
||||
precision = None
|
||||
if unit_system == "IMPERIAL":
|
||||
precision = props.imperial_precision
|
||||
precision = prefs.doc.imperial_precision
|
||||
return format_distance(thickness, precision=precision, suppress_zero_inches=True, in_unit_length=True)
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -503,12 +503,12 @@ class EnableEditingAssignedMaterial(bpy.types.Operator):
|
||||
props.material_set_attributes.clear()
|
||||
|
||||
if "Usage" in material.is_a():
|
||||
bonsai.bim.helper.import_attributes2(
|
||||
bonsai.bim.helper.import_attributes(
|
||||
material, props.material_set_usage_attributes, callback=self.import_attributes_callback
|
||||
)
|
||||
bonsai.bim.helper.import_attributes2(material[0], props.material_set_attributes)
|
||||
bonsai.bim.helper.import_attributes(material[0], props.material_set_attributes)
|
||||
else:
|
||||
bonsai.bim.helper.import_attributes2(material, props.material_set_attributes)
|
||||
bonsai.bim.helper.import_attributes(material, props.material_set_attributes)
|
||||
return {"FINISHED"}
|
||||
|
||||
def import_attributes_callback(
|
||||
@@ -652,7 +652,7 @@ class EnableEditingMaterialSetItemProfile(bpy.types.Operator):
|
||||
self.props.active_material_set_item_id = self.material_set_item
|
||||
self.props.material_set_item_profile_attributes.clear()
|
||||
profile = tool.Ifc.get().by_id(self.material_set_item).Profile
|
||||
bonsai.bim.helper.import_attributes2(profile, self.props.material_set_item_profile_attributes)
|
||||
bonsai.bim.helper.import_attributes(profile, self.props.material_set_item_profile_attributes)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -713,7 +713,7 @@ class EnableEditingMaterialSetItem(bpy.types.Operator):
|
||||
self.props.material_set_item_material = str(material_set_item.Material.id())
|
||||
|
||||
self.props.material_set_item_attributes.clear()
|
||||
bonsai.bim.helper.import_attributes2(material_set_item, self.props.material_set_item_attributes)
|
||||
bonsai.bim.helper.import_attributes(material_set_item, self.props.material_set_item_attributes)
|
||||
|
||||
if material_set_item.is_a("IfcMaterialProfile"):
|
||||
if material_set_item.Profile and material_set_item.Profile.ProfileName:
|
||||
|
||||
@@ -20,6 +20,7 @@ import ifcopenshell
|
||||
import bpy
|
||||
from ifcopenshell.util.doc import get_entity_doc
|
||||
import bonsai.tool as tool
|
||||
from bonsai.bim.module.classification.data import MaterialClassificationsData
|
||||
from bonsai.bim.module.material.data import MaterialsData, ObjectMaterialData
|
||||
from bonsai.bim.module.profile.data import ProfileData
|
||||
from bonsai.bim.prop import StrProperty, Attribute
|
||||
@@ -157,10 +158,16 @@ class Material(PropertyGroup):
|
||||
|
||||
|
||||
class BIMMaterialProperties(PropertyGroup):
|
||||
|
||||
def update_active_material_index(self, context: bpy.types.Context) -> None:
|
||||
if not MaterialClassificationsData.is_loaded:
|
||||
return
|
||||
MaterialClassificationsData.data["references"] = MaterialClassificationsData.references()
|
||||
|
||||
is_editing: BoolProperty(name="Is Editing", default=False)
|
||||
material_type: EnumProperty(items=get_material_types, update=update_material_type, name="Material Type")
|
||||
materials: CollectionProperty(name="Materials", type=Material)
|
||||
active_material_index: IntProperty(name="Active Material Index")
|
||||
active_material_index: IntProperty(name="Active Material Index", update=update_active_material_index)
|
||||
profiles: EnumProperty(items=get_profiles, name="Profiles")
|
||||
active_material_id: IntProperty(name="Active Material ID")
|
||||
material_attributes: CollectionProperty(name="Material Attributes", type=Attribute)
|
||||
@@ -169,9 +176,8 @@ class BIMMaterialProperties(PropertyGroup):
|
||||
contexts: EnumProperty(items=get_contexts, name="Contexts")
|
||||
|
||||
@property
|
||||
def active_material(self):
|
||||
if 0 <= self.active_material_index < len(self.materials):
|
||||
return self.materials[self.active_material_index]
|
||||
def active_material(self) -> Material | None:
|
||||
return tool.Blender.get_active_uilist_element(self.materials, self.active_material_index)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
is_editing: bool
|
||||
|
||||
@@ -48,7 +48,7 @@ class BIM_PT_materials(Panel):
|
||||
MaterialsData.load()
|
||||
|
||||
self.props = tool.Material.get_material_props()
|
||||
material = tool.Material.get_active_material_item()
|
||||
material = self.props.active_material
|
||||
material_id = material.ifc_definition_id if material else None
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
|
||||
@@ -371,7 +371,9 @@ def get_vertical_profile_preview_data(
|
||||
return data
|
||||
|
||||
|
||||
def get_horizontal_profile_preview_data(context, relating_type):
|
||||
def get_horizontal_profile_preview_data(
|
||||
context: bpy.types.Context, relating_type: ifcopenshell.entity_instance
|
||||
) -> dict[str, Any]:
|
||||
material = ifcopenshell.util.element.get_material(relating_type)
|
||||
try:
|
||||
profile_curve = material.MaterialProfiles[0].Profile
|
||||
@@ -385,7 +387,7 @@ def get_horizontal_profile_preview_data(context, relating_type):
|
||||
polyline_data = context.scene.BIMPolylineProperties.insertion_polyline
|
||||
polyline_points = polyline_data[0].polyline_points if polyline_data else []
|
||||
if len(polyline_points) < 2:
|
||||
return
|
||||
return {}
|
||||
for point in polyline_points:
|
||||
polyline_verts.append(Vector((point.x, point.y, point.z)))
|
||||
polyline_edges = [(i, i + 1) for i in range(len(polyline_verts) - 1)]
|
||||
@@ -397,7 +399,7 @@ def get_horizontal_profile_preview_data(context, relating_type):
|
||||
|
||||
verts = shape.verts
|
||||
if not verts:
|
||||
raise RuntimeError(f"Profile shape has no vertices, it probably is invalid: '{profile}'.")
|
||||
raise RuntimeError(f"Profile shape has no vertices, it probably is invalid: '{profile_curve}'.")
|
||||
|
||||
edges = shape.edges
|
||||
|
||||
@@ -433,7 +435,7 @@ def get_horizontal_profile_preview_data(context, relating_type):
|
||||
case "9":
|
||||
grouped_verts = [(v[0] + x_offset, v[1] - y_offset, v[2]) for v in grouped_verts]
|
||||
|
||||
data = {}
|
||||
data: dict[str, Any] = {}
|
||||
data["verts"] = []
|
||||
data["edges"] = []
|
||||
data["tris"] = []
|
||||
|
||||
@@ -38,6 +38,7 @@ import bonsai.core.aggregate
|
||||
import bonsai.core.type
|
||||
import bonsai.core.root
|
||||
import bonsai.core.geometry
|
||||
import bonsai.core.model as core
|
||||
import bonsai.core.spatial
|
||||
from . import wall, slab, profile, mep
|
||||
from bonsai.bim.ifc import IfcStore
|
||||
@@ -564,6 +565,7 @@ class SetActiveType(bpy.types.Operator, tool.Ifc.Operator):
|
||||
props.relating_type_id = str(self.relating_type)
|
||||
|
||||
|
||||
# TODO: not exposed to UI.
|
||||
class AlignProduct(bpy.types.Operator):
|
||||
bl_idname = "bim.align_product"
|
||||
bl_label = "Align Product"
|
||||
|
||||
@@ -191,14 +191,6 @@ class BIMModelProperties(PropertyGroup):
|
||||
menu_relating_type_id: bpy.props.IntProperty()
|
||||
icon_id: bpy.props.IntProperty()
|
||||
updating: bpy.props.BoolProperty(default=False)
|
||||
occurrence_name_style: bpy.props.EnumProperty(
|
||||
items=[("CLASS", "By Class", ""), ("TYPE", "By Type", ""), ("CUSTOM", "Custom", "")],
|
||||
name="Occurrence Name Style",
|
||||
)
|
||||
occurrence_name_function: bpy.props.StringProperty(
|
||||
name="Occurrence Name Function",
|
||||
description="Code that will be evaluated to generate occurrence name for CUSTOM occurrence name style",
|
||||
)
|
||||
getter_enum = {"ifc_class": get_ifc_class, "relating_type": get_relating_type_id}
|
||||
extrusion_depth: bpy.props.FloatProperty(name="Extrusion Depth", min=0.001, default=42.0, subtype="DISTANCE")
|
||||
cardinal_point: bpy.props.EnumProperty(
|
||||
@@ -305,8 +297,6 @@ class BIMModelProperties(PropertyGroup):
|
||||
menu_relating_type_id: int
|
||||
icon_id: int
|
||||
updating: bool
|
||||
occurrence_name_style: Literal["CLASS", "TYPE", "CUSTOM"]
|
||||
occurrence_name_function: str
|
||||
extrusion_depth: float
|
||||
cardinal_point: Literal[
|
||||
"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19"
|
||||
|
||||
@@ -118,7 +118,7 @@ class EnableEditingProfile(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
props = tool.Profile.get_profile_props()
|
||||
props.profile_attributes.clear()
|
||||
bonsai.bim.helper.import_attributes2(tool.Ifc.get().by_id(self.profile), props.profile_attributes)
|
||||
bonsai.bim.helper.import_attributes(tool.Ifc.get().by_id(self.profile), props.profile_attributes)
|
||||
props.active_profile_id = self.profile
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -1594,14 +1594,15 @@ class ExportIFC(bpy.types.Operator, ExportHelper):
|
||||
|
||||
def execute(self, context):
|
||||
project_props = tool.Project.get_project_props()
|
||||
prefs = tool.Blender.get_addon_preferences()
|
||||
project_props.use_relative_project_path = self.use_relative_path
|
||||
if project_props.should_disable_undo_on_save:
|
||||
if prefs.should_disable_undo_on_save:
|
||||
old_history_size = tool.Ifc.get().history_size
|
||||
old_undo_steps = context.preferences.edit.undo_steps
|
||||
tool.Ifc.get().history_size = 0
|
||||
context.preferences.edit.undo_steps = 0
|
||||
IfcStore.execute_ifc_operator(self, context)
|
||||
if project_props.should_disable_undo_on_save:
|
||||
if prefs.should_disable_undo_on_save:
|
||||
tool.Ifc.get().history_size = old_history_size
|
||||
context.preferences.edit.undo_steps = old_undo_steps
|
||||
return {"FINISHED"}
|
||||
@@ -1885,12 +1886,12 @@ class LoadLinkedProject(bpy.types.Operator, ImportHelper):
|
||||
print("Finished", time.time() - start)
|
||||
return {"FINISHED"}
|
||||
|
||||
def process_occurrence(self, shape: ShapeElementType) -> None:
|
||||
def process_occurrence(self, shape: W.TriangulationElement) -> None:
|
||||
element = self.file.by_id(shape.id)
|
||||
faces: tuple[int, ...] = shape.geometry.faces
|
||||
verts: tuple[float, ...] = shape.geometry.verts
|
||||
materials: tuple[W.style, ...] = shape.geometry.materials
|
||||
material_ids: tuple[int, ...] = shape.geometry.material_ids
|
||||
faces = shape.geometry.faces
|
||||
verts = shape.geometry.verts
|
||||
materials = shape.geometry.materials
|
||||
material_ids = shape.geometry.material_ids
|
||||
|
||||
mat = ifcopenshell.util.shape.get_shape_matrix(shape)
|
||||
|
||||
@@ -1919,7 +1920,7 @@ class LoadLinkedProject(bpy.types.Operator, ImportHelper):
|
||||
verts = geometry.verts
|
||||
mesh["has_cartesian_point_offset"] = False
|
||||
|
||||
material_to_slot = {}
|
||||
material_to_slot: dict[int, int] = {}
|
||||
max_slot_index = 0
|
||||
|
||||
for i, material in enumerate(materials):
|
||||
|
||||
@@ -96,7 +96,7 @@ def is_editing_project_library_update(self: "BIMProjectProperties", context: bpy
|
||||
self.editing_project_library_id = int(self.selected_project_library)
|
||||
project_library = library_file.by_id(int(self.selected_project_library))
|
||||
self.project_library_attributes.clear()
|
||||
bonsai.bim.helper.import_attributes2(project_library, self.project_library_attributes)
|
||||
bonsai.bim.helper.import_attributes(project_library, self.project_library_attributes)
|
||||
self.parent_library = str(tool.Project.get_parent_library(project_library).id())
|
||||
ProjectLibraryData.load() # Show edit icon in enum.
|
||||
return
|
||||
@@ -300,7 +300,6 @@ class BIMProjectProperties(PropertyGroup):
|
||||
)
|
||||
should_use_cpu_multiprocessing: BoolProperty(name="CPU Multiprocessing", default=True)
|
||||
should_merge_materials_by_colour: BoolProperty(name="Merge Materials by Colour", default=False)
|
||||
should_stream: BoolProperty(name="Stream Data From IFC-SPF (Only for advanced users)", default=False)
|
||||
should_load_geometry: BoolProperty(name="Load Geometry", default=True)
|
||||
should_clean_mesh: BoolProperty(
|
||||
name="Clean Meshes",
|
||||
@@ -362,9 +361,6 @@ class BIMProjectProperties(PropertyGroup):
|
||||
description="Load indexed maps (UV and color maps)",
|
||||
default=True,
|
||||
)
|
||||
should_disable_undo_on_save: BoolProperty(
|
||||
name="Disable Undo When Saving (Faster saves, no undo for you!)", default=False
|
||||
)
|
||||
links: CollectionProperty(name="Links", type=Link)
|
||||
active_link_index: IntProperty(name="Active Link Index")
|
||||
export_schema: EnumProperty(items=get_export_schema, name="IFC Schema", update=update_export_schema)
|
||||
@@ -409,9 +405,13 @@ class BIMProjectProperties(PropertyGroup):
|
||||
queried_obj: bpy.props.PointerProperty(type=bpy.types.Object)
|
||||
queried_obj_root: bpy.props.PointerProperty(type=bpy.types.Object)
|
||||
clipping_planes: bpy.props.CollectionProperty(type=ObjProperty)
|
||||
clipping_planes_active: bpy.props.IntProperty(min=0, default=0, max=5)
|
||||
clipping_planes_active_index: bpy.props.IntProperty(min=0, default=0, max=5)
|
||||
edited_objs: bpy.props.CollectionProperty(type=EditedObj)
|
||||
|
||||
@property
|
||||
def active_clipping_plane(self) -> ObjProperty | None:
|
||||
return tool.Blender.get_active_uilist_element(self.clipping_planes, self.clipping_planes_active_index)
|
||||
|
||||
@property
|
||||
def clipping_planes_objs(self) -> list[bpy.types.Object]:
|
||||
return list({cp.obj for cp in self.clipping_planes if cp.obj})
|
||||
@@ -458,7 +458,6 @@ class BIMProjectProperties(PropertyGroup):
|
||||
geometry_library: Literal["opencascade", "cgal", "cgal-simple", "hybrid-cgal-simple-opencascade"]
|
||||
should_use_cpu_multiprocessing: bool
|
||||
should_merge_materials_by_colour: bool
|
||||
should_stream: bool
|
||||
should_load_geometry: bool
|
||||
should_clean_mesh: bool
|
||||
should_cache: bool
|
||||
@@ -474,7 +473,6 @@ class BIMProjectProperties(PropertyGroup):
|
||||
element_offset: int
|
||||
element_limit: int
|
||||
load_indexed_maps: bool
|
||||
should_disable_undo_on_save: bool
|
||||
links: bpy.types.bpy_prop_collection_idprop[Link]
|
||||
active_link_index: int
|
||||
export_schema: str
|
||||
@@ -492,7 +490,7 @@ class BIMProjectProperties(PropertyGroup):
|
||||
queried_obj: Union[bpy.types.Object, None]
|
||||
queried_obj_root: Union[bpy.types.Object, None]
|
||||
clipping_planes: bpy.types.bpy_prop_collection_idprop[ObjProperty]
|
||||
clipping_planes_active: int
|
||||
clipping_planes_active_index: int
|
||||
edited_objs: bpy.types.bpy_prop_collection_idprop[EditedObj]
|
||||
|
||||
def get_active_library_breadcrumb(self) -> Union[LibraryBreadcrumb, None]:
|
||||
|
||||
@@ -615,6 +615,8 @@ class BIM_UL_links(UIList):
|
||||
op.filepath = item.name
|
||||
else:
|
||||
row.prop(item, "name", text="")
|
||||
op = row.operator("bim.select_uri_attribute", text="", icon="FILE_FOLDER")
|
||||
op.attribute_data_path = tool.Blender.get_full_data_path(item, "name")
|
||||
op = row.operator("bim.load_link", text="", icon="LINKED")
|
||||
op.filepath = item.name
|
||||
op = row.operator("bim.unlink_ifc", text="", icon="X")
|
||||
|
||||
@@ -176,8 +176,8 @@ class MaterialPsetsData(Data):
|
||||
def load(cls):
|
||||
ifc_definition_id = None
|
||||
props = tool.Material.get_material_props()
|
||||
if props.materials and props.active_material_index < len(props.materials):
|
||||
ifc_definition_id = props.materials[props.active_material_index].ifc_definition_id
|
||||
if material := props.active_material:
|
||||
ifc_definition_id = material.ifc_definition_id
|
||||
|
||||
cls.data = {
|
||||
"ifc_definition_id": ifc_definition_id,
|
||||
@@ -189,8 +189,7 @@ class MaterialPsetsData(Data):
|
||||
@classmethod
|
||||
def pset_name(cls):
|
||||
props = tool.Material.get_material_props()
|
||||
if props.materials and props.active_material_index < len(props.materials):
|
||||
material = props.materials[props.active_material_index]
|
||||
if material := props.active_material:
|
||||
if material.ifc_definition_id:
|
||||
material = tool.Ifc.get().by_id(material.ifc_definition_id)
|
||||
category = getattr(material, "Category", None) or None
|
||||
@@ -240,9 +239,9 @@ class ResourceQtosData(Data):
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
rprops = bpy.context.scene.BIMResourceProperties
|
||||
rtprops = bpy.context.scene.BIMResourceTreeProperties
|
||||
ifc_definition_id = rtprops.resources[rprops.active_resource_index].ifc_definition_id
|
||||
active_resource = tool.Resource.get_resource_props().active_resource
|
||||
assert active_resource
|
||||
ifc_definition_id = active_resource.ifc_definition_id
|
||||
cls.data = {"qtos": cls.psetqtos(tool.Ifc.get().by_id(ifc_definition_id), qtos_only=True)}
|
||||
cls.is_loaded = True
|
||||
|
||||
@@ -253,9 +252,9 @@ class ResourcePsetsData(Data):
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
rprops = bpy.context.scene.BIMResourceProperties
|
||||
rtprops = bpy.context.scene.BIMResourceTreeProperties
|
||||
ifc_definition_id = rtprops.resources[rprops.active_resource_index].ifc_definition_id
|
||||
active_resource = tool.Resource.get_resource_props().active_resource
|
||||
assert active_resource
|
||||
ifc_definition_id = active_resource.ifc_definition_id
|
||||
cls.data = {"psets": cls.psetqtos(tool.Ifc.get().by_id(ifc_definition_id), psets_only=True)}
|
||||
cls.is_loaded = True
|
||||
|
||||
|
||||
@@ -115,7 +115,7 @@ def get_material_pset_names(self: "PsetProperties", context: object) -> tool.Ble
|
||||
return MaterialPsetsData.data["pset_name"]
|
||||
|
||||
|
||||
def get_material_set_pset_names(self: "PsetProperties", context: object):
|
||||
def get_material_set_pset_names(self: "PsetProperties", context: object) -> tool.Blender.BLENDER_ENUM_ITEMS:
|
||||
global psetnames
|
||||
if not ObjectMaterialData.is_loaded:
|
||||
ObjectMaterialData.load()
|
||||
@@ -128,12 +128,14 @@ def get_material_set_pset_names(self: "PsetProperties", context: object):
|
||||
return psetnames[ifc_class]
|
||||
|
||||
|
||||
def get_material_set_item_pset_names(self: "PsetProperties", context: object) -> tool.Blender.BLENDER_ENUM_ITEMS:
|
||||
def get_material_set_item_pset_names(
|
||||
self: "PsetProperties", context: bpy.types.Context
|
||||
) -> tool.Blender.BLENDER_ENUM_ITEMS:
|
||||
global psetnames
|
||||
obj = context.active_object
|
||||
assert obj
|
||||
omprops = tool.Material.get_object_material_props(obj)
|
||||
if not omprops.active_material_set_item_id:
|
||||
if not (ifc_definition_id := omprops.active_material_set_item_id):
|
||||
return []
|
||||
ifc_class = tool.Ifc.get().by_id(ifc_definition_id).is_a()
|
||||
if ifc_class not in psetnames:
|
||||
@@ -151,22 +153,22 @@ def get_task_qto_names(self: "PsetProperties", context: object) -> tool.Blender.
|
||||
return qtonames[ifc_class]
|
||||
|
||||
|
||||
def get_resource_pset_names(self: "PsetProperties", context: object) -> tool.Blender.BLENDER_ENUM_ITEMS:
|
||||
def get_resource_pset_names(self: "PsetProperties", context: bpy.types.Context) -> tool.Blender.BLENDER_ENUM_ITEMS:
|
||||
global psetnames
|
||||
rprops = context.scene.BIMResourceProperties
|
||||
rtprops = context.scene.BIMResourceTreeProperties
|
||||
ifc_class = tool.Ifc.get().by_id(rtprops.resources[rprops.active_resource_index].ifc_definition_id).is_a()
|
||||
active_resource = tool.Resource.get_resource_props().active_resource
|
||||
assert active_resource
|
||||
ifc_class = tool.Ifc.get().by_id(active_resource.ifc_definition_id).is_a()
|
||||
if ifc_class not in psetnames:
|
||||
psets = bonsai.bim.schema.ifc.psetqto.get_applicable(ifc_class, pset_only=True, schema=tool.Ifc.get_schema())
|
||||
psetnames[ifc_class] = blender_formatted_enum_from_psets(psets)
|
||||
return psetnames[ifc_class]
|
||||
|
||||
|
||||
def get_resource_qto_names(self: "PsetProperties", context: object) -> tool.Blender.BLENDER_ENUM_ITEMS:
|
||||
def get_resource_qto_names(self: "PsetProperties", context: bpy.types.Context) -> tool.Blender.BLENDER_ENUM_ITEMS:
|
||||
global qtonames
|
||||
rprops = context.scene.BIMResourceProperties
|
||||
rtprops = context.scene.BIMResourceTreeProperties
|
||||
ifc_class = tool.Ifc.get().by_id(rtprops.resources[rprops.active_resource_index].ifc_definition_id).is_a()
|
||||
active_resource = tool.Resource.get_resource_props().active_resource
|
||||
assert active_resource
|
||||
ifc_class = tool.Ifc.get().by_id(active_resource.ifc_definition_id).is_a()
|
||||
if ifc_class not in qtonames:
|
||||
psets = bonsai.bim.schema.ifc.psetqto.get_applicable(ifc_class, qto_only=True, schema=tool.Ifc.get_schema())
|
||||
qtonames[ifc_class] = blender_formatted_enum_from_psets(psets)
|
||||
@@ -223,7 +225,9 @@ def get_qto_name(self: "PsetProperties", context: bpy.types.Context) -> tool.Ble
|
||||
results = get_resource_qto_names(self, context)
|
||||
elif prop_type == "GroupPsetProperties":
|
||||
results = get_group_qto_names(self, context)
|
||||
return [("BBIM_CUSTOM", "Custom Qto", "Create a quantity set without using a template."), None] + results
|
||||
else:
|
||||
assert False
|
||||
return [("BBIM_CUSTOM", "Custom Qto", "Create a quantity set without using a template."), None] + list(results)
|
||||
|
||||
|
||||
def get_object_qto_name(self: "PsetProperties", context: object) -> tool.Blender.BLENDER_ENUM_ITEMS:
|
||||
|
||||
@@ -474,16 +474,17 @@ class BIM_PT_material_psets(Panel):
|
||||
return False
|
||||
|
||||
def draw(self, context):
|
||||
assert self.layout
|
||||
props = tool.Material.get_material_props()
|
||||
if props.materials and props.active_material_index < len(props.materials):
|
||||
ifc_definition_id = props.materials[props.active_material_index].ifc_definition_id
|
||||
if material := props.active_material:
|
||||
ifc_definition_id = material.ifc_definition_id
|
||||
|
||||
if not MaterialPsetsData.is_loaded:
|
||||
MaterialPsetsData.load()
|
||||
elif ifc_definition_id != MaterialPsetsData.data["ifc_definition_id"]:
|
||||
MaterialPsetsData.load()
|
||||
|
||||
props = context.scene.MaterialPsetProperties
|
||||
props = tool.Pset.get_pset_props("", "Material")
|
||||
row = self.layout.row(align=True)
|
||||
prop_with_search(row, props, "pset_name", text="")
|
||||
op = row.operator("bim.add_pset", icon="ADD", text="")
|
||||
@@ -523,6 +524,7 @@ class BIM_PT_material_set_item_psets(Panel):
|
||||
if not MaterialSetItemPsetsData.is_loaded:
|
||||
MaterialSetItemPsetsData.load()
|
||||
|
||||
assert self.layout
|
||||
obj = context.active_object
|
||||
assert obj
|
||||
omprops = tool.Material.get_object_material_props(obj)
|
||||
@@ -530,7 +532,7 @@ class BIM_PT_material_set_item_psets(Panel):
|
||||
self.layout.label(text="No Material Set Item Edited.")
|
||||
return
|
||||
|
||||
props = obj.MaterialSetItemPsetProperties
|
||||
props = tool.Pset.get_pset_props(obj.name, "MaterialSetItem")
|
||||
row = self.layout.row(align=True)
|
||||
prop_with_search(row, props, "pset_name", text="")
|
||||
op = row.operator("bim.add_pset", icon="ADD", text="")
|
||||
@@ -569,7 +571,8 @@ class BIM_PT_task_qtos(Panel):
|
||||
if not TaskQtosData.is_loaded:
|
||||
TaskQtosData.load()
|
||||
|
||||
props = context.scene.TaskPsetProperties
|
||||
assert self.layout
|
||||
props = tool.Pset.get_pset_props("", "Task")
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "qto_name", text="")
|
||||
op = row.operator("bim.add_qto", icon="ADD", text="")
|
||||
@@ -593,17 +596,15 @@ class BIM_PT_resource_qtos(Panel):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
props = context.scene.BIMResourceProperties
|
||||
total_resources = len(context.scene.BIMResourceTreeProperties.resources)
|
||||
if total_resources > 0 and props.active_resource_index < total_resources:
|
||||
return True
|
||||
return False
|
||||
active_resource = tool.Resource.get_resource_props().active_resource
|
||||
return bool(active_resource)
|
||||
|
||||
def draw(self, context):
|
||||
if not ResourceQtosData.is_loaded:
|
||||
ResourceQtosData.load()
|
||||
|
||||
props = context.scene.ResourcePsetProperties
|
||||
assert self.layout
|
||||
props = tool.Pset.get_pset_props("", "Resource")
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "qto_name", text="")
|
||||
op = row.operator("bim.add_qto", icon="ADD", text="")
|
||||
@@ -627,17 +628,15 @@ class BIM_PT_resource_psets(Panel):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
props = context.scene.BIMResourceProperties
|
||||
total_resources = len(context.scene.BIMResourceTreeProperties.resources)
|
||||
if total_resources > 0 and props.active_resource_index < total_resources:
|
||||
return True
|
||||
return False
|
||||
active_resource = tool.Resource.get_resource_props().active_resource
|
||||
return bool(active_resource)
|
||||
|
||||
def draw(self, context):
|
||||
if not ResourcePsetsData.is_loaded:
|
||||
ResourcePsetsData.load()
|
||||
|
||||
props = context.scene.ResourcePsetProperties
|
||||
assert self.layout
|
||||
props = tool.Pset.get_pset_props("", "Resource")
|
||||
row = self.layout.row(align=True)
|
||||
prop_with_search(row, props, "pset_name", text="")
|
||||
op = row.operator("bim.add_pset", icon="ADD", text="")
|
||||
@@ -671,7 +670,8 @@ class BIM_PT_group_qtos(Panel):
|
||||
if not GroupQtosData.is_loaded:
|
||||
GroupQtosData.load()
|
||||
|
||||
props = context.scene.GroupPsetProperties
|
||||
assert self.layout
|
||||
props = tool.Pset.get_pset_props("", "Group")
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "qto_name", text="")
|
||||
op = row.operator("bim.add_qto", icon="ADD", text="")
|
||||
@@ -705,7 +705,8 @@ class BIM_PT_group_psets(Panel):
|
||||
if not GroupPsetData.is_loaded:
|
||||
GroupPsetData.load()
|
||||
|
||||
props = context.scene.GroupPsetProperties
|
||||
assert self.layout
|
||||
props = tool.Pset.get_pset_props("", "Group")
|
||||
row = self.layout.row(align=True)
|
||||
prop_with_search(row, props, "pset_name", text="")
|
||||
op = row.operator("bim.add_pset", icon="ADD", text="")
|
||||
@@ -749,7 +750,8 @@ class BIM_PT_profile_psets(Panel):
|
||||
):
|
||||
ProfilePsetsData.load()
|
||||
|
||||
props = context.scene.ProfilePsetProperties
|
||||
assert self.layout
|
||||
props = tool.Pset.get_pset_props("", "Profile")
|
||||
row = self.layout.row(align=True)
|
||||
prop_with_search(row, props, "pset_name", text="")
|
||||
op = row.operator("bim.add_pset", icon="ADD", text="")
|
||||
@@ -781,7 +783,8 @@ class BIM_PT_work_schedule_psets(Panel):
|
||||
if not WorkSchedulePsetsData.is_loaded:
|
||||
WorkSchedulePsetsData.load()
|
||||
|
||||
props = context.scene.WorkSchedulePsetProperties
|
||||
assert self.layout
|
||||
props = tool.Pset.get_pset_props("", "WorkSchedule")
|
||||
row = self.layout.row(align=True)
|
||||
prop_with_search(row, props, "pset_name", text="")
|
||||
op = row.operator("bim.add_pset", icon="ADD", text="")
|
||||
|
||||
@@ -124,7 +124,7 @@ class ResourceData:
|
||||
@classmethod
|
||||
def cost_values(cls) -> list[dict[str, Any]]:
|
||||
results = []
|
||||
ifc_id = bpy.context.scene.BIMResourceProperties.active_resource_id
|
||||
ifc_id = tool.Resource.get_resource_props().active_resource_id
|
||||
if not ifc_id:
|
||||
return results
|
||||
resource = tool.Ifc.get().by_id(ifc_id)
|
||||
|
||||
@@ -87,10 +87,11 @@ class EditResource(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
props = tool.Resource.get_resource_props()
|
||||
core.edit_resource(
|
||||
tool.Ifc,
|
||||
tool.Resource,
|
||||
resource=tool.Ifc.get().by_id(context.scene.BIMResourceProperties.active_resource_id),
|
||||
resource=tool.Ifc.get().by_id(props.active_resource_id),
|
||||
)
|
||||
|
||||
|
||||
@@ -174,10 +175,11 @@ class EditResourceTime(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
props = tool.Resource.get_resource_props()
|
||||
core.edit_resource_time(
|
||||
tool.Ifc,
|
||||
tool.Resource,
|
||||
resource_time=tool.Ifc.get().by_id(context.scene.BIMResourceProperties.active_resource_time_id),
|
||||
resource_time=tool.Ifc.get().by_id(props.active_resource_time_id),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ import bonsai.tool as tool
|
||||
import bonsai.bim.module.pset.data
|
||||
import bonsai.bim.module.resource.data
|
||||
import bonsai.bim.module.sequence.data
|
||||
from bonsai.bim.prop import StrProperty, Attribute
|
||||
from bonsai.bim.prop import Attribute
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
@@ -36,11 +36,12 @@ from bpy.props import (
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
from typing import TYPE_CHECKING, Literal, get_args
|
||||
|
||||
quantitytypes_enum = {}
|
||||
quantitytypes_enum: dict[str, tool.Blender.BLENDER_ENUM_ITEMS] = {}
|
||||
|
||||
|
||||
def setup_quantity_types_enum():
|
||||
def setup_quantity_types_enum() -> None:
|
||||
resources = ifcopenshell.util.resource.RESOURCES_TO_QUANTITIES
|
||||
for resource, quantities in resources.items():
|
||||
quantitytypes_enum[resource] = [(q, q, "") for q in quantities]
|
||||
@@ -49,8 +50,8 @@ def setup_quantity_types_enum():
|
||||
setup_quantity_types_enum()
|
||||
|
||||
|
||||
def updateResourceName(self, context):
|
||||
props = context.scene.BIMResourceProperties
|
||||
def updateResourceName(self: "Resource", context: object) -> None:
|
||||
props = tool.Resource.get_resource_props()
|
||||
if not props.is_resource_update_enabled:
|
||||
return
|
||||
ifc_file = tool.Ifc.get()
|
||||
@@ -66,18 +67,19 @@ def updateResourceName(self, context):
|
||||
tool.Sequence.refresh_task_resources()
|
||||
|
||||
|
||||
def get_quantity_types(self, context):
|
||||
def get_quantity_types(self: "BIMResourceProperties", context: object) -> tool.Blender.BLENDER_ENUM_ITEMS:
|
||||
return quantitytypes_enum[self.active_resource_class]
|
||||
|
||||
|
||||
def update_active_resource_index(self, context):
|
||||
def update_active_resource_index(self: "BIMResourceProperties", context: object) -> None:
|
||||
bonsai.bim.module.pset.data.refresh()
|
||||
if self.should_show_resource_tools:
|
||||
tool.Resource.load_productivity_data()
|
||||
|
||||
|
||||
def updateResourceUsage(self, context):
|
||||
if not context.scene.BIMResourceProperties.is_resource_update_enabled:
|
||||
def updateResourceUsage(self: "Resource", context: object) -> None:
|
||||
props = tool.Resource.get_resource_props()
|
||||
if not props.is_resource_update_enabled:
|
||||
return
|
||||
if not self.schedule_usage:
|
||||
return
|
||||
@@ -102,6 +104,15 @@ class ISODuration(PropertyGroup):
|
||||
minutes: IntProperty(name="Minutes", default=0)
|
||||
seconds: IntProperty(name="Seconds", default=0)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
name: str
|
||||
years: int
|
||||
months: int
|
||||
days: int
|
||||
hours: int
|
||||
minutes: int
|
||||
seconds: int
|
||||
|
||||
|
||||
class Resource(PropertyGroup):
|
||||
name: StringProperty(name="Name", update=updateResourceName)
|
||||
@@ -111,10 +122,26 @@ class Resource(PropertyGroup):
|
||||
is_expanded: BoolProperty(name="Is Expanded")
|
||||
level_index: IntProperty(name="Level Index")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
name: str
|
||||
ifc_definition_id: int
|
||||
schedule_usage: float
|
||||
has_children: bool
|
||||
is_expanded: bool
|
||||
level_index: int
|
||||
|
||||
|
||||
# Separate from BIMResourceProperties - see BIMTaskTreeProperties note.
|
||||
class BIMResourceTreeProperties(PropertyGroup):
|
||||
resources: CollectionProperty(name="Resources", type=Resource)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
resources: bpy.types.bpy_prop_collection_idprop[Resource]
|
||||
|
||||
|
||||
EditingResourceType = Literal["ATTRIBUTES", "USAGE", "COSTS", "QUANTITY"]
|
||||
CostType = Literal["FIXED", "SUM", "CATEGORY"]
|
||||
|
||||
|
||||
class BIMResourceProperties(PropertyGroup):
|
||||
resource_attributes: CollectionProperty(name="Resource Attributes", type=Attribute)
|
||||
@@ -128,13 +155,7 @@ class BIMResourceProperties(PropertyGroup):
|
||||
active_resource_time_id: IntProperty(name="Active Resource Usage Id")
|
||||
resource_time_attributes: CollectionProperty(name="Resource Usage Attributes", type=Attribute)
|
||||
editing_resource_type: EnumProperty(
|
||||
name="Editing Resource Type",
|
||||
items=(
|
||||
("ATTRIBUTES", "", ""),
|
||||
("USAGE", "", ""),
|
||||
("COSTS", "", ""),
|
||||
("QUANTITY", "", ""),
|
||||
),
|
||||
name="Editing Resource Type", items=[(i, i, "") for i in get_args(EditingResourceType)]
|
||||
)
|
||||
cost_types: EnumProperty(
|
||||
items=[
|
||||
@@ -154,9 +175,56 @@ class BIMResourceProperties(PropertyGroup):
|
||||
quantity_attributes: CollectionProperty(name="Quantity Attributes", type=Attribute)
|
||||
should_show_resource_tools: BoolProperty(name="Edit Productivity", update=update_active_resource_index)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
resource_attributes: bpy.types.bpy_prop_collection_idprop[Attribute]
|
||||
is_editing: bool
|
||||
active_resource_index: int
|
||||
active_resource_id: int
|
||||
active_resource_class: str
|
||||
contracted_resources: str
|
||||
is_resource_update_enabled: bool
|
||||
is_loaded: bool
|
||||
active_resource_time_id: int
|
||||
resource_time_attributes: bpy.types.bpy_prop_collection_idprop[Attribute]
|
||||
editing_resource_type: EditingResourceType
|
||||
cost_types: CostType
|
||||
cost_category: str
|
||||
active_cost_value_id: int
|
||||
cost_value_editing_type: str
|
||||
cost_value_attributes: bpy.types.bpy_prop_collection_idprop[Attribute]
|
||||
cost_value_formula: str
|
||||
quantity_types: str
|
||||
is_editing_quantity: bool
|
||||
quantity_attributes: bpy.types.bpy_prop_collection_idprop[Attribute]
|
||||
should_show_resource_tools: bool
|
||||
|
||||
@property
|
||||
def tree(self) -> "BIMResourceTreeProperties":
|
||||
assert bpy.context.scene
|
||||
tprops = bpy.context.scene.BIMResourceTreeProperties # pyright: ignore[reportAttributeAccessIssue]
|
||||
assert isinstance(tprops, BIMResourceTreeProperties)
|
||||
return tprops
|
||||
|
||||
@property
|
||||
def productivity(self) -> "BIMResourceProductivity":
|
||||
assert bpy.context.scene
|
||||
productivity = bpy.context.scene.BIMResourceProductivity
|
||||
assert isinstance(productivity, BIMResourceProductivity)
|
||||
return productivity
|
||||
|
||||
@property
|
||||
def active_resource(self) -> Resource | None:
|
||||
return tool.Blender.get_active_uilist_element(self.tree.resources, self.active_resource_index)
|
||||
|
||||
|
||||
class BIMResourceProductivity(PropertyGroup):
|
||||
ifc_definition_id: IntProperty(name="IFC Definition ID")
|
||||
quantity_consumed: CollectionProperty(name="Duration", type=ISODuration)
|
||||
quantity_produced: FloatProperty(name="Quantity Produced")
|
||||
quantity_produced_name: StringProperty(name="Quantity Produced Name")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
ifc_definition_id: int
|
||||
quantity_consumed: bpy.types.bpy_prop_collection_idprop[ISODuration]
|
||||
quantity_produced: float
|
||||
quantity_produced_name: str
|
||||
|
||||
@@ -16,12 +16,16 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import annotations
|
||||
import bpy
|
||||
import bonsai.tool as tool
|
||||
import bonsai.bim.helper
|
||||
from bpy.types import Panel, UIList
|
||||
from bonsai.bim.module.resource.data import ResourceData
|
||||
from typing import Any
|
||||
from typing import Any, TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bonsai.bim.module.resource.prop import ISODuration, Resource, BIMResourceTreeProperties
|
||||
|
||||
|
||||
class BIM_PT_resources(Panel):
|
||||
@@ -38,9 +42,10 @@ class BIM_PT_resources(Panel):
|
||||
file = tool.Ifc.get()
|
||||
return file and hasattr(file, "schema") and file.schema != "IFC2X3"
|
||||
|
||||
layout: bpy.types.UILayout
|
||||
|
||||
def draw(self, context):
|
||||
self.props = context.scene.BIMResourceProperties
|
||||
self.tprops = context.scene.BIMResourceTreeProperties
|
||||
self.props = tool.Resource.get_resource_props()
|
||||
if not ResourceData.is_loaded:
|
||||
ResourceData.load()
|
||||
|
||||
@@ -64,7 +69,7 @@ class BIM_PT_resources(Panel):
|
||||
self.layout.template_list(
|
||||
"BIM_UL_resources",
|
||||
"",
|
||||
self.tprops,
|
||||
self.props.tree,
|
||||
"resources",
|
||||
self.props,
|
||||
"active_resource_index",
|
||||
@@ -79,18 +84,18 @@ class BIM_PT_resources(Panel):
|
||||
self.draw_editable_resource_costs_ui()
|
||||
elif self.props.active_resource_id and self.props.editing_resource_type == "USAGE":
|
||||
self.draw_editable_resource_time_attributes_ui()
|
||||
self.draw_productivity_ui(context)
|
||||
self.draw_productivity_ui()
|
||||
|
||||
def draw_productivity_ui(self, context):
|
||||
def draw_productivity_ui(self) -> None:
|
||||
row = self.layout.row(align=True)
|
||||
row.alignment = "RIGHT"
|
||||
row.prop(self.props, "should_show_resource_tools", text="Resource Tools", icon="RECOVER_LAST")
|
||||
if self.props.should_show_resource_tools:
|
||||
total_resources = len(self.tprops.resources)
|
||||
if not total_resources or self.props.active_resource_index >= total_resources:
|
||||
active_resource = self.props.active_resource
|
||||
if not active_resource:
|
||||
return
|
||||
|
||||
ifc_definition_id = self.tprops.resources[self.props.active_resource_index].ifc_definition_id
|
||||
ifc_definition_id = active_resource.ifc_definition_id
|
||||
resource = ResourceData.data["resources"][ifc_definition_id]
|
||||
|
||||
if not resource["type"] in ["IfcConstructionEquipmentResource", "IfcLaborResource"]:
|
||||
@@ -145,7 +150,7 @@ class BIM_PT_resources(Panel):
|
||||
row2_col1 = col1.row()
|
||||
row2_col1.label(text="Schedule Usage")
|
||||
row2col2 = col2.row()
|
||||
row2col2.prop(self.tprops.resources[self.props.active_resource_index], "schedule_usage", text="")
|
||||
row2col2.prop(active_resource, "schedule_usage", text="")
|
||||
row2col3 = col3.row()
|
||||
row2col3.operator("bim.calculate_resource_usage", text="", icon="TEMP")
|
||||
op = row2col3.operator(
|
||||
@@ -189,7 +194,7 @@ class BIM_PT_resources(Panel):
|
||||
row.label(text="{}".format(produtivitiy_rate_message), icon="ARMATURE_DATA")
|
||||
row.operator("bim.add_productivity_data", text="", icon="ADD")
|
||||
|
||||
def draw_resource_operators(self):
|
||||
def draw_resource_operators(self) -> None:
|
||||
row = self.layout.row(align=True)
|
||||
op = row.operator("bim.add_resource", text="Add SubContract", icon="TEXT")
|
||||
op.ifc_class = "IfcSubContractResource"
|
||||
@@ -198,11 +203,11 @@ class BIM_PT_resources(Panel):
|
||||
op.ifc_class = "IfcCrewResource"
|
||||
op.parent_resource = 0
|
||||
|
||||
total_resources = len(self.tprops.resources)
|
||||
if not total_resources or self.props.active_resource_index >= total_resources:
|
||||
active_resource = self.props.active_resource
|
||||
if not active_resource:
|
||||
return
|
||||
|
||||
ifc_definition_id = self.tprops.resources[self.props.active_resource_index].ifc_definition_id
|
||||
ifc_definition_id = active_resource.ifc_definition_id
|
||||
resource = ResourceData.data["resources"][ifc_definition_id]
|
||||
|
||||
if resource["type"] != "IfcSubContractResource":
|
||||
@@ -239,13 +244,13 @@ class BIM_PT_resources(Panel):
|
||||
row.operator("bim.edit_resource_time", text="", icon="CHECKMARK")
|
||||
row.operator("bim.disable_editing_resource", text="", icon="CANCEL")
|
||||
|
||||
def draw_editable_resource_attributes_ui(self):
|
||||
def draw_editable_resource_attributes_ui(self) -> None:
|
||||
bonsai.bim.helper.draw_attributes(self.props.resource_attributes, self.layout)
|
||||
|
||||
def draw_editable_resource_time_attributes_ui(self):
|
||||
def draw_editable_resource_time_attributes_ui(self) -> None:
|
||||
bonsai.bim.helper.draw_attributes(self.props.resource_time_attributes, self.layout)
|
||||
|
||||
def draw_editable_resource_quantity_ui(self):
|
||||
def draw_editable_resource_quantity_ui(self) -> None:
|
||||
resource = ResourceData.data["resources"][self.props.active_resource_id]
|
||||
|
||||
if resource["BaseQuantity"]:
|
||||
@@ -278,7 +283,7 @@ class BIM_PT_resources(Panel):
|
||||
op.resource = self.props.active_resource_id
|
||||
op.ifc_class = self.props.quantity_types
|
||||
|
||||
def draw_editable_resource_costs_ui(self):
|
||||
def draw_editable_resource_costs_ui(self) -> None:
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.props, "cost_types", text="")
|
||||
if self.props.cost_types == "CATEGORY":
|
||||
@@ -326,16 +331,6 @@ class BIM_PT_resources(Panel):
|
||||
op.parent = parent_id
|
||||
op.cost_value = cost_value_id
|
||||
|
||||
def draw_duration_property(self, duration_props, layout):
|
||||
for duration_prop in duration_props:
|
||||
if duration_prop.name == "BaseQuantityConsumed":
|
||||
layout.prop(duration_prop, "years", text="Y")
|
||||
layout.prop(duration_prop, "months", text="M")
|
||||
layout.prop(duration_prop, "days", text="D")
|
||||
layout.prop(duration_prop, "hours", text="H")
|
||||
layout.prop(duration_prop, "minutes", text="Min")
|
||||
layout.prop(duration_prop, "seconds", text="S")
|
||||
|
||||
|
||||
class BIM_UL_resources(UIList):
|
||||
@classmethod
|
||||
@@ -358,7 +353,16 @@ class BIM_UL_resources(UIList):
|
||||
col = row.column()
|
||||
col.label(text="")
|
||||
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
def draw_item(
|
||||
self,
|
||||
context: bpy.types.Context,
|
||||
layout: bpy.types.UILayout,
|
||||
data: BIMResourceTreeProperties,
|
||||
item: Resource,
|
||||
icon,
|
||||
active_data,
|
||||
active_propname,
|
||||
) -> None:
|
||||
icon_map = {
|
||||
"IfcSubContractResource": "TEXT",
|
||||
"IfcCrewResource": "COMMUNITY",
|
||||
@@ -369,7 +373,7 @@ class BIM_UL_resources(UIList):
|
||||
}
|
||||
if item:
|
||||
resource = ResourceData.data["resources"][item.ifc_definition_id]
|
||||
props = context.scene.BIMResourceProperties
|
||||
props = tool.Resource.get_resource_props()
|
||||
row = layout.row(align=True)
|
||||
for i in range(0, item.level_index):
|
||||
row.label(text="", icon="BLANK1")
|
||||
@@ -403,8 +407,12 @@ class BIM_UL_resources(UIList):
|
||||
row.operator("bim.disable_editing_resource", text="", icon="CANCEL")
|
||||
|
||||
|
||||
def draw_productivity_ui(self, context):
|
||||
def draw_duration_property(duration_props, layout):
|
||||
def draw_productivity_ui(self: bpy.types.Operator, context: object) -> None:
|
||||
assert self.layout
|
||||
|
||||
def draw_duration_property(
|
||||
duration_props: bpy.types.bpy_prop_collection_idprop[ISODuration], layout: bpy.types.UILayout
|
||||
) -> None:
|
||||
for duration_prop in duration_props:
|
||||
if duration_prop.name == "BaseQuantityConsumed":
|
||||
layout.prop(duration_prop, "years", text="Y")
|
||||
@@ -414,7 +422,7 @@ def draw_productivity_ui(self, context):
|
||||
layout.prop(duration_prop, "minutes", text="Min")
|
||||
layout.prop(duration_prop, "seconds", text="S")
|
||||
|
||||
productivity_props = context.scene.BIMResourceProductivity
|
||||
productivity_props = tool.Resource.get_resource_props().productivity
|
||||
grid = self.layout.grid_flow(columns=2, even_columns=False, even_rows=False, align=False)
|
||||
col1 = grid.column(align=False)
|
||||
col2 = grid.column(align=False)
|
||||
|
||||
@@ -106,6 +106,8 @@ class SelectFilterElements(bpy.types.Operator):
|
||||
query = tool.Search.get_query_for_selected_elements()
|
||||
filter_groups[self.group_index].filters[self.index].value = query
|
||||
if query.startswith("bpy.data.texts['"):
|
||||
text: bpy.types.Text = eval(query)
|
||||
name = text.name
|
||||
self.report({"INFO"}, f'List of Global Ids was saved to the text file "{name}" in the current .blend file')
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -813,8 +815,8 @@ class SelectSimilar(Operator, tool.Ifc.Operator):
|
||||
def execute(self, context):
|
||||
self.calculated_sum = 0 # reset if run before
|
||||
key = "predefined_type" if self.key == "PredefinedType" else self.key
|
||||
dprops = tool.Drawing.get_document_props()
|
||||
tolerance = dprops.tolerance
|
||||
prefs = tool.Blender.get_addon_preferences()
|
||||
tolerance = prefs.doc.tolerance
|
||||
formatted_tolerance = f"{tolerance:.{max(0, -int(f'{tolerance:.1e}'.split('e')[-1])) if tolerance < 1 else 1}f}"
|
||||
|
||||
if self.calculate_sum:
|
||||
|
||||
@@ -33,7 +33,7 @@ from bpy.props import (
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, Literal
|
||||
|
||||
|
||||
def get_element_key(self: "BIMSearchProperties", context: bpy.types.Context) -> list[tuple[str, str, str]]:
|
||||
|
||||
@@ -386,12 +386,11 @@ class TaskICOMData:
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def can_active_resource_be_assigned(cls):
|
||||
resource_props = bpy.context.scene.BIMResourceProperties
|
||||
resource_tprops = bpy.context.scene.BIMResourceTreeProperties
|
||||
total_resources = len(resource_tprops.resources)
|
||||
if total_resources and resource_props.active_resource_index < total_resources:
|
||||
resource_id = resource_tprops.resources[resource_props.active_resource_index].ifc_definition_id
|
||||
def can_active_resource_be_assigned(cls) -> bool:
|
||||
props = tool.Resource.get_resource_props()
|
||||
active_resource = props.active_resource
|
||||
if active_resource:
|
||||
resource_id = active_resource.ifc_definition_id
|
||||
return not tool.Ifc.get().by_id(resource_id).is_a("IfcCrewResource")
|
||||
return False
|
||||
|
||||
|
||||
@@ -308,8 +308,9 @@ def updateAssignedResourceName(self, context):
|
||||
pass
|
||||
|
||||
|
||||
def updateAssignedResourceUsage(self, context):
|
||||
if not context.scene.BIMResourceProperties.is_resource_update_enabled:
|
||||
def updateAssignedResourceUsage(self: "TaskResource", context: object) -> None:
|
||||
props = tool.Resource.get_resource_props()
|
||||
if not props.is_resource_update_enabled:
|
||||
return
|
||||
if not self.schedule_usage:
|
||||
return
|
||||
|
||||
@@ -581,7 +581,7 @@ class EnableEditingStructuralLoadCase(bpy.types.Operator):
|
||||
self.props.active_load_case_id = self.load_case
|
||||
self.props.load_case_editing_type = "ATTRIBUTES"
|
||||
self.props.load_case_attributes.clear()
|
||||
bonsai.bim.helper.import_attributes2(
|
||||
bonsai.bim.helper.import_attributes(
|
||||
tool.Ifc.get().by_id(self.load_case),
|
||||
self.props.load_case_attributes,
|
||||
callback=self.import_attributes_callback,
|
||||
@@ -782,7 +782,7 @@ class EnableEditingStructuralLoad(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
props = tool.Structural.get_structural_props()
|
||||
props.structural_load_attributes.clear()
|
||||
bonsai.bim.helper.import_attributes2(
|
||||
bonsai.bim.helper.import_attributes(
|
||||
tool.Ifc.get().by_id(self.structural_load), props.structural_load_attributes
|
||||
)
|
||||
props.active_structural_load_id = self.structural_load
|
||||
|
||||
@@ -753,7 +753,7 @@ class EnableEditingSurfaceStyle(bpy.types.Operator):
|
||||
|
||||
if attributes is not None:
|
||||
attributes.clear()
|
||||
bonsai.bim.helper.import_attributes2(surface_style or self.ifc_class, attributes, callback)
|
||||
bonsai.bim.helper.import_attributes(surface_style or self.ifc_class, attributes, callback)
|
||||
|
||||
material = tool.Ifc.get_object(style)
|
||||
msprops = tool.Style.get_material_style_props(material)
|
||||
|
||||
@@ -422,7 +422,7 @@ class EnableEditingZone(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
props = tool.System.get_zone_props()
|
||||
props.attributes.clear()
|
||||
bonsai.bim.helper.import_attributes2(tool.Ifc.get().by_id(self.zone), props.attributes)
|
||||
bonsai.bim.helper.import_attributes(tool.Ifc.get().by_id(self.zone), props.attributes)
|
||||
props.is_editing = self.zone
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -39,8 +39,8 @@ class TesterData:
|
||||
return bool(tool.Tester.report)
|
||||
|
||||
@classmethod
|
||||
def specification(cls) -> list[ifctester.reporter.ResultsSpecification]:
|
||||
def specification(cls) -> ifctester.reporter.ResultsSpecification:
|
||||
if not tool.Tester.report:
|
||||
return []
|
||||
props = bpy.context.scene.IfcTesterProperties
|
||||
props = tool.Tester.get_tester_props()
|
||||
return tool.Tester.report[props.active_specification_index]
|
||||
|
||||
@@ -39,7 +39,7 @@ class ExecuteIfcTester(bpy.types.Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
props = context.scene.IfcTesterProperties
|
||||
props = tool.Tester.get_tester_props()
|
||||
if not props.should_load_from_memory and not props.ifc_files.single_file:
|
||||
cls.poll_message_set("Select an IFC file or use 'load from memory' if it's loaded in Bonsai.")
|
||||
return False
|
||||
@@ -48,7 +48,7 @@ class ExecuteIfcTester(bpy.types.Operator):
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.IfcTesterProperties
|
||||
props = tool.Tester.get_tester_props()
|
||||
|
||||
props.specifications.clear()
|
||||
|
||||
@@ -69,7 +69,7 @@ class ExecuteIfcTester(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
def execute_tester(self, ifc_data: ifcopenshell.file, ifc_path: str, specs_path: str) -> Union[set[str], None]:
|
||||
props = bpy.context.scene.IfcTesterProperties
|
||||
props = tool.Tester.get_tester_props()
|
||||
props.failed_entities.clear()
|
||||
|
||||
# No need for if-statement, just postponing lots of diffs.
|
||||
@@ -132,7 +132,7 @@ class SelectRequirement(bpy.types.Operator):
|
||||
req_index: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.IfcTesterProperties
|
||||
props = tool.Tester.get_tester_props()
|
||||
report = tool.Tester.report
|
||||
props.old_index = self.spec_index
|
||||
failed_entities = report[self.spec_index]["requirements"][self.req_index]["failed_entities"]
|
||||
@@ -169,7 +169,7 @@ class SelectFailedEntities(bpy.types.Operator):
|
||||
req_index: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.IfcTesterProperties
|
||||
props = tool.Tester.get_tester_props()
|
||||
report = tool.Tester.report
|
||||
props.old_index = self.spec_index
|
||||
failed_entities = report[self.spec_index]["requirements"][self.req_index]["failed_entities"]
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import bpy
|
||||
from bonsai.bim.module.tester.data import TesterData
|
||||
from bonsai.bim.prop import StrProperty, MultipleFileSelect
|
||||
from bpy.types import PropertyGroup
|
||||
@@ -31,21 +34,29 @@ from bpy.props import (
|
||||
)
|
||||
|
||||
|
||||
def update_active_specification_index(self, context):
|
||||
def update_active_specification_index(self: "IfcTesterProperties", context: bpy.types.Context) -> None:
|
||||
TesterData.load()
|
||||
|
||||
|
||||
class Specification(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
description: StringProperty(name="Description")
|
||||
status: BoolProperty(default=False, name="Status")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
description: str
|
||||
status: bool
|
||||
|
||||
|
||||
class FailedEntities(PropertyGroup):
|
||||
ifc_id: IntProperty(name="IFC ID")
|
||||
element: StringProperty(name="Element")
|
||||
reason: StringProperty(name="Reason")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
ifc_id: int
|
||||
element: str
|
||||
reason: str
|
||||
|
||||
|
||||
class IfcTesterProperties(PropertyGroup):
|
||||
specs: PointerProperty(type=MultipleFileSelect)
|
||||
@@ -66,3 +77,18 @@ class IfcTesterProperties(PropertyGroup):
|
||||
failed_entities: CollectionProperty(name="FailedEntities", type=FailedEntities)
|
||||
has_entities: BoolProperty(default=False, name="")
|
||||
n_entities: IntProperty(name="", default=0)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
specs: MultipleFileSelect
|
||||
ifc_files: MultipleFileSelect
|
||||
should_load_from_memory: bool
|
||||
generate_html_report: bool
|
||||
generate_ods_report: bool
|
||||
flag: bool
|
||||
active_specification_index: int
|
||||
old_index: int
|
||||
active_failed_entity_index: int
|
||||
specifications: bpy.types.bpy_prop_collection_idprop[Specification]
|
||||
failed_entities: bpy.types.bpy_prop_collection_idprop[FailedEntities]
|
||||
has_entities: bool
|
||||
n_entities: int
|
||||
|
||||
@@ -16,10 +16,22 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bonsai.tool as tool
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import bpy
|
||||
from bpy.types import Panel, UIList
|
||||
import bonsai.tool as tool
|
||||
from bonsai.bim.module.tester.data import TesterData
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bonsai.bim.module.tester.prop import (
|
||||
FailedEntities,
|
||||
IfcTesterProperties,
|
||||
Specification,
|
||||
)
|
||||
|
||||
|
||||
class BIM_PT_tester(Panel):
|
||||
bl_label = "IFC Tester"
|
||||
@@ -34,8 +46,9 @@ class BIM_PT_tester(Panel):
|
||||
if not TesterData.is_loaded:
|
||||
TesterData.load()
|
||||
|
||||
assert self.layout
|
||||
self.layout.use_property_split = True
|
||||
props = context.scene.IfcTesterProperties
|
||||
props = tool.Tester.get_tester_props()
|
||||
|
||||
if tool.Ifc.get():
|
||||
row = self.layout.row()
|
||||
@@ -68,16 +81,17 @@ class BIM_PT_tester(Panel):
|
||||
"active_specification_index",
|
||||
)
|
||||
|
||||
self.draw_editable_ui(context)
|
||||
self.draw_editable_ui()
|
||||
row = self.layout.row()
|
||||
row.operator("bim.export_bcf", text="Export BCF", icon="EXPORT")
|
||||
|
||||
def draw_editable_ui(self, context):
|
||||
props = context.scene.IfcTesterProperties
|
||||
def draw_editable_ui(self) -> None:
|
||||
props = tool.Tester.get_tester_props()
|
||||
specification = TesterData.data["specification"]
|
||||
|
||||
n_requirements = len(specification["requirements"])
|
||||
|
||||
assert self.layout
|
||||
row = self.layout.row()
|
||||
row.label(
|
||||
text=f'Passed: {specification["total_checks_pass"]}/{specification["total_checks"]} ({specification["percent_checks_pass"]}%)'
|
||||
@@ -114,7 +128,16 @@ class BIM_PT_tester(Panel):
|
||||
|
||||
|
||||
class BIM_UL_tester_specifications(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
def draw_item(
|
||||
self,
|
||||
context: bpy.types.Context,
|
||||
layout: bpy.types.UILayout,
|
||||
data: IfcTesterProperties,
|
||||
item: Specification,
|
||||
icon,
|
||||
active_data,
|
||||
active_propname,
|
||||
) -> None:
|
||||
if item:
|
||||
row = layout.split(factor=0.3, align=True)
|
||||
row.label(text=item.name, icon="CHECKMARK" if item.status else "CANCEL")
|
||||
@@ -122,12 +145,20 @@ class BIM_UL_tester_specifications(UIList):
|
||||
|
||||
|
||||
class BIM_UL_tester_failed_entities(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
props = context.scene.IfcTesterProperties
|
||||
def draw_item(
|
||||
self,
|
||||
context: bpy.types.Context,
|
||||
layout: bpy.types.UILayout,
|
||||
data: IfcTesterProperties,
|
||||
item: FailedEntities,
|
||||
icon,
|
||||
active_data,
|
||||
active_propname,
|
||||
) -> None:
|
||||
if item:
|
||||
row = layout.row(align=True)
|
||||
row.label(text=item.element)
|
||||
row.label(text=item.reason)
|
||||
if props.should_load_from_memory:
|
||||
if data.should_load_from_memory:
|
||||
op = row.operator("bim.select_entity", text="", icon="RESTRICT_SELECT_OFF")
|
||||
op.ifc_id = item.ifc_id
|
||||
|
||||
@@ -57,13 +57,13 @@ class AssignType(bpy.types.Operator, tool.Ifc.Operator):
|
||||
related_objects = [bpy.data.objects[self.related_object]]
|
||||
else:
|
||||
related_objects = tool.Blender.get_selected_objects()
|
||||
model_props = tool.Model.get_model_props()
|
||||
prefs = tool.Blender.get_addon_preferences()
|
||||
for obj in related_objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element or not element.is_a("IfcObject"):
|
||||
continue
|
||||
core.assign_type(tool.Ifc, tool.Type, element=element, type=relating_type)
|
||||
if model_props.occurrence_name_style == "TYPE":
|
||||
if prefs.occurrence_name_style == "TYPE":
|
||||
obj.name = tool.Model.generate_occurrence_name(relating_type, element.is_a())
|
||||
|
||||
|
||||
|
||||
@@ -157,7 +157,7 @@ class SelectURIAttribute(bpy.types.Operator, ImportHelper):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Select a local file"
|
||||
attribute_data_path: bpy.props.StringProperty(name="Data Path") # pyright: ignore[reportRedeclaration]
|
||||
"""Full data path to Attribute."""
|
||||
"""Full data path to `Attribute`/string property."""
|
||||
use_relative_path: bpy.props.BoolProperty( # pyright: ignore[reportRedeclaration]
|
||||
name="Use Relative Path",
|
||||
default=False,
|
||||
@@ -168,8 +168,16 @@ class SelectURIAttribute(bpy.types.Operator, ImportHelper):
|
||||
use_relative_path: bool
|
||||
|
||||
def execute(self, context):
|
||||
attribute: Attribute = eval(self.attribute_data_path)
|
||||
attribute.string_value = tool.Ifc.get_uri(self.filepath, use_relative_path=self.use_relative_path)
|
||||
from bonsai.bim.prop import Attribute
|
||||
|
||||
filepath = tool.Ifc.get_uri(self.filepath, use_relative_path=self.use_relative_path)
|
||||
|
||||
attribute = eval(self.attribute_data_path)
|
||||
if isinstance(attribute, Attribute):
|
||||
attribute.string_value = filepath
|
||||
else:
|
||||
bpy_struct_path, _, attr_name = self.attribute_data_path.rpartition(".")
|
||||
setattr(eval(bpy_struct_path), attr_name, filepath)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -235,19 +243,12 @@ class SelectDir(bpy.types.Operator, ImportHelper):
|
||||
bl_description = "Open a file browser to choose the directory"
|
||||
data_path: bpy.props.StringProperty(name="Data Path")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
data_path: str
|
||||
|
||||
def execute(self, context):
|
||||
crumbs = self.data_path.split(".")
|
||||
if crumbs[0] == "preferences":
|
||||
crumbs.pop(0)
|
||||
data = tool.Blender.get_addon_preferences()
|
||||
else:
|
||||
data = context
|
||||
while crumbs:
|
||||
crumb = crumbs.pop(0)
|
||||
if crumbs:
|
||||
data = getattr(data, crumb)
|
||||
else:
|
||||
setattr(data, crumb, os.path.dirname(self.filepath))
|
||||
data, attr = tool.Blender.resolve_data_path_to_data_attr(self.data_path)
|
||||
setattr(data, attr, os.path.dirname(self.filepath))
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
import os
|
||||
import bpy
|
||||
import json
|
||||
import platformdirs
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.pset
|
||||
import ifcopenshell.util.unit
|
||||
@@ -128,20 +127,6 @@ def update_schema_dir(self: "BIMProperties", context: bpy.types.Context) -> None
|
||||
bonsai.bim.schema.ifc.schema_dir = bim_props.schema_dir
|
||||
|
||||
|
||||
def update_data_dir(self: "BIMProperties", context: bpy.types.Context) -> None:
|
||||
import bonsai.bim.schema
|
||||
|
||||
bim_props = tool.Blender.get_bim_props()
|
||||
bonsai.bim.schema.ifc.data_dir = bim_props.data_dir
|
||||
|
||||
|
||||
def update_cache_dir(self: "BIMProperties", context: bpy.types.Context) -> None:
|
||||
import bonsai.bim.schema
|
||||
|
||||
bim_props = tool.Blender.get_bim_props()
|
||||
bonsai.bim.schema.ifc.cache_dir = bim_props.cache_dir
|
||||
|
||||
|
||||
def update_section_color(self: "BIMProperties", context: bpy.types.Context) -> None:
|
||||
section_node_group = bpy.data.node_groups.get("Section Override")
|
||||
if section_node_group is None:
|
||||
@@ -521,16 +506,7 @@ class BIMProperties(PropertyGroup):
|
||||
schema_dir: StringProperty(
|
||||
default=os.path.join(cwd, "schema") + os.path.sep, name="Schema Directory", update=update_schema_dir
|
||||
)
|
||||
data_dir: StringProperty(
|
||||
default=(platformdirs.user_data_path("bonsai", roaming=True, ensure_exists=True) / "data").__str__(),
|
||||
name="Data Directory",
|
||||
update=update_data_dir,
|
||||
)
|
||||
cache_dir: StringProperty(
|
||||
default=platformdirs.user_cache_dir("bonsai"), name="Cache Directory", update=update_cache_dir
|
||||
)
|
||||
has_blend_warning: BoolProperty(name="Has Blend Warning", default=False)
|
||||
pset_dir: StringProperty(default=os.path.join("psets") + os.path.sep, name="Default Psets Directory")
|
||||
ifc_file: StringProperty(name="IFC File")
|
||||
last_transaction: StringProperty(name="Last Transaction")
|
||||
should_section_selected_objects: BoolProperty(name="Section Selected Objects", default=False)
|
||||
@@ -586,8 +562,6 @@ class BIMProperties(PropertyGroup):
|
||||
if TYPE_CHECKING:
|
||||
is_dirty: bool
|
||||
schema_dir: str
|
||||
data_dir: str
|
||||
cache_dir: str
|
||||
has_blend_warning: bool
|
||||
pset_dir: str
|
||||
ifc_file: str
|
||||
|
||||
@@ -24,6 +24,10 @@ import bpy_restrict_state
|
||||
|
||||
|
||||
class IfcSchema:
|
||||
data_dir: str
|
||||
cache_dir: str
|
||||
schema_dir: str
|
||||
|
||||
def __init__(self, schema_identifier="IFC4"):
|
||||
if schema_identifier not in ("IFC2X3", "IFC4", "IFC4X3_ADD2"):
|
||||
schema_identifier = "IFC4"
|
||||
|
||||
+197
-32
@@ -19,6 +19,7 @@
|
||||
import os
|
||||
import bpy
|
||||
import platform
|
||||
import platformdirs
|
||||
import bonsai.bim.helper
|
||||
from pathlib import Path
|
||||
from bpy.types import Panel
|
||||
@@ -35,11 +36,18 @@ import bonsai.bim
|
||||
import bonsai.tool as tool
|
||||
from ifcopenshell.util.file import IfcHeaderExtractor
|
||||
from bonsai.bim.prop import Attribute
|
||||
from typing import Optional, TYPE_CHECKING
|
||||
from typing import Optional, TYPE_CHECKING, Literal
|
||||
from natsort import natsorted
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bonsai.bim.prop import ObjProperty
|
||||
from bonsai.bim.module.project.prop import BIMProjectProperties
|
||||
|
||||
|
||||
class IFCFileSelector:
|
||||
layout: bpy.types.UILayout
|
||||
|
||||
# Avoid overriding blender prop annotations at runtime.
|
||||
if TYPE_CHECKING:
|
||||
filepath: str
|
||||
@@ -76,6 +84,7 @@ class IFCFileSelector:
|
||||
assert isinstance(context.space_data, bpy.types.SpaceFileBrowser)
|
||||
# Access filepath & Directory https://blender.stackexchange.com/a/207665
|
||||
params = context.space_data.params
|
||||
assert params
|
||||
# Decode byte string https://stackoverflow.com/a/47737082/
|
||||
directory = Path(params.directory.decode("utf-8"))
|
||||
filepath = os.path.join(directory, params.filename)
|
||||
@@ -122,6 +131,7 @@ class BIM_PT_section_plane(Panel):
|
||||
bl_parent_id = "BIM_PT_tab_sandbox"
|
||||
|
||||
def draw(self, context):
|
||||
assert self.layout
|
||||
layout = self.layout
|
||||
layout.use_property_split = True
|
||||
props = tool.Blender.get_bim_props()
|
||||
@@ -145,6 +155,7 @@ class BIM_PT_section_with_cappings(Panel):
|
||||
bl_parent_id = "BIM_PT_tab_sandbox"
|
||||
|
||||
def draw(self, context):
|
||||
assert self.layout
|
||||
layout = self.layout
|
||||
wm = context.window_manager
|
||||
row = layout.row(align=True)
|
||||
@@ -165,17 +176,26 @@ class BIM_PT_section_with_cappings(Panel):
|
||||
props,
|
||||
"clipping_planes",
|
||||
props,
|
||||
"clipping_planes_active",
|
||||
"clipping_planes_active_index",
|
||||
)
|
||||
|
||||
if props.clipping_planes_active < len(props.clipping_planes):
|
||||
active_clipping_plane_obj = props.clipping_planes[props.clipping_planes_active].obj
|
||||
if active_clipping_plane := props.active_clipping_plane:
|
||||
active_clipping_plane_obj = active_clipping_plane.obj
|
||||
box.prop(active_clipping_plane_obj, "location")
|
||||
box.prop(active_clipping_plane_obj, "rotation_euler")
|
||||
|
||||
|
||||
class BIM_UL_clipping_plane(bpy.types.UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
def draw_item(
|
||||
self,
|
||||
context,
|
||||
layout: bpy.types.UILayout,
|
||||
data: "BIMProjectProperties",
|
||||
item: "ObjProperty",
|
||||
icon,
|
||||
active_data,
|
||||
active_propname,
|
||||
) -> None:
|
||||
if item and item.obj:
|
||||
obj = item.obj
|
||||
row = layout.row(align=True)
|
||||
@@ -189,20 +209,112 @@ class BIM_UL_clipping_plane(bpy.types.UIList):
|
||||
|
||||
|
||||
class BIM_UL_generic(bpy.types.UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
def draw_item(
|
||||
self,
|
||||
context,
|
||||
layout: bpy.types.UILayout,
|
||||
data: bpy.types.PropertyGroup,
|
||||
item: bpy.types.PropertyGroup,
|
||||
icon,
|
||||
active_data,
|
||||
active_propname,
|
||||
) -> None:
|
||||
if item:
|
||||
layout.prop(item, "name", text="", emboss=False)
|
||||
else:
|
||||
layout.label(text="", translate=False)
|
||||
|
||||
|
||||
class BIM_UL_topics(bpy.types.UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
ob = data
|
||||
if item:
|
||||
layout.prop(item, "title", text="", emboss=False)
|
||||
else:
|
||||
layout.label(text="", translate=False)
|
||||
class DocPreferences(bpy.types.PropertyGroup):
|
||||
sheets_dir: StringProperty(
|
||||
default=os.path.join("sheets") + os.path.sep,
|
||||
name="Default Sheets Directory",
|
||||
)
|
||||
layouts_dir: StringProperty(
|
||||
default=os.path.join("layouts") + os.path.sep,
|
||||
name="Default Layouts Directory",
|
||||
)
|
||||
titleblocks_dir: StringProperty(
|
||||
default=os.path.join("layouts", "titleblocks") + os.path.sep,
|
||||
name="Default Titleblocks Directory",
|
||||
)
|
||||
drawings_dir: StringProperty(
|
||||
default=os.path.join("drawings") + os.path.sep,
|
||||
name="Default Drawings Directory",
|
||||
)
|
||||
stylesheet_path: StringProperty(
|
||||
default=os.path.join("drawings", "assets", "default.css"),
|
||||
name="Default Stylesheet",
|
||||
)
|
||||
schedules_stylesheet_path: StringProperty(
|
||||
default=os.path.join("drawings", "assets", "schedule.css"),
|
||||
name="Default Stylesheet for Schedules",
|
||||
)
|
||||
markers_path: StringProperty(
|
||||
default=os.path.join("drawings", "assets", "markers.svg"),
|
||||
name="Default Markers",
|
||||
)
|
||||
symbols_path: StringProperty(
|
||||
default=os.path.join("drawings", "assets", "symbols.svg"),
|
||||
name="Default Symbols",
|
||||
)
|
||||
patterns_path: StringProperty(
|
||||
default=os.path.join("drawings", "assets", "patterns.svg"),
|
||||
name="Default Patterns",
|
||||
)
|
||||
shadingstyles_path: StringProperty(
|
||||
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",
|
||||
)
|
||||
magic_font_scale: bpy.props.FloatProperty(
|
||||
default=0.004118616,
|
||||
name="Font Scale Factor",
|
||||
)
|
||||
imperial_precision: StringProperty(
|
||||
default="1/32",
|
||||
name="Imperial Precision",
|
||||
)
|
||||
tolerance: bpy.props.FloatProperty(
|
||||
default=0.00001,
|
||||
name="A tolerance used when selecting objects",
|
||||
)
|
||||
classes_to_wireframe: StringProperty(
|
||||
default="IfcVirtualElement",
|
||||
name="Classes to Wireframe",
|
||||
description="Upon import, these classes will display as wireframe.\nEx: IfcVirtualelement, IfcSpace",
|
||||
)
|
||||
classes_no_cut: StringProperty(
|
||||
default="IfcVirtualElement, IfcSpace",
|
||||
name="Classes that are not cut",
|
||||
description="The cut decoractor will be turned off for these classes\nEx: IfcVirtualelement, IfcSpace",
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
sheets_dir: str
|
||||
layouts_dir: str
|
||||
titleblocks_dir: str
|
||||
drawings_dir: str
|
||||
stylesheet_path: str
|
||||
schedules_stylesheet_path: str
|
||||
markers_path: str
|
||||
symbols_path: str
|
||||
patterns_path: str
|
||||
shadingstyles_path: str
|
||||
shadingstyle_default: str
|
||||
drawing_font: str
|
||||
magic_font_scale: float
|
||||
imperial_precision: str
|
||||
tolerance: float
|
||||
classes_to_wireframe: str
|
||||
classes_no_cut: str
|
||||
|
||||
|
||||
class BIM_ADDON_preferences(bpy.types.AddonPreferences):
|
||||
@@ -320,6 +432,53 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
|
||||
description="When modifying openings, other elements of the model will display with some transparency.\n0 is fully transparent and 100 is fully opaque",
|
||||
)
|
||||
|
||||
bsdd_load_preview_dictionaries: BoolProperty(
|
||||
name="Load Preview Dictionaries", description="Load dictionaries marked as Preview status", default=False
|
||||
)
|
||||
bsdd_load_inactive_dictionaries: BoolProperty(
|
||||
name="Load Inactive Dictionaries", description="Load dictionaries marked as Inactive status", default=False
|
||||
)
|
||||
bsdd_load_test_dictionaries: BoolProperty(
|
||||
name="Load Test Dictionaries", description="Load dictionaries that are for testing only", default=False
|
||||
)
|
||||
should_disable_undo_on_save: BoolProperty(
|
||||
name="Disable Undo When Saving (Faster saves, no undo for you!)", default=False
|
||||
)
|
||||
should_stream: BoolProperty(name="Stream Data From IFC-SPF (Only for advanced users)", default=False)
|
||||
occurrence_name_style: bpy.props.EnumProperty(
|
||||
items=[("CLASS", "By Class", ""), ("TYPE", "By Type", ""), ("CUSTOM", "Custom", "")],
|
||||
name="Occurrence Name Style",
|
||||
)
|
||||
occurrence_name_function: bpy.props.StringProperty(
|
||||
name="Occurrence Name Function",
|
||||
description="Code that will be evaluated to generate occurrence name for CUSTOM occurrence name style",
|
||||
)
|
||||
|
||||
def update_data_dir(self, context: bpy.types.Context) -> None:
|
||||
import bonsai.bim.schema
|
||||
|
||||
bonsai.bim.schema.ifc.data_dir = self.data_dir
|
||||
|
||||
def update_cache_dir(self, context: bpy.types.Context) -> None:
|
||||
import bonsai.bim.schema
|
||||
|
||||
bonsai.bim.schema.ifc.cache_dir = self.cache_dir
|
||||
|
||||
data_dir: StringProperty(
|
||||
default=(platformdirs.user_data_path("bonsai", roaming=True, ensure_exists=True) / "data").__str__(),
|
||||
name="Data Directory",
|
||||
update=update_data_dir,
|
||||
)
|
||||
cache_dir: StringProperty(
|
||||
default=platformdirs.user_cache_dir("bonsai"), name="Cache Directory", update=update_cache_dir
|
||||
)
|
||||
|
||||
pset_dir: StringProperty(
|
||||
default=os.path.join("psets") + os.path.sep,
|
||||
name="Default Psets Directory",
|
||||
)
|
||||
doc: bpy.props.PointerProperty(type=DocPreferences)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
svg2pdf_command: str
|
||||
svg2dxf_command: str
|
||||
@@ -341,6 +500,17 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
|
||||
decorator_color_error: tuple[float, float, float, float]
|
||||
decorator_color_background: tuple[float, float, float, float]
|
||||
opening_focus_opacity: int
|
||||
bsdd_load_preview_dictionaries: bool
|
||||
bsdd_load_inactive_dictionaries: bool
|
||||
bsdd_load_test_dictionaries: bool
|
||||
should_disable_undo_on_save: bool
|
||||
should_stream: bool
|
||||
occurrence_name_style: Literal["CLASS", "TYPE", "CUSTOM"]
|
||||
occurrence_name_function: str
|
||||
data_dir: str
|
||||
cache_dir: str
|
||||
pset_dir: str
|
||||
doc: DocPreferences
|
||||
|
||||
def draw(self, context: bpy.types.Context) -> None:
|
||||
layout = self.layout
|
||||
@@ -381,29 +551,26 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
|
||||
layout.prop(self, "spatial_elements_unselectable")
|
||||
|
||||
def draw_model_settings(self, layout: bpy.types.UILayout, context: bpy.types.Context) -> None:
|
||||
props = tool.Model.get_model_props()
|
||||
layout.prop(props, "occurrence_name_style")
|
||||
if props.occurrence_name_style == "CUSTOM":
|
||||
layout.prop(props, "occurrence_name_function")
|
||||
layout.prop(self, "occurrence_name_style")
|
||||
if self.occurrence_name_style == "CUSTOM":
|
||||
layout.prop(self, "occurrence_name_function")
|
||||
|
||||
def draw_directories(self, layout: bpy.types.UILayout, context: bpy.types.Context) -> None:
|
||||
props = tool.Blender.get_bim_props()
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "data_dir")
|
||||
row.operator("bim.select_dir", icon="FILE_FOLDER", text="").data_path = "scene.BIMProperties.data_dir"
|
||||
row.prop(self, "data_dir")
|
||||
row.operator("bim.select_dir", icon="FILE_FOLDER", text="").data_path = "preferences.data_dir"
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "cache_dir")
|
||||
row.operator("bim.select_dir", icon="FILE_FOLDER", text="").data_path = "scene.BIMProperties.cache_dir"
|
||||
row.prop(self, "cache_dir")
|
||||
row.operator("bim.select_dir", icon="FILE_FOLDER", text="").data_path = "preferences.cache_dir"
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(self, "tmp_dir")
|
||||
row.operator("bim.select_dir", icon="FILE_FOLDER", text="").data_path = "preferences.tmp_dir"
|
||||
|
||||
def draw_drawing_settings(self, layout: bpy.types.UILayout, context: bpy.types.Context) -> None:
|
||||
props = tool.Blender.get_bim_props()
|
||||
layout.prop(props, "pset_dir")
|
||||
dprops = tool.Drawing.get_document_props()
|
||||
layout.prop(self, "pset_dir")
|
||||
dprops = self.doc
|
||||
layout.prop(dprops, "sheets_dir")
|
||||
layout.prop(dprops, "layouts_dir")
|
||||
layout.prop(dprops, "titleblocks_dir")
|
||||
@@ -433,14 +600,12 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
|
||||
|
||||
def draw_other_settings(self, layout: bpy.types.UILayout, context: bpy.types.Context) -> None:
|
||||
layout.prop(self, "opening_focus_opacity")
|
||||
props = tool.Project.get_project_props()
|
||||
layout.prop(props, "should_disable_undo_on_save")
|
||||
layout.prop(props, "should_stream")
|
||||
bprops = tool.Bsdd.get_bsdd_props()
|
||||
layout.prop(self, "should_disable_undo_on_save")
|
||||
layout.prop(self, "should_stream")
|
||||
layout.label(text="bSDD:")
|
||||
layout.prop(bprops, "load_preview_dictionaries")
|
||||
layout.prop(bprops, "load_inactive_dictionaries")
|
||||
layout.prop(bprops, "load_test_dictionaries")
|
||||
layout.prop(self, "bsdd_load_preview_dictionaries")
|
||||
layout.prop(self, "bsdd_load_inactive_dictionaries")
|
||||
layout.prop(self, "bsdd_load_test_dictionaries")
|
||||
|
||||
|
||||
# Scene panel groups
|
||||
|
||||
@@ -30,7 +30,6 @@ def add_context(
|
||||
context_type: Optional[str] = None,
|
||||
context_identifier: Optional[str] = None,
|
||||
target_view: Optional[str] = None,
|
||||
target_scale: Optional[float] = None,
|
||||
parent: Optional[str] = None,
|
||||
) -> ifcopenshell.entity_instance:
|
||||
return ifc.run(
|
||||
@@ -38,7 +37,6 @@ def add_context(
|
||||
context_type=context_type,
|
||||
context_identifier=context_identifier,
|
||||
target_view=target_view,
|
||||
target_scale=target_scale,
|
||||
parent=parent,
|
||||
)
|
||||
|
||||
|
||||
@@ -404,10 +404,14 @@ def export_cost_schedules(
|
||||
|
||||
|
||||
def export_cost_schedules_to_pdf(
|
||||
cost: type[tool.Cost], filepath: str, cost_schedule: ifcopenshell.entity_instance, options: dict
|
||||
cost: type[tool.Cost],
|
||||
filepath: str,
|
||||
cost_schedule: ifcopenshell.entity_instance,
|
||||
options: dict,
|
||||
force_schedule_type: str = "",
|
||||
):
|
||||
cost.play_sound()
|
||||
return cost.export_cost_schedules_to_pdf(filepath, cost_schedule, options)
|
||||
return cost.export_cost_schedules_to_pdf(filepath, cost_schedule, options, force_schedule_type)
|
||||
|
||||
|
||||
def clear_cost_item_assignments(
|
||||
|
||||
@@ -543,7 +543,6 @@ class Material:
|
||||
def ensure_material_assigned(cls, elements, material_type, material): pass
|
||||
def ensure_material_unassigned(cls, elements): pass
|
||||
def ensure_new_material_set_is_valid(cls, material): pass
|
||||
def get_active_material_item(cls): pass
|
||||
def get_active_material_type(cls): pass
|
||||
def get_default_material(cls): pass
|
||||
def get_elements_by_material(cls, material): pass
|
||||
@@ -933,7 +932,7 @@ class Spatial:
|
||||
def filter_products(cls, products, action): pass
|
||||
def get_active_container(cls): pass
|
||||
def get_container(cls, element): pass
|
||||
def get_decomposed_elements(cls, container): pass
|
||||
def get_decomposed_elements(cls, container, recursive): pass
|
||||
def get_object_matrix(cls, obj): pass
|
||||
def get_relative_object_matrix(cls, target_obj, relative_to_obj): pass
|
||||
def get_selected_product_types(cls): pass
|
||||
|
||||
@@ -68,7 +68,7 @@ class Attribute(bonsai.core.tool.Attribute):
|
||||
@classmethod
|
||||
def import_entity_attributes(cls, entity: ifcopenshell.entity_instance) -> None:
|
||||
props = cls.get_explorer_props()
|
||||
helper.import_attributes2(entity, props.entity_attributes)
|
||||
helper.import_attributes(entity, props.entity_attributes)
|
||||
|
||||
@classmethod
|
||||
def export_entity_attributes(cls) -> dict[str, Any]:
|
||||
|
||||
@@ -246,9 +246,9 @@ class Blender(bonsai.core.tool.Blender):
|
||||
cost_props = tool.Cost.get_cost_props()
|
||||
return cost_props.cost_items[cost_props.active_cost_item_index].ifc_definition_id
|
||||
elif obj_type == "Resource":
|
||||
return context.scene.BIMResourceTreeProperties.resources[
|
||||
context.scene.BIMResourceProperties.active_resource_index
|
||||
].ifc_definition_id
|
||||
active_resource = tool.Resource.get_resource_props().active_resource
|
||||
assert active_resource
|
||||
return active_resource.ifc_definition_id
|
||||
elif obj_type == "Profile":
|
||||
props = tool.Profile.get_profile_props()
|
||||
return props.profiles[props.active_profile_index].ifc_definition_id
|
||||
@@ -1550,6 +1550,51 @@ class Blender(bonsai.core.tool.Blender):
|
||||
return repr(bpy_prop)
|
||||
return repr(bpy_struct)
|
||||
|
||||
@classmethod
|
||||
def resolve_data_path_to_data_attr(cls, data_path: str) -> tuple[bpy.types.bpy_struct, str]:
|
||||
"""
|
||||
:param data_path: Non-full data path to attribute.
|
||||
Examples:
|
||||
- `preferences.prop_group.string_prop` (`preferences` would mean addon preferences)
|
||||
- `scene.string_prop` (`scene` can be any member of `Context`)
|
||||
|
||||
:return: Resolved tuple of Blender Struct and property name.
|
||||
Examples:
|
||||
- `(preferences.prop_group, "string_prop)`
|
||||
- `(scene, "string_prop)`
|
||||
|
||||
"""
|
||||
# Get data to modify.
|
||||
base_path, _, data_path_ = data_path.partition(".")
|
||||
if base_path == "preferences":
|
||||
data = tool.Blender.get_addon_preferences()
|
||||
data_path = data_path_
|
||||
else:
|
||||
data = bpy.context
|
||||
|
||||
# Get property group if available.
|
||||
base_path, _, attr = data_path.rpartition(".")
|
||||
if base_path:
|
||||
data = data.path_resolve(base_path)
|
||||
return data, attr
|
||||
|
||||
@classmethod
|
||||
@contextlib.contextmanager
|
||||
def preserve_prop_value(cls, bpy_object: bpy.types.bpy_struct, prop_name: str):
|
||||
if bpy_object.is_property_set(prop_name):
|
||||
prop_value = getattr(bpy_object, prop_name)
|
||||
else:
|
||||
prop_value = ...
|
||||
try:
|
||||
yield
|
||||
except:
|
||||
raise
|
||||
finally:
|
||||
if prop_value is ...:
|
||||
bpy_object.property_unset(prop_name)
|
||||
return
|
||||
setattr(bpy_object, prop_name, prop_value)
|
||||
|
||||
@classmethod
|
||||
def set_prop_from_path(cls, bpy_object: bpy.types.bpy_struct, prop_path: str, value: Any) -> None:
|
||||
"""Set `data_block` property value using path from `path_from_id`."""
|
||||
@@ -1618,7 +1663,7 @@ class Blender(bonsai.core.tool.Blender):
|
||||
|
||||
@classmethod
|
||||
def get_user_data_dir(cls) -> Path:
|
||||
props = cls.get_bim_props()
|
||||
props = cls.get_addon_preferences()
|
||||
return Path(props.data_dir)
|
||||
|
||||
@classmethod
|
||||
@@ -1865,3 +1910,59 @@ class Blender(bonsai.core.tool.Blender):
|
||||
|
||||
# Remove file if crash didn't happened.
|
||||
path.unlink()
|
||||
|
||||
@classmethod
|
||||
def sync_old_preferences(cls) -> None:
|
||||
# Added on 25.07.15.
|
||||
# TODO: deprecate later.
|
||||
settings_remap = {
|
||||
"scene.BIMBSDDProperties.load_preview_dictionaries": "preferences.bsdd_load_preview_dictionaries",
|
||||
"scene.BIMBSDDProperties.load_inactive_dictionaries": "preferences.bsdd_load_inactive_dictionaries",
|
||||
"scene.BIMBSDDProperties.load_test_dictionaries": "preferences.bsdd_load_test_dictionaries",
|
||||
"scene.BIMProjectProperties.should_disable_undo_on_save": "preferences.should_disable_undo_on_save",
|
||||
"scene.BIMProjectProperties.should_stream": "preferences.should_stream",
|
||||
"scene.BIMModelProperties.occurrence_name_style": "preferences.occurrence_name_style",
|
||||
"scene.BIMModelProperties.occurrence_name_function": "preferences.occurrence_name_function",
|
||||
"scene.BIMProperties.pset_dir": "preferences.pset_dir",
|
||||
"scene.BIMProperties.data_dir": "preferences.data_dir",
|
||||
"scene.BIMProperties.cache_dir": "preferences.cache_dir",
|
||||
"scene.DocProperties.sheets_dir": "preferences.doc.sheets_dir",
|
||||
"scene.DocProperties.layouts_dir": "preferences.doc.layouts_dir",
|
||||
"scene.DocProperties.titleblocks_dir": "preferences.doc.titleblocks_dir",
|
||||
"scene.DocProperties.drawings_dir": "preferences.doc.drawings_dir",
|
||||
"scene.DocProperties.stylesheet_path": "preferences.doc.stylesheet_path",
|
||||
"scene.DocProperties.schedules_stylesheet_path": "preferences.doc.schedules_stylesheet_path",
|
||||
"scene.DocProperties.markers_path": "preferences.doc.markers_path",
|
||||
"scene.DocProperties.symbols_path": "preferences.doc.symbols_path",
|
||||
"scene.DocProperties.patterns_path": "preferences.doc.patterns_path",
|
||||
"scene.DocProperties.shadingstyles_path": "preferences.doc.shadingstyles_path",
|
||||
"scene.DocProperties.shadingstyle_default": "preferences.doc.shadingstyle_default",
|
||||
"scene.DocProperties.drawing_font": "preferences.doc.drawing_font",
|
||||
"scene.DocProperties.magic_font_scale": "preferences.doc.magic_font_scale",
|
||||
"scene.DocProperties.imperial_precision": "preferences.doc.imperial_precision",
|
||||
"scene.DocProperties.tolerance": "preferences.doc.tolerance",
|
||||
"scene.DocProperties.classes_to_wireframe": "preferences.doc.classes_to_wireframe",
|
||||
"scene.DocProperties.classes_no_cut": "preferences.doc.classes_no_cut",
|
||||
}
|
||||
|
||||
props_updated = False
|
||||
for old_path, path in settings_remap.items():
|
||||
data, attr = cls.resolve_data_path_to_data_attr(path)
|
||||
# User already overridden the value.
|
||||
if data.is_property_set(attr):
|
||||
continue
|
||||
|
||||
data_old, attr_old = cls.resolve_data_path_to_data_attr(old_path)
|
||||
# User was only using default value previously.
|
||||
if attr_old not in data_old:
|
||||
continue
|
||||
|
||||
old_value = data_old[attr_old]
|
||||
print(f"Updating {path} based on previous value from {old_path} - '{old_value}'.")
|
||||
setattr(data, attr, old_value)
|
||||
props_updated = True
|
||||
|
||||
# Doesn't seem to save on exit if edited from Python API, so we do it manually.
|
||||
assert bpy.context.preferences
|
||||
if props_updated and bpy.context.preferences.use_preferences_save:
|
||||
bpy.ops.wm.save_userpref()
|
||||
|
||||
@@ -22,13 +22,14 @@ import bonsai.tool as tool
|
||||
import bpy
|
||||
import json
|
||||
import bsdd
|
||||
import ifcopenshell.api.library
|
||||
import ifcopenshell.util.type
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.classification
|
||||
from typing import Any, Union, Optional, TYPE_CHECKING
|
||||
from typing import Any, Union, TYPE_CHECKING, TypedDict
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bonsai.bim.module.bsdd.prop import BIMBSDDProperties
|
||||
from bonsai.bim.module.bsdd.prop import BIMBSDDProperties, BSDDDictionary
|
||||
|
||||
|
||||
class Bsdd(bonsai.core.tool.Bsdd):
|
||||
@@ -84,6 +85,7 @@ class Bsdd(bonsai.core.tool.Bsdd):
|
||||
@classmethod
|
||||
def create_dictionaries(cls, dictionaries: list[bsdd.DictionaryContractV1]) -> None:
|
||||
props = cls.get_bsdd_props()
|
||||
active_bsdds: set[str] = {r.Location for r in cls.get_active_bsdd_ifc()}
|
||||
for dictionary in sorted(dictionaries, key=lambda d: d["name"]):
|
||||
new = props.dictionaries.add()
|
||||
new.name = dictionary["name"]
|
||||
@@ -92,6 +94,7 @@ class Bsdd(bonsai.core.tool.Bsdd):
|
||||
new.organization_name_owner = dictionary["organizationNameOwner"]
|
||||
new.status = dictionary["status"]
|
||||
new.version = dictionary["version"]
|
||||
new["is_active"] = dictionary["uri"] in active_bsdds
|
||||
|
||||
@classmethod
|
||||
def get_active_class_data(cls) -> Union[bsdd.ClassContractV1, dict]:
|
||||
@@ -107,20 +110,22 @@ class Bsdd(bonsai.core.tool.Bsdd):
|
||||
|
||||
@classmethod
|
||||
def get_dictionary(cls, uri: str) -> bsdd.DictionaryContractV1:
|
||||
props = cls.get_bsdd_props()
|
||||
response = cls.client.get_dictionary(dictionary_uri=uri, include_test_dictionaries=props.load_test_dictionaries)
|
||||
prefs = tool.Blender.get_addon_preferences()
|
||||
response = cls.client.get_dictionary(
|
||||
dictionary_uri=uri, include_test_dictionaries=prefs.bsdd_load_test_dictionaries
|
||||
)
|
||||
if dicts := response.get("dictionaries"):
|
||||
return dicts[0]
|
||||
|
||||
@classmethod
|
||||
def get_dictionaries(cls) -> list[bsdd.DictionaryContractV1]:
|
||||
props = cls.get_bsdd_props()
|
||||
response = cls.client.get_dictionary(include_test_dictionaries=props.load_test_dictionaries)
|
||||
prefs = tool.Blender.get_addon_preferences()
|
||||
response = cls.client.get_dictionary(include_test_dictionaries=prefs.bsdd_load_test_dictionaries)
|
||||
dicts = response.get("dictionaries") or []
|
||||
statuses = ["Active"]
|
||||
if props.load_preview_dictionaries:
|
||||
if prefs.bsdd_load_preview_dictionaries:
|
||||
statuses.append("Preview")
|
||||
if props.load_inactive_dictionaries:
|
||||
if prefs.bsdd_load_inactive_dictionaries:
|
||||
statuses.append("Inactive")
|
||||
return list(filter(lambda d: d["status"] in statuses, dicts))
|
||||
|
||||
@@ -390,3 +395,80 @@ class Bsdd(bonsai.core.tool.Bsdd):
|
||||
uris.add(uri)
|
||||
class_uri, pset_name = pset_uri.rsplit("#", 1)
|
||||
return class_uri in uris
|
||||
|
||||
@classmethod
|
||||
def get_active_bsdd_library(cls) -> Union[ifcopenshell.entity_instance, None]:
|
||||
for document in tool.Ifc.get().by_type("IfcLibraryInformation"):
|
||||
if document.Name == "BBIM_Active_bSDD":
|
||||
return document
|
||||
|
||||
@classmethod
|
||||
def get_refs(cls, library: ifcopenshell.entity_instance) -> tuple[ifcopenshell.entity_instance, ...]:
|
||||
if library.file.schema == "IFC2X3":
|
||||
return library.LibraryReference or ()
|
||||
return library.HasLibraryReferences
|
||||
|
||||
@classmethod
|
||||
def get_active_bsdd_ifc(cls) -> tuple[ifcopenshell.entity_instance, ...]:
|
||||
library = cls.get_active_bsdd_library()
|
||||
if not library:
|
||||
return ()
|
||||
return cls.get_refs(library)
|
||||
|
||||
class BSDDJsonData(TypedDict):
|
||||
name: str
|
||||
description: str
|
||||
|
||||
@classmethod
|
||||
def save_active_bsdd_to_ifc(cls) -> None:
|
||||
props = cls.get_bsdd_props()
|
||||
assert props.dictionaries
|
||||
|
||||
ifc_file = tool.Ifc.get()
|
||||
library = cls.get_active_bsdd_library()
|
||||
if not library:
|
||||
library = ifcopenshell.api.library.add_library(ifc_file, "BBIM_Active_bSDD")
|
||||
|
||||
active_bsdd: dict[str, BSDDDictionary] = {d.uri: d for d in props.dictionaries if d.is_active}
|
||||
refs_to_remove: list[ifcopenshell.entity_instance] = []
|
||||
active_bsdd_ifc: set[str] = set()
|
||||
for ref in cls.get_refs(library):
|
||||
uri = ref.Location
|
||||
if uri not in active_bsdd:
|
||||
refs_to_remove.append(ref)
|
||||
continue
|
||||
active_bsdd_ifc.add(uri)
|
||||
|
||||
# There is a possible edge case here.
|
||||
# E.g. user had some dicts from test/preview previously active.
|
||||
# But now other user opens file in Bonsai, where test/preview are not enabled in preferences,
|
||||
# and makes changes to active dictionaries.
|
||||
# Then, this will also unmark those test/preview dicts as active,
|
||||
# which can be sometimes confusing.
|
||||
#
|
||||
# But the alternative is to only change status just for the dictionary
|
||||
# changed by users, but then we need to come up with some other mechanism
|
||||
# how to keep this library clean from dead urls
|
||||
# (e.g. when dictionaries will go out of date).
|
||||
for ref in refs_to_remove:
|
||||
ifcopenshell.api.library.remove_reference(ifc_file, ref)
|
||||
|
||||
missing_bsdd = set(active_bsdd) - active_bsdd_ifc
|
||||
for uri in missing_bsdd:
|
||||
blender_dict = active_bsdd[uri]
|
||||
ref = ifcopenshell.api.library.add_reference(ifc_file, library)
|
||||
ref.Location = blender_dict.uri
|
||||
# Can't use Description as it's not present in IFC2X3.
|
||||
data: Bsdd.BSDDJsonData = {
|
||||
"name": blender_dict.name,
|
||||
"description": f"{blender_dict.status} - {blender_dict.version}",
|
||||
}
|
||||
ref.Name = json.dumps(data)
|
||||
|
||||
@classmethod
|
||||
def get_active_bsdd_enum_items(cls) -> list[tuple[str, str, str]]:
|
||||
results: list[tuple[str, str, str]] = []
|
||||
for ref in tool.Bsdd.get_active_bsdd_ifc():
|
||||
data: Bsdd.BSDDJsonData = json.loads(ref.Name)
|
||||
results.append((ref.Location, data["name"], data["description"]))
|
||||
return results
|
||||
|
||||
@@ -72,7 +72,7 @@ class Context(bonsai.core.tool.Context):
|
||||
assert prop
|
||||
prop.data_type = "string"
|
||||
|
||||
bonsai.bim.helper.import_attributes2(context, props.context_attributes, callback)
|
||||
bonsai.bim.helper.import_attributes(context, props.context_attributes, callback)
|
||||
|
||||
@classmethod
|
||||
def clear_context(cls) -> None:
|
||||
|
||||
@@ -130,7 +130,7 @@ class Cost(bonsai.core.tool.Cost):
|
||||
|
||||
props = cls.get_cost_props()
|
||||
props.cost_schedule_attributes.clear()
|
||||
bonsai.bim.helper.import_attributes2(cost_schedule, props.cost_schedule_attributes, callback=special_import)
|
||||
bonsai.bim.helper.import_attributes(cost_schedule, props.cost_schedule_attributes, callback=special_import)
|
||||
|
||||
@classmethod
|
||||
def enable_editing_cost_items(cls, cost_schedule: ifcopenshell.entity_instance) -> None:
|
||||
@@ -236,7 +236,7 @@ class Cost(bonsai.core.tool.Cost):
|
||||
def load_cost_item_attributes(cls, cost_item: ifcopenshell.entity_instance) -> None:
|
||||
props = cls.get_cost_props()
|
||||
props.cost_item_attributes.clear()
|
||||
bonsai.bim.helper.import_attributes2(cost_item, props.cost_item_attributes)
|
||||
bonsai.bim.helper.import_attributes(cost_item, props.cost_item_attributes)
|
||||
|
||||
@classmethod
|
||||
def disable_editing_cost_item(cls) -> None:
|
||||
@@ -381,7 +381,7 @@ class Cost(bonsai.core.tool.Cost):
|
||||
def load_cost_item_quantity_attributes(cls, physical_quantity: ifcopenshell.entity_instance) -> None:
|
||||
props = cls.get_cost_props()
|
||||
props.quantity_attributes.clear()
|
||||
bonsai.bim.helper.import_attributes2(physical_quantity, props.quantity_attributes)
|
||||
bonsai.bim.helper.import_attributes(physical_quantity, props.quantity_attributes)
|
||||
|
||||
@classmethod
|
||||
def enable_editing_cost_item_values(cls, cost_item: ifcopenshell.entity_instance) -> None:
|
||||
@@ -460,7 +460,7 @@ class Cost(bonsai.core.tool.Cost):
|
||||
break
|
||||
return True
|
||||
|
||||
bonsai.bim.helper.import_attributes2(
|
||||
bonsai.bim.helper.import_attributes(
|
||||
cost_value, props.cost_value_attributes, callback=import_attributes_callback
|
||||
)
|
||||
|
||||
@@ -846,10 +846,18 @@ class Cost(bonsai.core.tool.Cost):
|
||||
return "Could not open file location"
|
||||
|
||||
@classmethod
|
||||
def export_cost_schedules_to_pdf(cls, filepath: str, cost_schedule: ifcopenshell.entity_instance, options: dict):
|
||||
def export_cost_schedules_to_pdf(
|
||||
cls, filepath: str, cost_schedule: ifcopenshell.entity_instance, options: dict, force_schedule_type: str = ""
|
||||
):
|
||||
from ifc5d.ifc5Dspreadsheet import Ifc5DPdfWriter
|
||||
|
||||
writer = Ifc5DPdfWriter(file=tool.Ifc.get(), output=filepath, cost_schedule=cost_schedule, options=options)
|
||||
writer = Ifc5DPdfWriter(
|
||||
file=tool.Ifc.get(),
|
||||
output=filepath,
|
||||
cost_schedule=cost_schedule,
|
||||
options=options,
|
||||
force_schedule_type=force_schedule_type,
|
||||
)
|
||||
writer.write()
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
from __future__ import annotations
|
||||
import os
|
||||
import json
|
||||
import bmesh
|
||||
import bpy
|
||||
import ifcopenshell.ifcopenshell_wrapper as W
|
||||
import ifcopenshell.api.material
|
||||
@@ -56,8 +57,8 @@ class Debug(bonsai.core.tool.Debug):
|
||||
|
||||
@classmethod
|
||||
def purge_hdf5_cache(cls) -> None:
|
||||
props = tool.Blender.get_bim_props()
|
||||
cache_dir = props.cache_dir
|
||||
prefs = tool.Blender.get_addon_preferences()
|
||||
cache_dir = prefs.cache_dir
|
||||
filelist = [f for f in os.listdir(cache_dir) if f.endswith(".h5")]
|
||||
for f in filelist:
|
||||
try:
|
||||
@@ -70,6 +71,7 @@ class Debug(bonsai.core.tool.Debug):
|
||||
mesh = bpy.data.meshes.new("Debug")
|
||||
bm.to_mesh(mesh)
|
||||
obj = bpy.data.objects.new(name, mesh)
|
||||
assert bpy.context.scene
|
||||
bpy.context.scene.collection.objects.link(obj)
|
||||
return obj
|
||||
|
||||
@@ -80,6 +82,7 @@ class Debug(bonsai.core.tool.Debug):
|
||||
mesh = bpy.data.meshes.new("Debug")
|
||||
mesh.from_pydata(verts, edges, [])
|
||||
obj = bpy.data.objects.new(name, mesh)
|
||||
assert bpy.context.scene
|
||||
bpy.context.scene.collection.objects.link(obj)
|
||||
return obj
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ class Document(bonsai.core.tool.Document):
|
||||
return False
|
||||
|
||||
import_callback = callback if document.is_a("IfcDocumentReference") else None
|
||||
bonsai.bim.helper.import_attributes2(document, props.document_attributes, callback=import_callback)
|
||||
bonsai.bim.helper.import_attributes(document, props.document_attributes, callback=import_callback)
|
||||
|
||||
@classmethod
|
||||
def import_project_documents(cls) -> None:
|
||||
|
||||
@@ -1075,7 +1075,7 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
|
||||
for ifc_literal in cls.get_text_literal(obj, return_list=True):
|
||||
literal_props = props.literals.add()
|
||||
bonsai.bim.helper.import_attributes2(ifc_literal, literal_props.attributes)
|
||||
bonsai.bim.helper.import_attributes(ifc_literal, literal_props.attributes)
|
||||
|
||||
box_alignment_mask = [False] * 9
|
||||
position_string = literal_props.attributes["BoxAlignment"].string_value
|
||||
@@ -1314,34 +1314,37 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
@classmethod
|
||||
def get_default_layout_path(cls, identification: str, name: str) -> str:
|
||||
project = tool.Ifc.get().by_type("IfcProject")[0]
|
||||
props = tool.Drawing.get_document_props()
|
||||
prefs = tool.Blender.get_addon_preferences()
|
||||
layouts_dir = (
|
||||
ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "LayoutsDir") or props.layouts_dir
|
||||
ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "LayoutsDir") or prefs.doc.layouts_dir
|
||||
)
|
||||
return os.path.join(layouts_dir, cls.sanitise_filename(f"{identification} - {name}.svg")).replace("\\", "/")
|
||||
|
||||
@classmethod
|
||||
def get_default_sheet_path(cls, identification: str, name: str) -> str:
|
||||
project = tool.Ifc.get().by_type("IfcProject")[0]
|
||||
props = tool.Drawing.get_document_props()
|
||||
sheets_dir = ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "SheetsDir") or props.sheets_dir
|
||||
prefs = tool.Blender.get_addon_preferences()
|
||||
sheets_dir = (
|
||||
ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "SheetsDir") or prefs.doc.sheets_dir
|
||||
)
|
||||
return os.path.join(sheets_dir, cls.sanitise_filename(f"{identification} - {name}.svg")).replace("\\", "/")
|
||||
|
||||
@classmethod
|
||||
def get_default_titleblock_path(cls, name: str) -> str:
|
||||
project = tool.Ifc.get().by_type("IfcProject")[0]
|
||||
props = tool.Drawing.get_document_props()
|
||||
prefs = tool.Blender.get_addon_preferences()
|
||||
titleblocks_dir = (
|
||||
ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "TitleblocksDir") or props.titleblocks_dir
|
||||
ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "TitleblocksDir")
|
||||
or prefs.doc.titleblocks_dir
|
||||
)
|
||||
return os.path.join(titleblocks_dir, cls.sanitise_filename(f"{name}.svg")).replace("\\", "/")
|
||||
|
||||
@classmethod
|
||||
def get_default_drawing_path(cls, name: str) -> str:
|
||||
project = tool.Ifc.get().by_type("IfcProject")[0]
|
||||
props = tool.Drawing.get_document_props()
|
||||
prefs = tool.Blender.get_addon_preferences()
|
||||
drawings_dir = (
|
||||
ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "DrawingsDir") or props.drawings_dir
|
||||
ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "DrawingsDir") or prefs.doc.drawings_dir
|
||||
)
|
||||
return os.path.join(drawings_dir, cls.sanitise_filename(f"{name}.svg")).replace("\\", "/")
|
||||
|
||||
@@ -1353,19 +1356,20 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
RESOURCE_TYPES = ("Stylesheet", "Markers", "Symbols", "Patterns", "ShadingStyles")
|
||||
|
||||
@classmethod
|
||||
def get_default_drawing_resource_path(cls, resource: str) -> Union[str, None]:
|
||||
def get_default_drawing_resource_path(cls, resource: ResourceType) -> Union[str, None]:
|
||||
project = tool.Ifc.get().by_type("IfcProject")[0]
|
||||
props = tool.Drawing.get_document_props()
|
||||
doc_prefs = tool.Blender.get_addon_preferences().doc
|
||||
resource_path = ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", f"{resource}Path") or getattr(
|
||||
props, f"{resource.lower()}_path"
|
||||
doc_prefs, f"{resource.lower()}_path"
|
||||
)
|
||||
if resource_path:
|
||||
assert isinstance(resource_path, str)
|
||||
return resource_path.replace("\\", "/")
|
||||
|
||||
@classmethod
|
||||
def get_default_shading_style(cls) -> str:
|
||||
dprops = tool.Drawing.get_document_props()
|
||||
return dprops.shadingstyle_default
|
||||
prefs = tool.Blender.get_addon_preferences()
|
||||
return prefs.doc.shadingstyle_default
|
||||
|
||||
@classmethod
|
||||
def setup_shading_styles_path(cls, resource_path: str) -> None:
|
||||
|
||||
@@ -1802,7 +1802,7 @@ class Geometry(bonsai.core.tool.Geometry):
|
||||
return False
|
||||
return None
|
||||
|
||||
bonsai.bim.helper.import_attributes2(item, props.item_attributes, callback=callback)
|
||||
bonsai.bim.helper.import_attributes(item, props.item_attributes, callback=callback)
|
||||
|
||||
profile = None
|
||||
if item.is_a("IfcSweptAreaSolid"):
|
||||
|
||||
@@ -81,7 +81,7 @@ class Georeference(bonsai.core.tool.Georeference):
|
||||
for context in tool.Ifc.get().by_type("IfcGeometricRepresentationContext", include_subtypes=False):
|
||||
if context.HasCoordinateOperation:
|
||||
projected_crs = context.HasCoordinateOperation[0].TargetCRS
|
||||
bonsai.bim.helper.import_attributes2(projected_crs, props.projected_crs, callback=callback)
|
||||
bonsai.bim.helper.import_attributes(projected_crs, props.projected_crs, callback=callback)
|
||||
return
|
||||
|
||||
@classmethod
|
||||
@@ -153,9 +153,7 @@ class Georeference(bonsai.core.tool.Georeference):
|
||||
for context in tool.Ifc.get().by_type("IfcGeometricRepresentationContext", include_subtypes=False):
|
||||
if context.HasCoordinateOperation:
|
||||
coordinate_operation = context.HasCoordinateOperation[0]
|
||||
bonsai.bim.helper.import_attributes2(
|
||||
coordinate_operation, props.coordinate_operation, callback=callback
|
||||
)
|
||||
bonsai.bim.helper.import_attributes(coordinate_operation, props.coordinate_operation, callback=callback)
|
||||
return
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -61,13 +61,13 @@ class Library(bonsai.core.tool.Library):
|
||||
def import_library_attributes(cls, library: ifcopenshell.entity_instance) -> None:
|
||||
props = cls.get_library_props()
|
||||
props.library_attributes.clear()
|
||||
bonsai.bim.helper.import_attributes2(library, props.library_attributes)
|
||||
bonsai.bim.helper.import_attributes(library, props.library_attributes)
|
||||
|
||||
@classmethod
|
||||
def import_reference_attributes(cls, reference: ifcopenshell.entity_instance) -> None:
|
||||
props = cls.get_library_props()
|
||||
props.reference_attributes.clear()
|
||||
bonsai.bim.helper.import_attributes2(reference, props.reference_attributes)
|
||||
bonsai.bim.helper.import_attributes(reference, props.reference_attributes)
|
||||
|
||||
@classmethod
|
||||
def import_references(cls, library: ifcopenshell.entity_instance) -> None:
|
||||
|
||||
@@ -76,14 +76,6 @@ class Material(bonsai.core.tool.Material):
|
||||
def get_elements_by_material(cls, material: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
|
||||
return ifcopenshell.util.element.get_elements_by_material(tool.Ifc.get(), material)
|
||||
|
||||
@classmethod
|
||||
def get_active_material_item(cls) -> Union[MaterialItem, None]:
|
||||
"""Get active material props item if index is valid, otherwise, return None."""
|
||||
props = tool.Material.get_material_props()
|
||||
if 0 <= props.active_material_index < len(props.materials):
|
||||
return props.materials[props.active_material_index]
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def get_material_category(cls, material: ifcopenshell.entity_instance) -> str:
|
||||
# IfcMaterial has Category since IFC4.
|
||||
@@ -95,7 +87,7 @@ class Material(bonsai.core.tool.Material):
|
||||
|
||||
# Store active category name to reselect it later.
|
||||
# Occurs when we expand/contract all categories.
|
||||
active_item = cls.get_active_material_item()
|
||||
active_item = props.active_material
|
||||
previously_selected_category = None
|
||||
if active_item and active_item.is_category:
|
||||
previously_selected_category = active_item.name
|
||||
@@ -170,7 +162,7 @@ class Material(bonsai.core.tool.Material):
|
||||
def load_material_attributes(cls, material: ifcopenshell.entity_instance) -> None:
|
||||
props = tool.Material.get_material_props()
|
||||
props.material_attributes.clear()
|
||||
bonsai.bim.helper.import_attributes2(material, props.material_attributes)
|
||||
bonsai.bim.helper.import_attributes(material, props.material_attributes)
|
||||
|
||||
@classmethod
|
||||
def enable_editing_material(cls, material: ifcopenshell.entity_instance) -> None:
|
||||
|
||||
@@ -49,7 +49,7 @@ from bonsai.bim import import_ifc
|
||||
from bonsai.bim.module.model.data import AuthoringData, RailingData, RoofData, WindowData, DoorData
|
||||
from bonsai.bim.module.model.opening import FilledOpeningGenerator
|
||||
from ifcopenshell.util.shape_builder import ShapeBuilder, np_to_3d
|
||||
from typing import Optional, Union, TypeVar, Any, Literal, TYPE_CHECKING, TypedDict
|
||||
from typing import Optional, Union, TypeVar, Any, Literal, TYPE_CHECKING, TypedDict, assert_never
|
||||
from collections.abc import Iterable, Sequence
|
||||
|
||||
T = TypeVar("T")
|
||||
@@ -275,17 +275,19 @@ class Model(bonsai.core.tool.Model):
|
||||
|
||||
@classmethod
|
||||
def generate_occurrence_name(cls, element_type: ifcopenshell.entity_instance, ifc_class: str) -> str:
|
||||
props = cls.get_model_props()
|
||||
if props.occurrence_name_style == "CLASS":
|
||||
prefs = tool.Blender.get_addon_preferences()
|
||||
if prefs.occurrence_name_style == "CLASS":
|
||||
return ifc_class[3:]
|
||||
elif props.occurrence_name_style == "TYPE":
|
||||
elif prefs.occurrence_name_style == "TYPE":
|
||||
return element_type.Name or "Unnamed"
|
||||
elif props.occurrence_name_style == "CUSTOM":
|
||||
elif prefs.occurrence_name_style == "CUSTOM":
|
||||
try:
|
||||
# Power users gonna power
|
||||
return eval(props.occurrence_name_function) or "Instance"
|
||||
return eval(prefs.occurrence_name_function) or "Instance"
|
||||
except:
|
||||
return "Instance"
|
||||
else:
|
||||
assert_never(prefs.occurrence_name_style)
|
||||
|
||||
@classmethod
|
||||
def get_extrusion(cls, representation: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]:
|
||||
|
||||
@@ -97,7 +97,7 @@ class Owner(bonsai.core.tool.Owner):
|
||||
for line in data[name] or []:
|
||||
collection.add().name = line
|
||||
|
||||
bonsai.bim.helper.import_attributes2(address, props.address_attributes, callback)
|
||||
bonsai.bim.helper.import_attributes(address, props.address_attributes, callback)
|
||||
|
||||
@classmethod
|
||||
def clear_address(cls) -> None:
|
||||
@@ -154,7 +154,7 @@ class Owner(bonsai.core.tool.Owner):
|
||||
organisation = tool.Ifc.get().by_id(props.active_organisation_id)
|
||||
props.organisation_attributes.clear()
|
||||
|
||||
bonsai.bim.helper.import_attributes2(organisation, props.organisation_attributes)
|
||||
bonsai.bim.helper.import_attributes(organisation, props.organisation_attributes)
|
||||
|
||||
@classmethod
|
||||
def clear_organisation(cls) -> None:
|
||||
@@ -209,7 +209,7 @@ class Owner(bonsai.core.tool.Owner):
|
||||
for name_ in data[name] or []:
|
||||
collection.add().name = name_ or ""
|
||||
|
||||
bonsai.bim.helper.import_attributes2(person, props.person_attributes, callback)
|
||||
bonsai.bim.helper.import_attributes(person, props.person_attributes, callback)
|
||||
|
||||
@classmethod
|
||||
def clear_person(cls) -> None:
|
||||
@@ -253,7 +253,7 @@ class Owner(bonsai.core.tool.Owner):
|
||||
role = cls.get_role()
|
||||
props = cls.get_owner_props()
|
||||
props.role_attributes.clear()
|
||||
bonsai.bim.helper.import_attributes2(role, props.role_attributes)
|
||||
bonsai.bim.helper.import_attributes(role, props.role_attributes)
|
||||
|
||||
@classmethod
|
||||
def clear_role(cls) -> None:
|
||||
@@ -279,7 +279,7 @@ class Owner(bonsai.core.tool.Owner):
|
||||
def import_actor_attributes(cls, actor: ifcopenshell.entity_instance) -> None:
|
||||
props = cls.get_owner_props()
|
||||
props.actor_attributes.clear()
|
||||
bonsai.bim.helper.import_attributes2(actor, props.actor_attributes)
|
||||
bonsai.bim.helper.import_attributes(actor, props.actor_attributes)
|
||||
|
||||
@classmethod
|
||||
def clear_actor(cls) -> None:
|
||||
@@ -329,7 +329,7 @@ class Owner(bonsai.core.tool.Owner):
|
||||
new.enum_value = str(data["ApplicationDeveloper"].id())
|
||||
return True
|
||||
|
||||
bonsai.bim.helper.import_attributes2(application, props.application_attributes, callback)
|
||||
bonsai.bim.helper.import_attributes(application, props.application_attributes, callback)
|
||||
|
||||
@classmethod
|
||||
def export_application_attributes(cls) -> dict[str, Any]:
|
||||
|
||||
@@ -474,8 +474,8 @@ class Polyline(bonsai.core.tool.Polyline):
|
||||
else:
|
||||
unit_scale = tool.Blender.get_unit_scale()
|
||||
if bpy.context.scene.unit_settings.system == "IMPERIAL":
|
||||
dprops = tool.Drawing.get_document_props()
|
||||
precision = dprops.imperial_precision
|
||||
prefs = tool.Blender.get_addon_preferences()
|
||||
precision = prefs.doc.imperial_precision
|
||||
if is_area:
|
||||
unit_scale = 1
|
||||
else:
|
||||
|
||||
@@ -223,6 +223,7 @@ class Profile(bonsai.core.tool.Profile):
|
||||
"""Set default profile attributes to keep profile valid."""
|
||||
class_match = False
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
# We're using for-loop as classes may not match exactly.
|
||||
for ifc_class, params in cls.DEFAULT_PROFILE_ATTRS.items():
|
||||
if profile.is_a(ifc_class):
|
||||
class_match = True
|
||||
|
||||
@@ -69,7 +69,7 @@ class Project(bonsai.core.tool.Project):
|
||||
|
||||
@classmethod
|
||||
def load_pset_templates(cls) -> None:
|
||||
props = tool.Blender.get_bim_props()
|
||||
props = tool.Blender.get_addon_preferences()
|
||||
pset_dir = tool.Ifc.resolve_uri(props.pset_dir)
|
||||
if os.path.isdir(pset_dir):
|
||||
for path in Path(pset_dir).glob("*.ifc"):
|
||||
|
||||
@@ -75,11 +75,11 @@ class Pset(bonsai.core.tool.Pset):
|
||||
@classmethod
|
||||
def get_pset_props(cls, obj: str, obj_type: tool.Ifc.OBJECT_TYPE) -> PsetProperties:
|
||||
if obj_type == "Object":
|
||||
return bpy.data.objects.get(obj).PsetProperties
|
||||
return bpy.data.objects[obj].PsetProperties
|
||||
elif obj_type == "Material":
|
||||
return bpy.context.scene.MaterialPsetProperties
|
||||
elif obj_type == "MaterialSetItem":
|
||||
return bpy.data.objects.get(obj).MaterialSetItemPsetProperties
|
||||
return bpy.data.objects[obj].MaterialSetItemPsetProperties
|
||||
elif obj_type == "Task":
|
||||
return bpy.context.scene.TaskPsetProperties
|
||||
elif obj_type == "Resource":
|
||||
|
||||
@@ -133,7 +133,7 @@ class PsetTemplate(bonsai.core.tool.PsetTemplate):
|
||||
for f in tool.Blender.get_data_dir_paths("pset", "*.ifc"):
|
||||
paths.append((f, "Global Pset Template"))
|
||||
|
||||
props = tool.Blender.get_bim_props()
|
||||
props = tool.Blender.get_addon_preferences()
|
||||
pset_dir = Path(tool.Ifc.resolve_uri(props.pset_dir))
|
||||
if pset_dir.is_dir():
|
||||
for path in Path(pset_dir).glob("*.ifc"):
|
||||
|
||||
@@ -33,21 +33,29 @@ import ifcopenshell.util.date as ifcdateutils
|
||||
import ifcopenshell.util.cost
|
||||
import ifcopenshell.util.resource
|
||||
import ifcopenshell.util.constraint
|
||||
from typing import Any, Union, TYPE_CHECKING
|
||||
from typing import Any, Union, TYPE_CHECKING, Literal
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bonsai.bim.module.resource.prop import BIMResourceProperties
|
||||
from bonsai.bim.prop import Attribute
|
||||
from bonsai.bim.module.resource.prop import BIMResourceProperties, BIMResourceProductivity
|
||||
|
||||
|
||||
class Resource(bonsai.core.tool.Resource):
|
||||
@classmethod
|
||||
def get_resource_props(cls) -> BIMResourceProperties:
|
||||
"""
|
||||
BIMResourceTreeProperties can be accessed using `.tree`.
|
||||
BIMResourceProductivity - using `.productivity`.
|
||||
"""
|
||||
return bpy.context.scene.BIMResourceProperties
|
||||
|
||||
@classmethod
|
||||
def load_resources(cls) -> None:
|
||||
def create_new_resource_li(resource, level_index):
|
||||
new = bpy.context.scene.BIMResourceTreeProperties.resources.add()
|
||||
props = cls.get_resource_props()
|
||||
tprops = props.tree
|
||||
|
||||
def create_new_resource_li(resource: ifcopenshell.entity_instance, level_index: int) -> None:
|
||||
new = tprops.resources.add()
|
||||
new.ifc_definition_id = resource.id()
|
||||
new.is_expanded = resource.id() not in contracted_resources
|
||||
new.level_index = level_index
|
||||
@@ -60,8 +68,6 @@ class Resource(bonsai.core.tool.Resource):
|
||||
for nested_resource in rel.RelatedObjects
|
||||
]
|
||||
|
||||
props = bpy.context.scene.BIMResourceProperties
|
||||
tprops = bpy.context.scene.BIMResourceTreeProperties
|
||||
tprops.resources.clear()
|
||||
contracted_resources = json.loads(props.contracted_resources)
|
||||
props.is_resource_update_enabled = False
|
||||
@@ -76,8 +82,8 @@ class Resource(bonsai.core.tool.Resource):
|
||||
|
||||
@classmethod
|
||||
def load_resource_properties(cls) -> None:
|
||||
props = bpy.context.scene.BIMResourceProperties
|
||||
tprops = bpy.context.scene.BIMResourceTreeProperties
|
||||
props = cls.get_resource_props()
|
||||
tprops = props.tree
|
||||
props.is_resource_update_enabled = False
|
||||
for item in tprops.resources:
|
||||
resource = tool.Ifc.get().by_id(item.ifc_definition_id)
|
||||
@@ -89,20 +95,23 @@ class Resource(bonsai.core.tool.Resource):
|
||||
|
||||
@classmethod
|
||||
def disable_editing_resource(cls) -> None:
|
||||
bpy.context.scene.BIMResourceProperties.active_resource_id = 0
|
||||
bpy.context.scene.BIMResourceProperties.active_resource_time_id = 0
|
||||
props = cls.get_resource_props()
|
||||
props.property_unset("active_resource_id")
|
||||
props.property_unset("active_resource_time_id")
|
||||
|
||||
@classmethod
|
||||
def disable_resource_editing_ui(cls) -> None:
|
||||
bpy.context.scene.BIMResourceProperties.is_editing = False
|
||||
props = cls.get_resource_props()
|
||||
props.property_unset("is_editing")
|
||||
|
||||
@classmethod
|
||||
def load_resource_attributes(cls, resource: ifcopenshell.entity_instance) -> None:
|
||||
bonsai.bim.helper.import_attributes2(resource, bpy.context.scene.BIMResourceProperties.resource_attributes)
|
||||
props = cls.get_resource_props()
|
||||
bonsai.bim.helper.import_attributes(resource, props.resource_attributes)
|
||||
|
||||
@classmethod
|
||||
def enable_editing_resource(cls, resource: ifcopenshell.entity_instance) -> None:
|
||||
props = bpy.context.scene.BIMResourceProperties
|
||||
props = cls.get_resource_props()
|
||||
props.active_resource_id = resource.id()
|
||||
props.resource_attributes.clear()
|
||||
props.editing_resource_type = "ATTRIBUTES"
|
||||
@@ -110,11 +119,12 @@ class Resource(bonsai.core.tool.Resource):
|
||||
|
||||
@classmethod
|
||||
def get_resource_attributes(cls) -> dict[str, Any]:
|
||||
return bonsai.bim.helper.export_attributes(bpy.context.scene.BIMResourceProperties.resource_attributes)
|
||||
props = cls.get_resource_props()
|
||||
return bonsai.bim.helper.export_attributes(props.resource_attributes)
|
||||
|
||||
@classmethod
|
||||
def enable_editing_resource_time(cls, resource: ifcopenshell.entity_instance) -> None:
|
||||
props = bpy.context.scene.BIMResourceProperties
|
||||
props = cls.get_resource_props()
|
||||
props.resource_time_attributes.clear()
|
||||
props.active_resource_time_id = resource.Usage.id()
|
||||
props.active_resource_id = resource.id()
|
||||
@@ -127,8 +137,10 @@ class Resource(bonsai.core.tool.Resource):
|
||||
|
||||
@classmethod
|
||||
def load_resource_time_attributes(cls, resource_time: ifcopenshell.entity_instance) -> None:
|
||||
def callback(name, prop, data):
|
||||
if prop.data_type == "string":
|
||||
props = cls.get_resource_props()
|
||||
|
||||
def callback(name: str, prop: Union[Attribute, None], data: dict[str, Any]) -> None | Literal[True]:
|
||||
if prop and prop.data_type == "string":
|
||||
if isinstance(data[name], datetime):
|
||||
prop.string_value = "" if prop.is_null else data[name].isoformat()
|
||||
return True
|
||||
@@ -136,13 +148,13 @@ class Resource(bonsai.core.tool.Resource):
|
||||
prop.string_value = "" if prop.is_null else ifcdateutils.datetime2ifc(data[name], "IfcDuration")
|
||||
return True
|
||||
|
||||
bonsai.bim.helper.import_attributes2(
|
||||
resource_time, bpy.context.scene.BIMResourceProperties.resource_time_attributes, callback
|
||||
)
|
||||
bonsai.bim.helper.import_attributes(resource_time, props.resource_time_attributes, callback)
|
||||
|
||||
@classmethod
|
||||
def get_resource_time_attributes(cls) -> dict[str, Any]:
|
||||
def callback(attributes, prop):
|
||||
import bonsai.bim.module.sequence.helper as helper
|
||||
|
||||
def callback(attributes: dict[str, Any], prop: Attribute) -> bool:
|
||||
if "Start" in prop.name or "Finish" in prop.name or prop.name == "StatusTime":
|
||||
if prop.is_null:
|
||||
attributes[prop.name] = None
|
||||
@@ -155,26 +167,27 @@ class Resource(bonsai.core.tool.Resource):
|
||||
return True
|
||||
attributes[prop.name] = helper.parse_duration(prop.string_value)
|
||||
return True
|
||||
return False
|
||||
|
||||
props = bpy.context.scene.BIMResourceProperties
|
||||
props = cls.get_resource_props()
|
||||
return bonsai.bim.helper.export_attributes(props.resource_time_attributes, callback)
|
||||
|
||||
@classmethod
|
||||
def enable_editing_resource_costs(cls, resource: ifcopenshell.entity_instance) -> None:
|
||||
props = bpy.context.scene.BIMResourceProperties
|
||||
props = cls.get_resource_props()
|
||||
props.active_resource_id = resource.id()
|
||||
props.editing_resource_type = "COSTS"
|
||||
cls.update_cost_values_ui_data()
|
||||
|
||||
@classmethod
|
||||
def disable_editing_resource_cost_value(cls) -> None:
|
||||
props = bpy.context.scene.BIMResourceProperties
|
||||
props = cls.get_resource_props()
|
||||
props.active_cost_value_id = 0
|
||||
props.cost_value_editing_type = ""
|
||||
|
||||
@classmethod
|
||||
def enable_editing_resource_cost_value_formula(cls, cost_value: ifcopenshell.entity_instance) -> None:
|
||||
props = bpy.context.scene.BIMResourceProperties
|
||||
props = cls.get_resource_props()
|
||||
props.cost_value_attributes.clear()
|
||||
props.active_cost_value_id = cost_value.id()
|
||||
props.cost_value_editing_type = "FORMULA"
|
||||
@@ -230,48 +243,44 @@ class Resource(bonsai.core.tool.Resource):
|
||||
prop.enum_value = str(data["UnitBasis"].UnitComponent.id())
|
||||
return True
|
||||
|
||||
props = bpy.context.scene.BIMResourceProperties
|
||||
bonsai.bim.helper.import_attributes2(cost_value, props.cost_value_attributes, callback)
|
||||
props = cls.get_resource_props()
|
||||
bonsai.bim.helper.import_attributes(cost_value, props.cost_value_attributes, callback)
|
||||
|
||||
@classmethod
|
||||
def enable_editing_cost_value_attributes(cls, cost_value: ifcopenshell.entity_instance) -> None:
|
||||
props = bpy.context.scene.BIMResourceProperties
|
||||
props = cls.get_resource_props()
|
||||
props.cost_value_attributes.clear()
|
||||
props.active_cost_value_id = cost_value.id()
|
||||
props.cost_value_editing_type = "ATTRIBUTES"
|
||||
|
||||
@classmethod
|
||||
def get_resource_cost_value_formula(cls):
|
||||
return bpy.context.scene.BIMResourceProperties.cost_value_formula
|
||||
props = cls.get_resource_props()
|
||||
return props.cost_value_formula
|
||||
|
||||
@classmethod
|
||||
def get_resource_cost_value_attributes(cls) -> dict[str, Any]:
|
||||
def callback(attributes, prop):
|
||||
props = cls.get_resource_props()
|
||||
|
||||
def callback(data: dict[str, Any], prop: Attribute) -> bool:
|
||||
if prop.name == "UnitBasisValue":
|
||||
if prop.is_null:
|
||||
attributes["UnitBasis"] = None
|
||||
data["UnitBasis"] = None
|
||||
return True
|
||||
attributes["UnitBasis"] = {
|
||||
data["UnitBasis"] = {
|
||||
"ValueComponent": prop.float_value or 1,
|
||||
"UnitComponent": tool.Ifc.get().by_id(
|
||||
int(
|
||||
bpy.context.scene.BIMResourceProperties.cost_value_attributes.get(
|
||||
"UnitBasisUnit"
|
||||
).enum_value
|
||||
)
|
||||
),
|
||||
"UnitComponent": tool.Ifc.get().by_id(int(props.cost_value_attributes["UnitBasisUnit"].enum_value)),
|
||||
}
|
||||
return True
|
||||
if prop.name == "UnitBasisUnit":
|
||||
return True
|
||||
return False
|
||||
|
||||
return bonsai.bim.helper.export_attributes(
|
||||
bpy.context.scene.BIMResourceProperties.cost_value_attributes, callback
|
||||
)
|
||||
return bonsai.bim.helper.export_attributes(props.cost_value_attributes, callback)
|
||||
|
||||
@classmethod
|
||||
def enable_editing_resource_base_quantity(cls, resource: ifcopenshell.entity_instance) -> None:
|
||||
props = bpy.context.scene.BIMResourceProperties
|
||||
props = cls.get_resource_props()
|
||||
props.active_resource_id = resource.id()
|
||||
props.editing_resource_type = "QUANTITY"
|
||||
props.active_resource_class = resource.is_a()
|
||||
@@ -279,22 +288,24 @@ class Resource(bonsai.core.tool.Resource):
|
||||
|
||||
@classmethod
|
||||
def enable_editing_resource_quantity(cls, resource_quantity: ifcopenshell.entity_instance) -> None:
|
||||
props = bpy.context.scene.BIMResourceProperties
|
||||
props = cls.get_resource_props()
|
||||
props.quantity_attributes.clear()
|
||||
props.is_editing_quantity = True
|
||||
bonsai.bim.helper.import_attributes2(resource_quantity, props.quantity_attributes)
|
||||
bonsai.bim.helper.import_attributes(resource_quantity, props.quantity_attributes)
|
||||
|
||||
@classmethod
|
||||
def disable_editing_resource_quantity(cls) -> None:
|
||||
bpy.context.scene.BIMResourceProperties.is_editing_quantity = False
|
||||
props = cls.get_resource_props()
|
||||
props.property_unset("is_editing_quantity")
|
||||
|
||||
@classmethod
|
||||
def get_resource_quantity_attributes(cls) -> dict[str, Any]:
|
||||
return bonsai.bim.helper.export_attributes(bpy.context.scene.BIMResourceProperties.quantity_attributes)
|
||||
props = cls.get_resource_props()
|
||||
return bonsai.bim.helper.export_attributes(props.quantity_attributes)
|
||||
|
||||
@classmethod
|
||||
def expand_resource(cls, resource: ifcopenshell.entity_instance) -> None:
|
||||
props = bpy.context.scene.BIMResourceProperties
|
||||
props = cls.get_resource_props()
|
||||
contracted_resources = json.loads(props.contracted_resources)
|
||||
if not resource.id() in contracted_resources:
|
||||
return
|
||||
@@ -303,7 +314,7 @@ class Resource(bonsai.core.tool.Resource):
|
||||
|
||||
@classmethod
|
||||
def contract_resource(cls, resource: ifcopenshell.entity_instance) -> None:
|
||||
props = bpy.context.scene.BIMResourceProperties
|
||||
props = cls.get_resource_props()
|
||||
contracted_resources = json.loads(props.contracted_resources)
|
||||
contracted_resources.append(resource.id())
|
||||
props.contracted_resources = json.dumps(contracted_resources)
|
||||
@@ -328,16 +339,13 @@ class Resource(bonsai.core.tool.Resource):
|
||||
|
||||
@classmethod
|
||||
def get_highlighted_resource(cls) -> Union[ifcopenshell.entity_instance, None]:
|
||||
resources = len(bpy.context.scene.BIMResourceTreeProperties.resources)
|
||||
if resources and resources > bpy.context.scene.BIMResourceProperties.active_resource_index:
|
||||
return tool.Ifc.get().by_id(
|
||||
bpy.context.scene.BIMResourceTreeProperties.resources[
|
||||
bpy.context.scene.BIMResourceProperties.active_resource_index
|
||||
].ifc_definition_id
|
||||
)
|
||||
props = cls.get_resource_props()
|
||||
active_resource = props.active_resource
|
||||
if active_resource:
|
||||
return tool.Ifc.get().by_id(active_resource.ifc_definition_id)
|
||||
|
||||
@classmethod
|
||||
def clear_productivity_data(cls, props: bpy.types.PropertyGroup) -> None:
|
||||
def clear_productivity_data(cls, props: BIMResourceProductivity) -> None:
|
||||
for duration_prop in props.quantity_consumed or []:
|
||||
if duration_prop.name == "BaseQuantityConsumed":
|
||||
duration_prop.years = 0
|
||||
@@ -352,22 +360,21 @@ class Resource(bonsai.core.tool.Resource):
|
||||
@classmethod
|
||||
def load_productivity_data(cls) -> None:
|
||||
duration_props = None
|
||||
for collection_prop in bpy.context.scene.BIMResourceProductivity.quantity_consumed:
|
||||
productivity_props = tool.Resource.get_resource_props().productivity
|
||||
for collection_prop in productivity_props.quantity_consumed:
|
||||
duration_props = collection_prop if collection_prop.name == "BaseQuantityConsumed" else None
|
||||
break
|
||||
if not duration_props:
|
||||
duration_props = bpy.context.scene.BIMResourceProductivity.quantity_consumed.add()
|
||||
duration_props = productivity_props.quantity_consumed.add()
|
||||
duration_props.name = "BaseQuantityConsumed"
|
||||
cls.clear_productivity_data(bpy.context.scene.BIMResourceProductivity)
|
||||
cls.clear_productivity_data(productivity_props)
|
||||
current_resource = tool.Resource.get_highlighted_resource()
|
||||
if current_resource:
|
||||
productivity = cls.get_productivity(current_resource)
|
||||
if productivity:
|
||||
bpy.context.scene.BIMResourceProductivity.quantity_produced = (
|
||||
ifcopenshell.util.resource.get_quantity_produced(productivity)
|
||||
)
|
||||
bpy.context.scene.BIMResourceProductivity.quantity_produced_name = (
|
||||
ifcopenshell.util.resource.get_quantity_produced_name(productivity)
|
||||
productivity_props.quantity_produced = ifcopenshell.util.resource.get_quantity_produced(productivity)
|
||||
productivity_props.quantity_produced_name = ifcopenshell.util.resource.get_quantity_produced_name(
|
||||
productivity
|
||||
)
|
||||
time_consumed = ifcopenshell.util.resource.get_unit_consumed(productivity)
|
||||
if time_consumed:
|
||||
@@ -383,7 +390,7 @@ class Resource(bonsai.core.tool.Resource):
|
||||
|
||||
@classmethod
|
||||
def get_productivity_attributes(cls) -> dict[str, Any]:
|
||||
props = bpy.context.scene.BIMResourceProductivity
|
||||
props = tool.Resource.get_resource_props().productivity
|
||||
productivity = {}
|
||||
if props.quantity_consumed:
|
||||
import bonsai.bim.module.sequence.helper as helper
|
||||
@@ -442,24 +449,24 @@ class Resource(bonsai.core.tool.Resource):
|
||||
|
||||
@classmethod
|
||||
def go_to_resource(cls, resource: ifcopenshell.entity_instance) -> None:
|
||||
def get_ancestors_ids(resource):
|
||||
ids = []
|
||||
def get_ancestors_ids(resource: ifcopenshell.entity_instance) -> list[int]:
|
||||
ids: list[int] = []
|
||||
for rel in resource.Nests or []:
|
||||
ids.append(rel.RelatingObject.id())
|
||||
ids.extend(get_ancestors_ids(rel.RelatingObject))
|
||||
return ids
|
||||
|
||||
props = cls.get_resource_props()
|
||||
ancestors = get_ancestors_ids(resource)
|
||||
contracted_resources = json.loads(bpy.context.scene.BIMResourceProperties.contracted_resources)
|
||||
contracted_resources = json.loads(props.contracted_resources)
|
||||
for ancestor in ancestors:
|
||||
if ancestor in contracted_resources:
|
||||
contracted_resources.remove(ancestor)
|
||||
bpy.context.scene.BIMResourceProperties.contracted_resources = json.dumps(contracted_resources)
|
||||
props.contracted_resources = json.dumps(contracted_resources)
|
||||
cls.load_resources()
|
||||
|
||||
resource_props = bpy.context.scene.BIMResourceTreeProperties
|
||||
expanded_resources = [item.ifc_definition_id for item in resource_props.resources]
|
||||
bpy.context.scene.BIMResourceProperties.active_resource_index = expanded_resources.index(resource.id())
|
||||
expanded_resources = [item.ifc_definition_id for item in props.tree.resources]
|
||||
props.active_resource_index = expanded_resources.index(resource.id())
|
||||
|
||||
@classmethod
|
||||
def run_calculate_resource_usage(cls, resource: ifcopenshell.entity_instance) -> None:
|
||||
|
||||
@@ -112,7 +112,7 @@ class Sequence(bonsai.core.tool.Sequence):
|
||||
|
||||
props = cls.get_work_plan_props()
|
||||
props.work_plan_attributes.clear()
|
||||
bonsai.bim.helper.import_attributes2(work_plan, props.work_plan_attributes, callback)
|
||||
bonsai.bim.helper.import_attributes(work_plan, props.work_plan_attributes, callback)
|
||||
|
||||
@classmethod
|
||||
def enable_editing_work_plan(cls, work_plan: Union[ifcopenshell.entity_instance, None]) -> None:
|
||||
@@ -163,7 +163,7 @@ class Sequence(bonsai.core.tool.Sequence):
|
||||
|
||||
props = cls.get_work_schedule_props()
|
||||
props.work_schedule_attributes.clear()
|
||||
bonsai.bim.helper.import_attributes2(work_schedule, props.work_schedule_attributes, callback)
|
||||
bonsai.bim.helper.import_attributes(work_schedule, props.work_schedule_attributes, callback)
|
||||
|
||||
@classmethod
|
||||
def enable_editing_work_schedule(cls, work_schedule: ifcopenshell.entity_instance) -> None:
|
||||
@@ -382,7 +382,7 @@ class Sequence(bonsai.core.tool.Sequence):
|
||||
def load_task_attributes(cls, task: ifcopenshell.entity_instance) -> None:
|
||||
props = cls.get_work_schedule_props()
|
||||
props.task_attributes.clear()
|
||||
bonsai.bim.helper.import_attributes2(task, props.task_attributes)
|
||||
bonsai.bim.helper.import_attributes(task, props.task_attributes)
|
||||
|
||||
@classmethod
|
||||
def enable_editing_task_attributes(cls, task: ifcopenshell.entity_instance) -> None:
|
||||
@@ -424,7 +424,7 @@ class Sequence(bonsai.core.tool.Sequence):
|
||||
|
||||
props.task_time_attributes.clear()
|
||||
props.durations_attributes.clear()
|
||||
bonsai.bim.helper.import_attributes2(task_time, props.task_time_attributes, callback)
|
||||
bonsai.bim.helper.import_attributes(task_time, props.task_time_attributes, callback)
|
||||
|
||||
@classmethod
|
||||
def enable_editing_task_time(cls, task: ifcopenshell.entity_instance) -> None:
|
||||
@@ -562,7 +562,7 @@ class Sequence(bonsai.core.tool.Sequence):
|
||||
def load_work_calendar_attributes(cls, work_calendar: ifcopenshell.entity_instance) -> dict[str, Any]:
|
||||
props = bpy.context.scene.BIMWorkCalendarProperties
|
||||
props.work_calendar_attributes.clear()
|
||||
return bonsai.bim.helper.import_attributes2(work_calendar, props.work_calendar_attributes)
|
||||
return bonsai.bim.helper.import_attributes(work_calendar, props.work_calendar_attributes)
|
||||
|
||||
@classmethod
|
||||
def enable_editing_work_calendar(cls, work_calendar: ifcopenshell.entity_instance) -> None:
|
||||
@@ -582,7 +582,7 @@ class Sequence(bonsai.core.tool.Sequence):
|
||||
props = bpy.context.scene.BIMWorkCalendarProperties
|
||||
props.work_time_attributes.clear()
|
||||
|
||||
bonsai.bim.helper.import_attributes2(work_time, props.work_time_attributes)
|
||||
bonsai.bim.helper.import_attributes(work_time, props.work_time_attributes)
|
||||
|
||||
@classmethod
|
||||
def enable_editing_work_time(cls, work_time: ifcopenshell.entity_instance) -> None:
|
||||
@@ -713,7 +713,7 @@ class Sequence(bonsai.core.tool.Sequence):
|
||||
def load_rel_sequence_attributes(cls, rel_sequence: ifcopenshell.entity_instance) -> None:
|
||||
props = cls.get_work_schedule_props()
|
||||
props.sequence_attributes.clear()
|
||||
bonsai.bim.helper.import_attributes2(rel_sequence, props.sequence_attributes)
|
||||
bonsai.bim.helper.import_attributes(rel_sequence, props.sequence_attributes)
|
||||
|
||||
@classmethod
|
||||
def enable_editing_rel_sequence_attributes(cls, rel_sequence: ifcopenshell.entity_instance) -> None:
|
||||
@@ -738,7 +738,7 @@ class Sequence(bonsai.core.tool.Sequence):
|
||||
return True
|
||||
|
||||
props.lag_time_attributes.clear()
|
||||
bonsai.bim.helper.import_attributes2(lag_time, props.lag_time_attributes, callback)
|
||||
bonsai.bim.helper.import_attributes(lag_time, props.lag_time_attributes, callback)
|
||||
|
||||
@classmethod
|
||||
def enable_editing_sequence_lag_time(cls, rel_sequence: ifcopenshell.entity_instance) -> None:
|
||||
|
||||
@@ -561,8 +561,8 @@ class Spatial(bonsai.core.tool.Spatial):
|
||||
@classmethod
|
||||
def get_active_container(cls) -> Union[ifcopenshell.entity_instance, None]:
|
||||
props = cls.get_spatial_props()
|
||||
if props.active_container_index < len(props.containers):
|
||||
container = tool.Ifc.get().by_id(props.containers[props.active_container_index].ifc_definition_id)
|
||||
if active_container := props.active_container:
|
||||
container = tool.Ifc.get().by_id(active_container.ifc_definition_id)
|
||||
return container
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -560,7 +560,7 @@ class Style(bonsai.core.tool.Style):
|
||||
props = cls.get_style_props()
|
||||
attributes = props.attributes
|
||||
attributes.clear()
|
||||
bonsai.bim.helper.import_attributes2(style, attributes)
|
||||
bonsai.bim.helper.import_attributes(style, attributes)
|
||||
|
||||
@classmethod
|
||||
def has_blender_external_style(cls, style_elements: dict[str, ifcopenshell.entity_instance]) -> bool:
|
||||
|
||||
@@ -151,7 +151,7 @@ class System(bonsai.core.tool.System):
|
||||
def import_system_attributes(cls, system: ifcopenshell.entity_instance) -> None:
|
||||
props = cls.get_system_props()
|
||||
props.system_attributes.clear()
|
||||
bonsai.bim.helper.import_attributes2(system, props.system_attributes)
|
||||
bonsai.bim.helper.import_attributes(system, props.system_attributes)
|
||||
|
||||
@classmethod
|
||||
def get_systems(cls) -> list[ifcopenshell.entity_instance]:
|
||||
|
||||
@@ -16,11 +16,23 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Union
|
||||
|
||||
import bpy
|
||||
import ifctester.ids
|
||||
import ifctester.reporter
|
||||
from typing import Union
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bonsai.bim.module.tester.prop import IfcTesterProperties
|
||||
|
||||
|
||||
class Tester:
|
||||
specs: Union[ifctester.ids.Ids, None] = None
|
||||
report: list[ifctester.reporter.ResultsSpecification] = []
|
||||
|
||||
@classmethod
|
||||
def get_tester_props(cls) -> IfcTesterProperties:
|
||||
assert (scene := bpy.context.scene)
|
||||
return scene.IfcTesterProperties # pyright: ignore[reportAttributeAccessIssue]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user