From d2f19e7c285152978aece7ff3b6ce207a3c04a64 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Tue, 30 Mar 2021 12:00:25 +1100 Subject: [PATCH] You can now edit structural boundary conditions on member connections --- .../bim/module/structural/__init__.py | 1 + .../bim/module/structural/operator.py | 46 ++++-- .../blenderbim/bim/module/structural/prop.py | 4 +- .../blenderbim/bim/module/structural/ui.py | 135 ++++++++++-------- .../add_structural_boundary_condition.py | 12 +- .../ifcopenshell/api/structural/data.py | 79 +++++----- 6 files changed, 165 insertions(+), 112 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/structural/__init__.py b/src/blenderbim/blenderbim/bim/module/structural/__init__.py index d543d5dc13..816b5b374e 100644 --- a/src/blenderbim/blenderbim/bim/module/structural/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/structural/__init__.py @@ -18,6 +18,7 @@ classes = ( operator.DisableEditingStructuralBoundaryCondition, operator.AddStructuralMemberConnection, operator.EnableEditingStructuralConnectionCondition, + operator.DisableEditingStructuralConnectionCondition, operator.RemoveStructuralConnectionCondition, prop.StructuralAnalysisModel, prop.BIMStructuralProperties, diff --git a/src/blenderbim/blenderbim/bim/module/structural/operator.py b/src/blenderbim/blenderbim/bim/module/structural/operator.py index d193941a3e..131fbf548a 100644 --- a/src/blenderbim/blenderbim/bim/module/structural/operator.py +++ b/src/blenderbim/blenderbim/bim/module/structural/operator.py @@ -17,8 +17,28 @@ class AddStructuralMemberConnection(bpy.types.Operator): class EnableEditingStructuralConnectionCondition(bpy.types.Operator): bl_idname = "bim.enable_editing_structural_connection_condition" bl_label = "Enable Editing Structural Connection Condition" + connects_structural_member: bpy.props.IntProperty() def execute(self, context): + obj = bpy.context.active_object + oprops = obj.BIMObjectProperties + props = obj.BIMStructuralProperties + applied_condition_id = Data.connects_structural_members[self.connects_structural_member]["AppliedCondition"] + if applied_condition_id: + bpy.ops.bim.enable_editing_structural_boundary_condition(boundary_condition=applied_condition_id) + + props.active_connects_structural_member = self.connects_structural_member + return {"FINISHED"} + + +class DisableEditingStructuralConnectionCondition(bpy.types.Operator): + bl_idname = "bim.disable_editing_structural_connection_condition" + bl_label = "Disable Editing Structural Connection Condition" + + def execute(self, context): + obj = bpy.context.active_object + props = obj.BIMStructuralProperties + props.active_connects_structural_member = 0 return {"FINISHED"} @@ -33,13 +53,16 @@ class RemoveStructuralConnectionCondition(bpy.types.Operator): class AddStructuralBoundaryCondition(bpy.types.Operator): bl_idname = "bim.add_structural_boundary_condition" bl_label = "Add Structural Boundary Condition" + connection: bpy.props.IntProperty() def execute(self, context): - obj = context.active_object file = IfcStore.get_file() - connection = file.by_id(obj.BIMObjectProperties.ifc_definition_id) + connection = file.by_id(self.connection) ifcopenshell.api.run("structural.add_structural_boundary_condition", file, **{"connection": connection}) - Data.load(IfcStore.get_file(), connection.id()) + if connection.is_a("IfcRelConnectsStructuralMember"): + Data.load(IfcStore.get_file(), connection.RelatedStructuralConnection.id()) + else: + Data.load(IfcStore.get_file(), connection.id()) return {"FINISHED"} @@ -59,15 +82,15 @@ class RemoveStructuralBoundaryCondition(bpy.types.Operator): class EnableEditingStructuralBoundaryCondition(bpy.types.Operator): bl_idname = "bim.enable_editing_structural_boundary_condition" bl_label = "Enable Editing Structural Boundary Condition" + boundary_condition: bpy.props.IntProperty() def execute(self, context): obj = context.active_object - oprops = obj.BIMObjectProperties props = obj.BIMStructuralProperties while len(props.boundary_condition_attributes) > 0: props.boundary_condition_attributes.remove(0) - data = Data.boundary_conditions[oprops.ifc_definition_id] + data = Data.boundary_conditions[self.boundary_condition] for attribute in IfcStore.get_schema().declaration_by_name(data["type"]).all_attributes(): value = data[attribute.name()] @@ -91,21 +114,21 @@ class EnableEditingStructuralBoundaryCondition(bpy.types.Operator): new.string_value = "" if new.is_null else data[attribute.name()] new.data_type = "string" - props.is_editing_boundary_condition = True + props.active_boundary_condition = self.boundary_condition return {"FINISHED"} class EditStructuralBoundaryCondition(bpy.types.Operator): bl_idname = "bim.edit_structural_boundary_condition" bl_label = "Edit Structural Boundary Condition" + connection: bpy.props.IntProperty() def execute(self, context): obj = context.active_object - oprops = obj.BIMObjectProperties props = obj.BIMStructuralProperties file = IfcStore.get_file() - connection = file.by_id(obj.BIMObjectProperties.ifc_definition_id) + connection = file.by_id(self.connection) condition = connection.AppliedCondition attributes = {} @@ -122,7 +145,10 @@ class EditStructuralBoundaryCondition(bpy.types.Operator): ifcopenshell.api.run( "structural.edit_structural_boundary_condition", file, **{"condition": condition, "attributes": attributes} ) - Data.load(IfcStore.get_file(), connection.id()) + if connection.is_a("IfcRelConnectsStructuralMember"): + Data.load(IfcStore.get_file(), connection.RelatedStructuralConnection.id()) + else: + Data.load(IfcStore.get_file(), connection.id()) bpy.ops.bim.disable_editing_structural_boundary_condition() return {"FINISHED"} @@ -132,7 +158,7 @@ class DisableEditingStructuralBoundaryCondition(bpy.types.Operator): bl_label = "Disable Editing Structural Boundary Condition" def execute(self, context): - context.active_object.BIMStructuralProperties.is_editing_boundary_condition = False + context.active_object.BIMStructuralProperties.active_boundary_condition = 0 return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/structural/prop.py b/src/blenderbim/blenderbim/bim/module/structural/prop.py index a642b047cd..ec09012432 100644 --- a/src/blenderbim/blenderbim/bim/module/structural/prop.py +++ b/src/blenderbim/blenderbim/bim/module/structural/prop.py @@ -30,5 +30,5 @@ class BIMStructuralProperties(PropertyGroup): class BIMObjectStructuralProperties(PropertyGroup): boundary_condition_attributes: CollectionProperty(name="Boundary Condition Attributes", type=Attribute) - is_editing_boundary_condition: BoolProperty(name="Is Editing Boundary Condition", default=False) - is_editing_connection_conditions: BoolProperty(name="Is Editing Connection Conditions", default=False) + active_boundary_condition: IntProperty(name="Active Boundary Condition") + active_connects_structural_member: IntProperty(name="Active Connects Structural Member") diff --git a/src/blenderbim/blenderbim/bim/module/structural/ui.py b/src/blenderbim/blenderbim/bim/module/structural/ui.py index cee838b32c..6c38cdf02e 100644 --- a/src/blenderbim/blenderbim/bim/module/structural/ui.py +++ b/src/blenderbim/blenderbim/bim/module/structural/ui.py @@ -3,6 +3,61 @@ from blenderbim.bim.ifc import IfcStore from ifcopenshell.api.structural.data import Data +def draw_boundary_condition_ui(layout, boundary_condition_id, connection_id, props): + data = Data.boundary_conditions[boundary_condition_id] if boundary_condition_id else {} + row = layout.row(align=True) + if not data: + row.label(text="No Boundary Condition Found", icon="CON_TRACKTO") + row.operator("bim.add_structural_boundary_condition", text="", icon="ADD").connection = connection_id + return + + if props.active_boundary_condition and props.active_boundary_condition == boundary_condition_id: + row.label(text=data["type"], icon="CON_TRACKTO") + row.operator("bim.edit_structural_boundary_condition", text="", icon="CHECKMARK").connection = connection_id + row.operator("bim.disable_editing_structural_boundary_condition", text="", icon="X") + elif props.active_boundary_condition and props.active_boundary_condition != boundary_condition_id: + row.label(text=data["type"], icon="CON_TRACKTO") + row.operator("bim.remove_structural_boundary_condition", text="", icon="X") + else: + row.label(text=data["type"], icon="CON_TRACKTO") + op = row.operator("bim.enable_editing_structural_boundary_condition", text="", icon="GREASEPENCIL") + op.boundary_condition = data["id"] + row.operator("bim.remove_structural_boundary_condition", text="", icon="X") + + if props.active_boundary_condition and props.active_boundary_condition == boundary_condition_id: + draw_boundary_condition_editable_ui(layout, props) + else: + draw_boundary_condition_read_only_ui(layout, data) + + +def draw_boundary_condition_editable_ui(layout, props): + for attribute in props.boundary_condition_attributes: + if attribute.data_type == "string": + row = layout.row() + row.prop(attribute, "string_value", text=attribute["name"]) + else: + row = layout.row(align=True) + row.prop(attribute, "enum_value", text=attribute["name"]) + if attribute.enum_value == "IfcBoolean": + row.prop(attribute, "bool_value", text="") + else: + row.prop(attribute, "float_value", text="") + if attribute.is_optional: + row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="") + +def draw_boundary_condition_read_only_ui(layout, boundary_condition_data): + for key, value in boundary_condition_data.items(): + if key == "id" or key == "type" or value == None: + continue + row = layout.row(align=True) + row.label(text=key) + if isinstance(value, bool): + row.label(text="", icon="CHECKBOX_HLT" if value else "CHECKBOX_DEHLT") + else: + row.label(text=str(value)) + + + class BIM_PT_structural_boundary_conditions(Panel): bl_label = "IFC Structural Boundary Conditions" bl_idname = "BIM_PT_structural_boundary_conditions" @@ -25,54 +80,11 @@ class BIM_PT_structural_boundary_conditions(Panel): def draw(self, context): self.oprops = context.active_object.BIMObjectProperties self.props = context.active_object.BIMStructuralProperties - if self.oprops.ifc_definition_id not in Data.boundary_conditions: + if self.oprops.ifc_definition_id not in Data.connections: Data.load(IfcStore.get_file(), self.oprops.ifc_definition_id) - self.data = Data.boundary_conditions[self.oprops.ifc_definition_id] - - row = self.layout.row(align=True) - if self.data and self.props.is_editing_boundary_condition: - row.label(text=self.data["type"], icon="CON_TRACKTO") - row.operator("bim.edit_structural_boundary_condition", text="", icon="CHECKMARK") - row.operator("bim.disable_editing_structural_boundary_condition", text="", icon="X") - elif self.data and not self.props.is_editing_boundary_condition: - row.label(text=self.data["type"], icon="CON_TRACKTO") - row.operator("bim.enable_editing_structural_boundary_condition", text="", icon="GREASEPENCIL") - row.operator("bim.remove_structural_boundary_condition", text="", icon="X") - else: - row.label(text="No Boundary Condition Found", icon="CON_TRACKTO") - row.operator("bim.add_structural_boundary_condition", text="", icon="ADD") - - if self.props.is_editing_boundary_condition: - self.draw_editable_ui(context) - else: - self.draw_read_only_ui(context) - - def draw_editable_ui(self, context): - for attribute in self.props.boundary_condition_attributes: - if attribute.data_type == "string": - row = self.layout.row() - row.prop(attribute, "string_value", text=attribute["name"]) - else: - row = self.layout.row(align=True) - row.prop(attribute, "enum_value", text=attribute["name"]) - if attribute.enum_value == "IfcBoolean": - row.prop(attribute, "bool_value", text="") - else: - row.prop(attribute, "float_value", text="") - if attribute.is_optional: - row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="") - - def draw_read_only_ui(self, context): - for key, value in self.data.items(): - if key == "id" or key == "type" or value == None: - continue - row = self.layout.row(align=True) - row.label(text=key) - if isinstance(value, bool): - row.label(text="", icon="CHECKBOX_HLT" if value else "CHECKBOX_DEHLT") - else: - row.label(text=str(value)) + applied_condition_id = Data.connections[self.oprops.ifc_definition_id]["AppliedCondition"] + draw_boundary_condition_ui(self.layout, applied_condition_id, self.oprops.ifc_definition_id, self.props) class BIM_PT_connected_structural_members(Panel): @@ -97,25 +109,34 @@ class BIM_PT_connected_structural_members(Panel): def draw(self, context): self.oprops = context.active_object.BIMObjectProperties self.props = context.active_object.BIMStructuralProperties - if self.oprops.ifc_definition_id not in Data.connected_structural_members: + if self.oprops.ifc_definition_id not in Data.connections: Data.load(IfcStore.get_file(), self.oprops.ifc_definition_id) - self.data = Data.connected_structural_members[self.oprops.ifc_definition_id] + rel_ids = Data.connections[self.oprops.ifc_definition_id]["ConnectsStructuralMembers"] row = self.layout.row(align=True) - row.label(text=f"{len(self.data.keys())} Connected Structural Members Found", icon="CON_TRACKTO") + row.label(text=f"{len(rel_ids)} Connected Structural Members", icon="CON_TRACKTO") row.operator("bim.add_structural_member_connection", text="", icon="ADD") - for _,rel in self.data.items(): + for rel_id in rel_ids: + rel = Data.connects_structural_members[rel_id] row = self.layout.row(align=True) - if self.props.is_editing_connection_conditions: - row.label(text=str(rel["RelatingStructuralMember"])) - # row.operator("bim.edit_structural_member_connection", text="", icon="CHECKMARK") - # row.operator("bim.disable_editing_structural_member_connection", text="", icon="X") - else: - row.label(text=f"To Member #{rel['RelatingStructuralMember']}") - row.operator("bim.enable_editing_structural_connection_condition", text="", icon="GREASEPENCIL") + row.label(text=f"To Member #{rel['RelatingStructuralMember']}") + if self.props.active_connects_structural_member and self.props.active_connects_structural_member == rel_id: + row.operator("bim.disable_editing_structural_connection_condition", text="", icon="CHECKMARK") + row.operator("bim.disable_editing_structural_connection_condition", text="", icon="X") + self.draw_editable_ui(context, self.layout, rel) + elif self.props.active_connects_structural_member: row.operator("bim.remove_structural_connection_condition", text="", icon="X") + else: + op = row.operator("bim.enable_editing_structural_connection_condition", text="", icon="GREASEPENCIL") + op.connects_structural_member = rel_id + row.operator("bim.remove_structural_connection_condition", text="", icon="X") + + def draw_editable_ui(self, context, layout, data): + box = layout.box() + row = box.row(align=True) + draw_boundary_condition_ui(box, data["AppliedCondition"], data["id"], self.props) class BIM_PT_structural_analysis_models(Panel): diff --git a/src/ifcopenshell-python/ifcopenshell/api/structural/add_structural_boundary_condition.py b/src/ifcopenshell-python/ifcopenshell/api/structural/add_structural_boundary_condition.py index 35449e7cfc..9cb49ec47a 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/structural/add_structural_boundary_condition.py +++ b/src/ifcopenshell-python/ifcopenshell/api/structural/add_structural_boundary_condition.py @@ -6,10 +6,16 @@ class Usecase: self.settings[key] = value def execute(self): - if self.settings["connection"].is_a("IfcStructuralPointConnection"): + if self.settings["connection"].is_a("IfcRelConnectsStructuralMember"): + related_connection = self.settings["connection"].RelatedStructuralConnection + else: + related_connection = self.settings["connection"] + + if related_connection.is_a("IfcStructuralPointConnection"): boundary_class = "IfcBoundaryNodeCondition" - elif self.settings["connection"].is_a("IfcStructuralCurveConnection"): + elif related_connection.is_a("IfcStructuralCurveConnection"): boundary_class = "IfcBoundaryEdgeCondition" - elif self.settings["connection"].is_a("IfcStructuralSurfaceConnection"): + elif related_connection.is_a("IfcStructuralSurfaceConnection"): boundary_class = "IfcBoundaryFaceCondition" + self.settings["connection"].AppliedCondition = self.file.create_entity(boundary_class) diff --git a/src/ifcopenshell-python/ifcopenshell/api/structural/data.py b/src/ifcopenshell-python/ifcopenshell/api/structural/data.py index 2ef7ce678d..75537c5815 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/structural/data.py +++ b/src/ifcopenshell-python/ifcopenshell/api/structural/data.py @@ -3,8 +3,9 @@ class Data: products = {} structural_analysis_models = {} boundary_conditions = {} - connected_structural_members = {} - connection_conditions = {} + members = {} + connections = {} + connects_structural_members = {} @classmethod def purge(cls): @@ -12,8 +13,9 @@ class Data: cls.products = {} cls.structural_analysis_models = {} cls.boundary_conditions = {} - cls.connected_structural_members = {} - cls.connection_conditions = {} + cls.members = {} + cls.connections = {} + cls.connects_structural_members = {} @classmethod def load(cls, file, product_id=None): @@ -55,48 +57,45 @@ class Data: @classmethod def load_structural_connection(cls, product_id): + cls.connections = {} cls.boundary_conditions = {} - cls.connected_structural_members = {} - # cls.connection_conditions = {} + cls.connects_structural_members = {} connection = cls._file.by_id(product_id) + connection_data = {"AppliedCondition": None, "ConnectsStructuralMembers": []} + if connection.AppliedCondition: - data = connection.AppliedCondition.get_info() - for key, value in data.items(): - if not value or key in ["Name", "type", "id"]: - continue - data[key] = value.wrappedValue - else: - data = {} - cls.boundary_conditions[connection.id()] = data + cls.load_boundary_condition(connection.AppliedCondition) + connection_data["AppliedCondition"] = connection.AppliedCondition.id() - cls.connected_structural_members[connection.id()] = {} for rel in connection.ConnectsStructuralMembers or []: - data = rel.get_info() - del data["OwnerHistory"] - data["RelatingStructuralMember"] = rel.RelatingStructuralMember.id() - data["RelatedStructuralConnection"] = rel.RelatedStructuralConnection.id() - del data["ConditionCoordinateSystem"] # TODO: consider orientation - del data["AppliedCondition"] # delete and parse below - if rel.is_a("IfcRelConnectsWithEccentricity"): - data["ConnectionConstraint"] = rel.ConnectionConstraint # TODO + cls.load_connects_structural_member(rel) + connection_data["ConnectsStructuralMembers"].append(rel.id()) - cls.connected_structural_members[connection.id()][rel.id()] = data + cls.connections[connection.id()] = connection_data - if rel.AppliedCondition: - data = rel.AppliedCondition.get_info() - for key, value in data.items(): - if not value or key in ["Name", "type", "id"]: - continue - data[key] = value.wrappedValue - else: - data = {} - cls.connection_conditions[rel.id()] = data - - print(cls.connected_structural_members) - print(cls.connection_conditions) + @classmethod + def load_boundary_condition(cls, boundary_condition): + data = boundary_condition.get_info() + for key, value in data.items(): + if not value or key in ["Name", "type", "id"]: + continue + data[key] = value.wrappedValue + cls.boundary_conditions[boundary_condition.id()] = data - # cls.connected_structural_members[connection.id()] = [ - # rel.id() - # for rel in connection.ConnectsStructuralMembers or [] - # ] \ No newline at end of file + @classmethod + def load_connects_structural_member(cls, rel): + rel_data = rel.get_info() + del rel_data["OwnerHistory"] + rel_data["RelatingStructuralMember"] = rel.RelatingStructuralMember.id() + rel_data["RelatedStructuralConnection"] = rel.RelatedStructuralConnection.id() + del rel_data["ConditionCoordinateSystem"] # TODO: consider orientation + + if rel.is_a("IfcRelConnectsWithEccentricity"): + rel_data["ConnectionConstraint"] = rel.ConnectionConstraint # TODO + + if rel.AppliedCondition: + cls.load_boundary_condition(rel.AppliedCondition) + rel_data["AppliedCondition"] = rel.AppliedCondition.id() + + cls.connects_structural_members[rel.id()] = rel_data