diff --git a/src/bonsai/bonsai/bim/module/cad/workspace.py b/src/bonsai/bonsai/bim/module/cad/workspace.py index d0d61f4157..d5027a39fe 100644 --- a/src/bonsai/bonsai/bim/module/cad/workspace.py +++ b/src/bonsai/bonsai/bim/module/cad/workspace.py @@ -322,10 +322,10 @@ def add_header_apply_button(layout, text, apply_operator, cancel_operator, ui_co ) row = layout.row(align=True) - row.operator(apply_operator, text="Apply" if ui_context!="TOOL_HEADER" else "", icon="CHECKMARK") - row.label(text="", icon="EVENT_TAB") if ui_context!="TOOL_HEADER" else row - row = layout.row(align=True) if ui_context!="TOOL_HEADER" else row - row.operator(cancel_operator, text="Cancel" if ui_context!="TOOL_HEADER" else "", icon="CANCEL") + row.operator(apply_operator, text="Apply" if ui_context != "TOOL_HEADER" else "", icon="CHECKMARK") + row.label(text="", icon="EVENT_TAB") if ui_context != "TOOL_HEADER" else row + row = layout.row(align=True) if ui_context != "TOOL_HEADER" else row + row.operator(cancel_operator, text="Cancel" if ui_context != "TOOL_HEADER" else "", icon="CANCEL") row = layout.row(align=True) row.label(text="Tools") diff --git a/src/bonsai/bonsai/bim/module/geometry/__init__.py b/src/bonsai/bonsai/bim/module/geometry/__init__.py index 1caba354ba..d2d82e450e 100644 --- a/src/bonsai/bonsai/bim/module/geometry/__init__.py +++ b/src/bonsai/bonsai/bim/module/geometry/__init__.py @@ -58,6 +58,7 @@ classes = ( operator.SelectConnection, operator.SwitchRepresentation, operator.UnassignRepresentationItemStyle, + operator.UpdateItemAttributes, operator.UpdateParametricRepresentation, operator.UpdateRepresentation, prop.RepresentationItem, diff --git a/src/bonsai/bonsai/bim/module/geometry/operator.py b/src/bonsai/bonsai/bim/module/geometry/operator.py index c3253c5511..b6fa28afdd 100644 --- a/src/bonsai/bonsai/bim/module/geometry/operator.py +++ b/src/bonsai/bonsai/bim/module/geometry/operator.py @@ -1780,7 +1780,7 @@ class OverrideModeSetObject(bpy.types.Operator, tool.Ifc.Operator): tool.Geometry.reload_representation(bpy.context.scene.BIMGeometryProperties.representation_obj) return else: - self.reload_item(obj) + tool.Geometry.import_item(obj) return elif item.is_a("IfcSweptAreaSolid"): unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) @@ -1866,15 +1866,6 @@ class OverrideModeSetObject(bpy.types.Operator, tool.Ifc.Operator): context.scene.BIMGeometryProperties.mode = "EDIT" context.scene.BIMGeometryProperties.is_changing_mode = False - def reload_item(self, obj): - tool.Loader.settings.contexts = ifcopenshell.util.representation.get_prioritised_contexts(tool.Ifc.get()) - tool.Loader.settings.context_settings = tool.Loader.create_settings() - tool.Loader.settings.gross_context_settings = tool.Loader.create_settings(is_gross=True) - item = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id) - geometry = tool.Loader.create_generic_shape(item) - obj.data.clear_geometry() - tool.Loader.convert_geometry_to_mesh(geometry, obj.data) - def invoke(self, context, event): return IfcStore.execute_ifc_operator(self, context, is_invoke=True) @@ -2388,27 +2379,33 @@ class ImportRepresentationItems(bpy.types.Operator, tool.Ifc.Operator): item = tool.Ifc.get().by_id(item_id) item_mesh = bpy.data.meshes.new(f"Item/{item.is_a()}/{item_id}") item_mesh.BIMMeshProperties.ifc_definition_id = item_id - geometry = tool.Loader.create_generic_shape(item) - tool.Loader.convert_geometry_to_mesh(geometry, item_mesh) item_obj = bpy.data.objects.new(f"Item/{item.is_a()}/{item_id}", item_mesh) item_obj.matrix_world = obj.matrix_world item_obj.show_in_front = True bpy.context.collection.objects.link(item_obj) - if tool.Geometry.is_meshlike_item(item): - tool.Geometry.lock_object(item_obj) - new = props.item_objs.add() new.obj = item_obj - if item.is_a("IfcSweptAreaSolid"): - tool.Geometry.record_object_position(item_obj) - if item.Position: - unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) - position = Matrix(ifcopenshell.util.placement.get_axis2placement(item.Position).tolist()) - position.translation *= unit_scale - position_i = position.inverted() - item_obj.matrix_world = item_obj.matrix_world @ position - for vert in item_obj.data.vertices: - vert.co = position_i @ vert.co + tool.Geometry.import_item(item_obj) + tool.Geometry.import_item_attributes(item_obj) + + if tool.Geometry.is_meshlike_item(item): + tool.Geometry.lock_object(item_obj) + + +class UpdateItemAttributes(bpy.types.Operator, tool.Ifc.Operator): + bl_idname = "bim.update_item_attributes" + bl_label = "Update Item Attributes" + bl_options = {"REGISTER", "UNDO"} + + def _execute(self, context): + obj = context.active_object + props = obj.data.BIMMeshProperties + item = tool.Ifc.get().by_id(props.ifc_definition_id) + for attribute in props.item_attributes: + if attribute.name == "Depth": + item.Depth = attribute.float_value + tool.Geometry.reload_representation(bpy.context.scene.BIMGeometryProperties.representation_obj) + tool.Geometry.import_item(obj) diff --git a/src/bonsai/bonsai/bim/module/model/workspace.py b/src/bonsai/bonsai/bim/module/model/workspace.py index 1b2a113f15..531e54c7be 100644 --- a/src/bonsai/bonsai/bim/module/model/workspace.py +++ b/src/bonsai/bonsai/bim/module/model/workspace.py @@ -105,12 +105,17 @@ class BimTool(WorkSpaceTool): ("bim.hotkey", {"type": "P", "value": "PRESS", "ctrl": True}, {"properties": [("hotkey", "C_P")]}), ("bim.hotkey", {"type": "P", "value": "PRESS", "alt": True}, {"properties": [("hotkey", "A_P")]}), ) + ifc_element_type = "all" - def draw_settings(context, layout, ws_tool): + @classmethod + def draw_settings(cls, context, layout, ws_tool): # Unlike operators, Blender doesn't treat workspace tools as a class, so we'll create our own. - CreateObjectUI.draw(context, layout, ifc_element_type="all") - if context.active_object and context.selected_objects: - EditObjectUI.draw(context, layout) + if context.scene.BIMGeometryProperties.mode == "ITEM": + EditItemUI.draw(context, layout) + else: + CreateObjectUI.draw(context, layout, ifc_element_type=cls.ifc_element_type) + if context.active_object and context.selected_objects: + EditObjectUI.draw(context, layout, ifc_element_type=cls.ifc_element_type) class WallTool(BimTool): @@ -123,12 +128,6 @@ class WallTool(BimTool): bl_widget = None ifc_element_type = "IfcWallType" - @classmethod - def draw_settings(cls, context, layout, ws_tool): - CreateObjectUI.draw(context, layout, ifc_element_type=cls.ifc_element_type) - if context.active_object and context.selected_objects: - EditObjectUI.draw(context, layout, ifc_element_type=cls.ifc_element_type) - class SlabTool(BimTool): bl_space_type = "VIEW_3D" @@ -140,12 +139,6 @@ class SlabTool(BimTool): bl_widget = None ifc_element_type = "IfcSlabType" - @classmethod - def draw_settings(cls, context, layout, ws_tool): - CreateObjectUI.draw(context, layout, ifc_element_type=cls.ifc_element_type) - if context.active_object and context.selected_objects: - EditObjectUI.draw(context, layout, ifc_element_type=cls.ifc_element_type) - class DoorTool(BimTool): bl_space_type = "VIEW_3D" @@ -157,12 +150,6 @@ class DoorTool(BimTool): bl_widget = None ifc_element_type = "IfcDoorType" - @classmethod - def draw_settings(cls, context, layout, ws_tool): - CreateObjectUI.draw(context, layout, ifc_element_type=cls.ifc_element_type) - if context.active_object and context.selected_objects: - EditObjectUI.draw(context, layout, ifc_element_type=cls.ifc_element_type) - class WindowTool(BimTool): bl_space_type = "VIEW_3D" @@ -174,12 +161,6 @@ class WindowTool(BimTool): bl_widget = None ifc_element_type = "IfcWindowType" - @classmethod - def draw_settings(cls, context, layout, ws_tool): - CreateObjectUI.draw(context, layout, ifc_element_type=cls.ifc_element_type) - if context.active_object and context.selected_objects: - EditObjectUI.draw(context, layout, ifc_element_type=cls.ifc_element_type) - class ColumnTool(BimTool): bl_space_type = "VIEW_3D" @@ -191,12 +172,6 @@ class ColumnTool(BimTool): bl_widget = None ifc_element_type = "IfcColumnType" - @classmethod - def draw_settings(cls, context, layout, ws_tool): - CreateObjectUI.draw(context, layout, ifc_element_type=cls.ifc_element_type) - if context.active_object and context.selected_objects: - EditObjectUI.draw(context, layout, ifc_element_type=cls.ifc_element_type) - class BeamTool(BimTool): bl_space_type = "VIEW_3D" @@ -208,12 +183,6 @@ class BeamTool(BimTool): bl_widget = None ifc_element_type = "IfcBeamType" - @classmethod - def draw_settings(cls, context, layout, ws_tool): - CreateObjectUI.draw(context, layout, ifc_element_type=cls.ifc_element_type) - if context.active_object and context.selected_objects: - EditObjectUI.draw(context, layout, ifc_element_type=cls.ifc_element_type) - class DuctTool(BimTool): bl_space_type = "VIEW_3D" @@ -225,12 +194,6 @@ class DuctTool(BimTool): bl_widget = None ifc_element_type = "IfcDuctSegmentType" - @classmethod - def draw_settings(cls, context, layout, ws_tool): - CreateObjectUI.draw(context, layout, ifc_element_type=cls.ifc_element_type) - if context.active_object and context.selected_objects: - EditObjectUI.draw(context, layout, ifc_element_type=cls.ifc_element_type) - class CableCarrierTool(BimTool): bl_space_type = "VIEW_3D" @@ -242,12 +205,6 @@ class CableCarrierTool(BimTool): bl_widget = None ifc_element_type = "IfcCableCarrierSegmentType" - @classmethod - def draw_settings(cls, context, layout, ws_tool): - CreateObjectUI.draw(context, layout, ifc_element_type=cls.ifc_element_type) - if context.active_object and context.selected_objects: - EditObjectUI.draw(context, layout, ifc_element_type=cls.ifc_element_type) - class PipeTool(BimTool): bl_space_type = "VIEW_3D" @@ -259,12 +216,6 @@ class PipeTool(BimTool): bl_widget = None ifc_element_type = "IfcPipeSegmentType" - @classmethod - def draw_settings(cls, context, layout, ws_tool): - CreateObjectUI.draw(context, layout, ifc_element_type=cls.ifc_element_type) - if context.active_object and context.selected_objects: - EditObjectUI.draw(context, layout, ifc_element_type=cls.ifc_element_type) - class CableTool(BimTool): bl_space_type = "VIEW_3D" @@ -276,12 +227,6 @@ class CableTool(BimTool): bl_widget = None ifc_element_type = "IfcCableSegmentType" - @classmethod - def draw_settings(cls, context, layout, ws_tool): - CreateObjectUI.draw(context, layout, ifc_element_type=cls.ifc_element_type) - if context.active_object and context.selected_objects: - EditObjectUI.draw(context, layout, ifc_element_type=cls.ifc_element_type) - def add_layout_hotkey_operator(layout, text, hotkey, description, ui_context=""): parts = hotkey.split("_") if hotkey else [] @@ -314,6 +259,22 @@ def format_ifc_camel_case(string): return "".join(" " + char if char.isupper() else char for char in string).strip() +class EditItemUI: + @classmethod + def draw(cls, context, layout): + cls.layout = layout + row = cls.layout.row() + row.label(text="Item Mode", icon="MESH_DATA") + if not (obj := context.active_object) or not tool.Geometry.is_representation_item(obj): + return + for item_attribute in obj.data.BIMMeshProperties.item_attributes: + row = cls.layout.row() + row.prop(item_attribute, "float_value", text=item_attribute.name) + if len(obj.data.BIMMeshProperties.item_attributes): + row = cls.layout.row() + row.operator("bim.update_item_attributes", icon="FILE_REFRESH", text="") + + class CreateObjectUI: @classmethod def draw(cls, context, layout, ifc_element_type=None): diff --git a/src/bonsai/bonsai/bim/prop.py b/src/bonsai/bonsai/bim/prop.py index f58f44ad0c..853995ec64 100644 --- a/src/bonsai/bonsai/bim/prop.py +++ b/src/bonsai/bonsai/bim/prop.py @@ -508,6 +508,7 @@ class BIMMeshProperties(PropertyGroup): subshape_type: StringProperty(name="Subshape Type") ifc_definition: StringProperty(name="IFC Definition") ifc_parameters: CollectionProperty(name="IFC Parameters", type=IfcParameter) + item_attributes: CollectionProperty(name="Item Attributes", type=Attribute) material_checksum: StringProperty(name="Material Checksum", default="[]") mesh_checksum: StringProperty(name="Mesh Checksum", default="") replaced_mesh: PointerProperty(type=bpy.types.Mesh, description="Original mesh to revert section cutaway") diff --git a/src/bonsai/bonsai/tool/geometry.py b/src/bonsai/bonsai/tool/geometry.py index 3cd1db1cd9..11f2b6270b 100644 --- a/src/bonsai/bonsai/tool/geometry.py +++ b/src/bonsai/bonsai/tool/geometry.py @@ -31,6 +31,7 @@ import ifcopenshell.guid import ifcopenshell.util.element import ifcopenshell.util.representation import ifcopenshell.util.system +import ifcopenshell.util.placement import bonsai.core.tool import bonsai.core.drawing import bonsai.core.geometry @@ -1438,3 +1439,35 @@ class Geometry(bonsai.core.tool.Geometry): ifcopenshell.util.element.remove_deep2(tool.Ifc.get(), old_position) if has_changed: cls.reload_representation(rep_obj) + + @classmethod + def import_item_attributes(cls, obj: bpy.types.Object) -> None: + props = obj.data.BIMMeshProperties + props.item_attributes.clear() + item = tool.Ifc.get().by_id(props.ifc_definition_id) + if item.is_a("IfcExtrudedAreaSolid"): + new = props.item_attributes.add() + new.name = "Depth" + new.float_value = item.Depth + + @classmethod + def import_item(cls, obj: bpy.types.Object) -> None: + props = bpy.context.scene.BIMGeometryProperties + tool.Loader.settings.contexts = ifcopenshell.util.representation.get_prioritised_contexts(tool.Ifc.get()) + tool.Loader.settings.context_settings = tool.Loader.create_settings() + tool.Loader.settings.gross_context_settings = tool.Loader.create_settings(is_gross=True) + item = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id) + geometry = tool.Loader.create_generic_shape(item) + obj.data.clear_geometry() + tool.Loader.convert_geometry_to_mesh(geometry, obj.data) + + if item.is_a("IfcSweptAreaSolid"): + cls.record_object_position(obj) + if item.Position: + unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) + position = Matrix(ifcopenshell.util.placement.get_axis2placement(item.Position).tolist()) + position.translation *= unit_scale + position_i = position.inverted() + obj.matrix_world = props.representation_obj.matrix_world @ position + for vert in obj.data.vertices: + vert.co = position_i @ vert.co diff --git a/src/ifcopenshell-python/ifcopenshell/util/cost.py b/src/ifcopenshell-python/ifcopenshell/util/cost.py index c13a562aa6..d1990c5c48 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/cost.py +++ b/src/ifcopenshell-python/ifcopenshell/util/cost.py @@ -23,6 +23,7 @@ import ifcopenshell.ifcopenshell_wrapper as ifcopenshell_wrapper from ifcopenshell.util.doc import get_predefined_type_doc from ifcopenshell.util.element import get_psets from ifcopenshell.util.unit import get_unit_symbol + arithmetic_operator_symbols = {"ADD": "+", "DIVIDE": "/", "MULTIPLY": "*", "SUBTRACT": "-"} symbol_arithmetic_operators = {"+": "ADD", "/": "DIVIDE", "*": "MULTIPLY", "-": "SUBTRACT"} FILTER_BY_TYPE = Literal["PRODUCT", "RESOURCE", "PROCESS"]