diff --git a/src/blenderbim/blenderbim/bim/module/structural/__init__.py b/src/blenderbim/blenderbim/bim/module/structural/__init__.py index bac453ce5c..20e5e28500 100644 --- a/src/blenderbim/blenderbim/bim/module/structural/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/structural/__init__.py @@ -29,10 +29,15 @@ classes = ( operator.AddStructuralLoadCase, operator.EditStructuralLoadCase, operator.RemoveStructuralLoadCase, + operator.AddStructuralLoadGroup, + operator.RemoveStructuralLoadGroup, + operator.AddStructuralActivity, operator.EnableEditingStructuralLoadCase, - operator.EnableEditingStructuralLoadCaseActivity, + operator.EnableEditingStructuralLoadCaseGroups, operator.DisableEditingStructuralLoadCase, + operator.EnableEditingStructuralLoadGroupActivities, prop.StructuralAnalysisModel, + prop.StructuralActivity, prop.BIMStructuralProperties, prop.BIMObjectStructuralProperties, ui.BIM_PT_structural_analysis_models, @@ -41,6 +46,7 @@ classes = ( ui.BIM_PT_structural_member, ui.BIM_PT_structural_connection, ui.BIM_UL_structural_analysis_models, + ui.BIM_UL_structural_activities, ui.BIM_PT_structural_load_cases, ) diff --git a/src/blenderbim/blenderbim/bim/module/structural/operator.py b/src/blenderbim/blenderbim/bim/module/structural/operator.py index 5eba1e2301..845f023b1f 100644 --- a/src/blenderbim/blenderbim/bim/module/structural/operator.py +++ b/src/blenderbim/blenderbim/bim/module/structural/operator.py @@ -627,13 +627,106 @@ class DisableEditingStructuralLoadCase(bpy.types.Operator): return {"FINISHED"} -class EnableEditingStructuralLoadCaseActivity(bpy.types.Operator): - bl_idname = "bim.enable_editing_structural_load_case_activity" - bl_label = "Enable Editing Structural Load Case Activity" +class EnableEditingStructuralLoadCaseGroups(bpy.types.Operator): + bl_idname = "bim.enable_editing_structural_load_case_groups" + bl_label = "Enable Editing Structural Load Case Groups" load_case: bpy.props.IntProperty() def execute(self, context): self.props = context.scene.BIMStructuralProperties self.props.active_load_case_id = self.load_case - self.props.load_case_editing_type = "ACTIVITY" + self.props.load_case_editing_type = "GROUPS" + return {"FINISHED"} + + +class AddStructuralLoadGroup(bpy.types.Operator): + bl_idname = "bim.add_structural_load_group" + bl_label = "Add Structural Load Group" + load_case: bpy.props.IntProperty() + + def execute(self, context): + self.file = IfcStore.get_file() + load_group = ifcopenshell.api.run("structural.add_structural_load_group", self.file) + ifcopenshell.api.run("group.assign_group", self.file, product=load_group, group=self.file.by_id(self.load_case)) + Data.load(IfcStore.get_file()) + return {"FINISHED"} + + +class RemoveStructuralLoadGroup(bpy.types.Operator): + bl_idname = "bim.remove_structural_load_group" + bl_label = "Remove Structural Load Group" + load_group: bpy.props.IntProperty() + + def execute(self, context): + self.file = IfcStore.get_file() + ifcopenshell.api.run( + "structural.remove_structural_load_group", self.file, load_group=self.file.by_id(self.load_group) + ) + Data.load(self.file) + return {"FINISHED"} + + +class EnableEditingStructuralLoadGroupActivities(bpy.types.Operator): + bl_idname = "bim.enable_editing_structural_load_group_activities" + bl_label = "Enable Editing Structural Load Group Activities" + load_group: bpy.props.IntProperty() + + def execute(self, context): + self.file = IfcStore.get_file() + self.props = context.scene.BIMStructuralProperties + self.props.active_load_group_id = self.load_group + self.props.load_group_editing_type = "ACTIVITY" + self.load_structural_activities() + return {"FINISHED"} + + def load_structural_activities(self): + while len(self.props.load_group_activities) > 0: + self.props.load_group_activities.remove(0) + for activity_id in Data.load_groups[self.load_group]["IsGroupedBy"]: + activity = Data.structural_activities[activity_id] + new = self.props.load_group_activities.add() + new.ifc_definition_id = activity_id + new.name = self.file.by_id(activity["AssignedToStructuralItem"]).Name or "Unnamed" + new.applied_load_class = self.file.by_id(activity["AppliedLoad"]).is_a() + + +class AddStructuralActivity(bpy.types.Operator): + bl_idname = "bim.add_structural_activity" + bl_label = "Add Structural Activity" + load_group: bpy.props.IntProperty() + + def execute(self, context): + self.props = context.scene.BIMStructuralProperties + self.file = IfcStore.get_file() + for obj in context.selected_objects: + if not obj.BIMObjectProperties.ifc_definition_id: + continue + element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id) + applied_load_class = self.props.applicable_structural_activity_types + + if element.is_a("IfcStructuralPointConnection"): + if applied_load_class not in ["IfcStructuralLoadSingleForce", "IfcStructuralLoadSingleDisplacement"]: + continue + ifc_class = "IfcStructuralPointAction" + elif element.is_a("IfcStructuralCurveMember"): + if applied_load_class != "IfcStructuralLoadLinearForce": + continue + ifc_class = "IfcStructuralLinearAction" + elif element.is_a("IfcStructuralSurfaceMember"): + if applied_load_class != "IfcStructuralLoadPlanarForce": + continue + ifc_class = "IfcStructuralPlanarAction" + + activity = ifcopenshell.api.run( + "structural.add_structural_activity", + self.file, + ifc_class=ifc_class, + applied_load=None, # TODO + structural_member=element + ) + ifcopenshell.api.run( + "group.assign_group", self.file, product=activity, group=self.file.by_id(self.load_group) + ) + Data.load(IfcStore.get_file()) + bpy.ops.bim.enable_editing_structural_load_group_activities(load_group=self.load_group) return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/structural/prop.py b/src/blenderbim/blenderbim/bim/module/structural/prop.py index e969bb9717..c38feac30c 100644 --- a/src/blenderbim/blenderbim/bim/module/structural/prop.py +++ b/src/blenderbim/blenderbim/bim/module/structural/prop.py @@ -1,4 +1,5 @@ import bpy +from blenderbim.bim.ifc import IfcStore from math import radians from blenderbim.bim.prop import StrProperty, Attribute from bpy.types import PropertyGroup @@ -14,6 +15,28 @@ from bpy.props import ( ) +def getApplicableStructuralActivityTypes(self, context): + ifc_file = IfcStore.get_file() + element_classes = set( + [ + ifc_file.by_id(o.BIMObjectProperties.ifc_definition_id).is_a() + for o in bpy.context.selected_objects + if o.BIMObjectProperties.ifc_definition_id + ] + ) + types = [("IfcStructuralLoadTemperature", "IfcStructuralLoadTemperature", "")] + if "IfcStructuralPointConnection" in element_classes: + types.extend([ + ("IfcStructuralLoadSingleForce", "IfcStructuralLoadSingleForce", ""), + ("IfcStructuralLoadSingleDisplacement", "IfcStructuralLoadSingleDisplacement", "") + ]) + if "IfcStructuralCurveMember" in element_classes: + types.append(("IfcStructuralLoadLinearForce", "IfcStructuralLoadLinearForce", "")) + if "IfcStructuralSurfaceMember" in element_classes: + types.append(("IfcStructuralLoadPlanarForce", "IfcStructuralLoadPlanarForce", "")) + return types + + def updateAxisAngle(self, context): if not self.axis_empty: return @@ -26,6 +49,7 @@ def updateAxisAngle(self, context): empty.rotation_mode = "XYZ" empty.rotation_euler[0] = radians(self.axis_angle) + def updateConnectionCS(self, context): if not self.ccs_empty: return @@ -43,6 +67,12 @@ class StructuralAnalysisModel(PropertyGroup): ifc_definition_id: IntProperty(name="IFC Definition ID") +class StructuralActivity(PropertyGroup): + name: StringProperty(name="Name") + applied_load_class: StringProperty(name="Applied Load Class") + ifc_definition_id: IntProperty(name="IFC Definition ID") + + class BIMStructuralProperties(PropertyGroup): structural_analysis_model_attributes: CollectionProperty( name="Structural Analysis Model Attributes", type=Attribute @@ -54,6 +84,14 @@ class BIMStructuralProperties(PropertyGroup): load_case_editing_type: StringProperty(name="Load Case Editing Type") load_case_attributes: CollectionProperty(name="Load Case Attributes", type=Attribute) active_load_case_id: IntProperty(name="Active Load Case Id") + load_group_editing_type: StringProperty(name="Load Group Editing Type") + # load_group_attributes: CollectionProperty(name="Load Group Attributes", type=Attribute) + active_load_group_id: IntProperty(name="Active Load Group Id") + applicable_structural_activity_types: EnumProperty( + items=getApplicableStructuralActivityTypes, name="Applicable Structural Activity Types" + ) + load_group_activities: CollectionProperty(name="Load Group Activities", type=StructuralActivity) + active_load_group_activity_index: IntProperty(name="Active Load Group Activity Index") class BIMObjectStructuralProperties(PropertyGroup): diff --git a/src/blenderbim/blenderbim/bim/module/structural/ui.py b/src/blenderbim/blenderbim/bim/module/structural/ui.py index 157726e62f..0aa44036f8 100644 --- a/src/blenderbim/blenderbim/bim/module/structural/ui.py +++ b/src/blenderbim/blenderbim/bim/module/structural/ui.py @@ -1,3 +1,4 @@ +import bpy from bpy.types import Panel, UIList from blenderbim.bim.ifc import IfcStore from ifcopenshell.api.structural.data import Data @@ -344,13 +345,16 @@ class BIM_PT_structural_load_cases(Panel): row.label(text=load_case["Name"] or "Unnamed", icon="CON_CLAMPTO") if self.props.active_load_case_id and self.props.active_load_case_id == load_case_id: - row.operator("bim.edit_structural_load_case", text="", icon="CHECKMARK") + if self.props.load_case_editing_type == "ATTRIBUTES": + row.operator("bim.edit_structural_load_case", text="", icon="CHECKMARK") + elif self.props.load_case_editing_type == "GROUPS": + row.operator("bim.add_structural_load_group", text="", icon="ADD").load_case = load_case_id row.operator("bim.disable_editing_structural_load_case", text="", icon="CANCEL") elif self.props.active_load_case_id: row.operator("bim.remove_structural_load_case", text="", icon="X").load_case = load_case_id else: row.operator( - "bim.enable_editing_structural_load_case_activity", text="", icon="GHOST_ENABLED" + "bim.enable_editing_structural_load_case_groups", text="", icon="GHOST_ENABLED" ).load_case = load_case_id row.operator( "bim.enable_editing_structural_load_case", text="", icon="GREASEPENCIL" @@ -360,8 +364,8 @@ class BIM_PT_structural_load_cases(Panel): if self.props.active_load_case_id == load_case_id: if self.props.load_case_editing_type == "ATTRIBUTES": self.draw_editable_load_case_ui() - elif self.props.load_case_editing_type == "ACTIVITY": - self.draw_editable_load_case_activity_ui() + elif self.props.load_case_editing_type == "GROUPS": + self.draw_editable_load_case_group_ui(load_case) def draw_editable_load_case_ui(self): for attribute in self.props.load_case_attributes: @@ -375,6 +379,42 @@ class BIM_PT_structural_load_cases(Panel): if attribute.is_optional: row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="") - def draw_editable_load_case_activity_ui(self): - row = self.layout.row(align=True) - row.label(text="Activities!") + def draw_editable_load_case_group_ui(self, load_case): + box = self.layout.box() + if not len(load_case["IsGroupedBy"]): + row = box.row(align=True) + row.label(text="No Load Groups Found") + for load_group_id in load_case["IsGroupedBy"]: + load_group = Data.load_groups[load_group_id] + row = box.row(align=True) + row.label(text=load_group["Name"] or "Unnamed", icon="GHOST_ENABLED") + op = row.operator("bim.enable_editing_structural_load_group_activities", text="", icon="GHOST_ENABLED") + op.load_group = load_group_id + row.operator("bim.remove_structural_load_group", text="", icon="X").load_group = load_group_id + + if self.props.active_load_group_id == load_group_id: + if self.props.load_group_editing_type == "ACTIVITY": + self.draw_editable_load_group_activities_ui(box, load_group) + + def draw_editable_load_group_activities_ui(self, layout, load_group): + row = layout.row(align=True) + row.prop(self.props, "applicable_structural_activity_types", text="") + op = row.operator("bim.add_structural_activity", text="", icon="ADD") + op.load_group = load_group["id"] + layout.template_list( + "BIM_UL_structural_activities", + "", + self.props, + "load_group_activities", + self.props, + "active_load_group_activity_index", + ) + + + +class BIM_UL_structural_activities(UIList): + def draw_item(self, context, layout, data, item, icon, active_data, active_propname): + if item: + row = layout.row(align=True) + row.label(text=item.name) + row.label(text=item.applied_load_class) diff --git a/src/ifcopenshell-python/ifcopenshell/api/structural/add_structural_activity.py b/src/ifcopenshell-python/ifcopenshell/api/structural/add_structural_activity.py new file mode 100644 index 0000000000..7fe08a17a1 --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/structural/add_structural_activity.py @@ -0,0 +1,31 @@ +import ifcopenshell.api + + +class Usecase: + def __init__(self, file, **settings): + self.file = file + self.settings = { + "ifc_class": "IfcStructuralPlanarAction", + "predefined_type": "CONST", + "global_or_local": "GLOBAL_COORDS", + "applied_load": None, + "structural_member": None, + } + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + activity = ifcopenshell.api.run( + "root.create_entity", + self.file, + ifc_class=self.settings["ifc_class"], + predefined_type=self.settings["predefined_type"], + ) + # TODO + # activity.AppliedLoad = self.settings["applied_load"] + activity.GlobalOrLocal = self.settings["global_or_local"] + + rel = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcRelConnectsStructuralActivity") + rel.RelatingElement = self.settings["structural_member"] + rel.RelatedStructuralActivity = activity + return activity diff --git a/src/ifcopenshell-python/ifcopenshell/api/structural/add_structural_load_group.py b/src/ifcopenshell-python/ifcopenshell/api/structural/add_structural_load_group.py new file mode 100644 index 0000000000..32682cfd17 --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/structural/add_structural_load_group.py @@ -0,0 +1,26 @@ +import ifcopenshell.api + + +class Usecase: + def __init__(self, file, **settings): + self.file = file + self.settings = { + "name": "Unnamed", + "predefined_type": "LOAD_GROUP", + "action_type": "NOTDEFINED", + "action_source": "NOTDEFINED", + } + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + load_group = ifcopenshell.api.run( + "root.create_entity", + self.file, + ifc_class="IfcStructuralLoadGroup", + predefined_type=self.settings["predefined_type"], + name=self.settings["name"], + ) + load_group.ActionType = self.settings["action_type"] + load_group.ActionSource = self.settings["action_source"] + return load_group diff --git a/src/ifcopenshell-python/ifcopenshell/api/structural/data.py b/src/ifcopenshell-python/ifcopenshell/api/structural/data.py index bb5c54857e..68b4344a6f 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/structural/data.py +++ b/src/ifcopenshell-python/ifcopenshell/api/structural/data.py @@ -48,6 +48,7 @@ class Data: cls.load_structural_load_cases() cls.load_structural_load_case_combinations() cls.load_structural_load_groups() + cls.load_structural_activities() cls.is_loaded = True @classmethod @@ -83,18 +84,12 @@ class Data: cls.load_cases = {} for case in cls._file.by_type("IfcStructuralLoadCase"): - # if case.IsGroupedBy: - # for rel in case.IsGroupedBy: - # for product in rel.RelatedObjects: - # cls.products.setdefault(product.id(), []).append(case.id()) data = case.get_info() del data["OwnerHistory"] - is_grouped_by = [] - for load_group in case.IsGroupedBy or []: - is_grouped_by.append(load_group.id()) + for rel in case.IsGroupedBy or []: + is_grouped_by.extend([o.id() for o in rel.RelatedObjects]) data["IsGroupedBy"] = is_grouped_by - cls.load_cases[case.id()] = data @classmethod @@ -121,20 +116,30 @@ class Data: for case in cls._file.by_type("IfcStructuralLoadGroup", include_subtypes=False): if case.PredefinedType == "LOAD_COMBINATION": continue - # if case.IsGroupedBy: - # for rel in case.IsGroupedBy: - # for product in rel.RelatedObjects: - # cls.products.setdefault(product.id(), []).append(case.id()) data = case.get_info() del data["OwnerHistory"] is_grouped_by = [] - for load_group in case.IsGroupedBy or []: - is_grouped_by.append(load_group.id()) + for rel in case.IsGroupedBy or []: + is_grouped_by.extend([o.id() for o in rel.RelatedObjects]) data["IsGroupedBy"] = is_grouped_by cls.load_groups[case.id()] = data + @classmethod + def load_structural_activities(cls): + cls.structural_activities = {} + for activity in cls._file.by_type("IfcStructuralActivity"): + data = activity.get_info() + del data["OwnerHistory"] + del data["ObjectPlacement"] + del data["Representation"] + data["AppliedLoad"] = data["AppliedLoad"].id() if data["AppliedLoad"] else None + data["AssignedToStructuralItem"] = None + if activity.AssignedToStructuralItem: + data["AssignedToStructuralItem"] = activity.AssignedToStructuralItem[0].RelatingElement.id() + cls.structural_activities[activity.id()] = data + @classmethod def load_structural_connection(cls, product_id): # cls.connections = {} diff --git a/src/ifcopenshell-python/ifcopenshell/api/structural/remove_structural_load_group.py b/src/ifcopenshell-python/ifcopenshell/api/structural/remove_structural_load_group.py new file mode 100644 index 0000000000..ce145fb650 --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/structural/remove_structural_load_group.py @@ -0,0 +1,16 @@ +import ifcopenshell.api + + +class Usecase: + def __init__(self, file, **settings): + self.file = file + self.settings = {"load_group": None} + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + # TODO: do a deep purge + for inverse in self.file.get_inverse(self.settings["load_group"]): + if inverse.is_a("IfcRelAssignsToGroup") and len(inverse.RelatedObjects) == 1: + self.file.remove(inverse) + self.file.remove(self.settings["load_group"])