diff --git a/src/blenderbim/blenderbim/bim/helper.py b/src/blenderbim/blenderbim/bim/helper.py index 3b255f76e7..46eaf2040b 100644 --- a/src/blenderbim/blenderbim/bim/helper.py +++ b/src/blenderbim/blenderbim/bim/helper.py @@ -8,6 +8,23 @@ from mathutils import Vector from blenderbim.bim.ifc import IfcStore +def draw_attributes(props, layout): + for attribute in props: + row = layout.row(align=True) + if attribute.data_type == "string": + row.prop(attribute, "string_value", text=attribute.name) + elif attribute.data_type == "boolean": + row.prop(attribute, "bool_value", text=attribute.name) + elif attribute.data_type == "integer": + row.prop(attribute, "int_value", text=attribute.name) + elif attribute.data_type == "float": + row.prop(attribute, "float_value", text=attribute.name) + elif attribute.data_type == "enum": + row.prop(attribute, "enum_value", text=attribute.name) + if attribute.is_optional: + row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="") + + def import_attributes(ifc_class, props, data, callback=None): for attribute in IfcStore.get_schema().declaration_by_name(ifc_class).all_attributes(): data_type = ifcopenshell.util.attribute.get_primitive_type(attribute) diff --git a/src/blenderbim/blenderbim/bim/module/structural/__init__.py b/src/blenderbim/blenderbim/bim/module/structural/__init__.py index 769a9497be..0995cd9a5a 100644 --- a/src/blenderbim/blenderbim/bim/module/structural/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/structural/__init__.py @@ -36,8 +36,16 @@ classes = ( operator.EnableEditingStructuralLoadCaseGroups, operator.DisableEditingStructuralLoadCase, operator.EnableEditingStructuralLoadGroupActivities, + operator.LoadStructuralLoads, + operator.DisableStructuralLoadEditingUI, + operator.AddStructuralLoad, + operator.EnableEditingStructuralLoad, + operator.DisableEditingStructuralLoad, + operator.RemoveStructuralLoad, + operator.EditStructuralLoad, prop.StructuralAnalysisModel, prop.StructuralActivity, + prop.StructuralLoad, prop.BIMStructuralProperties, prop.BIMObjectStructuralProperties, ui.BIM_PT_structural_analysis_models, @@ -48,6 +56,8 @@ classes = ( ui.BIM_UL_structural_analysis_models, ui.BIM_UL_structural_activities, ui.BIM_PT_structural_load_cases, + ui.BIM_UL_structural_loads, + ui.BIM_PT_structural_loads, ) diff --git a/src/blenderbim/blenderbim/bim/module/structural/operator.py b/src/blenderbim/blenderbim/bim/module/structural/operator.py index 420968768b..bd7b57988f 100644 --- a/src/blenderbim/blenderbim/bim/module/structural/operator.py +++ b/src/blenderbim/blenderbim/bim/module/structural/operator.py @@ -6,6 +6,7 @@ import blenderbim.bim.helper from math import degrees from mathutils import Vector, Matrix from blenderbim.bim.ifc import IfcStore +from blenderbim.bim.module.structural.prop import purge from ifcopenshell.api.structural.data import Data from ifcopenshell.api.context.data import Data as ContextData @@ -459,9 +460,17 @@ class EnableEditingStructuralConnectionCS(bpy.types.Operator): empty.location = location if item.ConditionCoordinateSystem is not None: - z_axis = Vector(item.ConditionCoordinateSystem.Axis.DirectionRatios).normalized() @ obj.matrix_world if item.ConditionCoordinateSystem.Axis else None - x_axis = Vector(item.ConditionCoordinateSystem.RefDirection.DirectionRatios).normalized() @ obj.matrix_world if item.ConditionCoordinateSystem.RefDirection else None - + z_axis = ( + Vector(item.ConditionCoordinateSystem.Axis.DirectionRatios).normalized() @ obj.matrix_world + if item.ConditionCoordinateSystem.Axis + else None + ) + x_axis = ( + Vector(item.ConditionCoordinateSystem.RefDirection.DirectionRatios).normalized() @ obj.matrix_world + if item.ConditionCoordinateSystem.RefDirection + else None + ) + if z_axis: y_axis = (z_axis.cross(x_axis)).normalized() x_axis = (y_axis.cross(z_axis)).normalized() @@ -473,7 +482,6 @@ class EnableEditingStructuralConnectionCS(bpy.types.Operator): (0, 0, 0, 1), ) ) - props.ccs_x_angle = degrees(empty.rotation_euler[0]) props.ccs_y_angle = degrees(empty.rotation_euler[1]) @@ -684,6 +692,7 @@ class EnableEditingStructuralLoadGroupActivities(bpy.types.Operator): self.props.active_load_group_id = self.load_group self.props.load_group_editing_type = "ACTIVITY" self.load_structural_activities() + purge() return {"FINISHED"} def load_structural_activities(self): @@ -709,27 +718,35 @@ class AddStructuralActivity(bpy.types.Operator): 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 + applied_load_class = self.props.applicable_structural_load_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" + allowed_load_classes = { + "IfcStructuralPointConnection": [ + "IfcStructuralLoadTemperature", + "IfcStructuralLoadSingleForce", + "IfcStructuralLoadSingleDisplacement", + ], + "IfcStructuralCurveMember": ["IfcStructuralLoadTemperature", "IfcStructuralLoadLinearForce"], + "IfcStructuralSurfaceMember": ["IfcStructuralLoadTemperature", "IfcStructuralLoadPlanarForce"], + } + + applicable_activity_class = { + "IfcStructuralPointConnection": "IfcStructuralPointAction", + "IfcStructuralCurveMember": "IfcStructuralLinearAction", + "IfcStructuralSurfaceMember": "IfcStructuralPlanarAction", + } + + if applied_load_class not in allowed_load_classes[element.is_a()]: + continue + + ifc_class = applicable_activity_class[element.is_a()] activity = ifcopenshell.api.run( "structural.add_structural_activity", self.file, ifc_class=ifc_class, - applied_load=None, # TODO - structural_member=element + applied_load=self.file.by_id(int(self.props.applicable_structural_loads)), + structural_member=element, ) ifcopenshell.api.run( "group.assign_group", self.file, product=activity, group=self.file.by_id(self.load_group) @@ -737,3 +754,108 @@ class AddStructuralActivity(bpy.types.Operator): Data.load(IfcStore.get_file()) bpy.ops.bim.enable_editing_structural_load_group_activities(load_group=self.load_group) return {"FINISHED"} + + +class LoadStructuralLoads(bpy.types.Operator): + bl_idname = "bim.load_structural_loads" + bl_label = "Load Structural Loads" + + def execute(self, context): + props = context.scene.BIMStructuralProperties + while len(props.structural_loads) > 0: + props.structural_loads.remove(0) + for ifc_definition_id, structural_loads in Data.structural_loads.items(): + new = props.structural_loads.add() + new.ifc_definition_id = ifc_definition_id + new.name = structural_loads["Name"] or "Unnamed" + props.is_editing_loads = True + bpy.ops.bim.disable_editing_structural_load() + return {"FINISHED"} + + +class DisableStructuralLoadEditingUI(bpy.types.Operator): + bl_idname = "bim.disable_structural_load_editing_ui" + bl_label = "Disable Structural Load Editing UI" + + def execute(self, context): + context.scene.BIMStructuralProperties.is_editing_loads = False + return {"FINISHED"} + + +class AddStructuralLoad(bpy.types.Operator): + bl_idname = "bim.add_structural_load" + bl_label = "Add Structural Load" + ifc_class: bpy.props.StringProperty() + + def execute(self, context): + result = ifcopenshell.api.run( + "structural.add_structural_load", IfcStore.get_file(), name="New Load", ifc_class=self.ifc_class + ) + Data.load(IfcStore.get_file()) + bpy.ops.bim.load_structural_loads() + bpy.ops.bim.enable_editing_structural_load(structural_load=result.id()) + return {"FINISHED"} + + +class EnableEditingStructuralLoad(bpy.types.Operator): + bl_idname = "bim.enable_editing_structural_load" + bl_label = "Enable Editing Structural Load" + structural_load: bpy.props.IntProperty() + + def execute(self, context): + props = context.scene.BIMStructuralProperties + while len(props.structural_load_attributes) > 0: + props.structural_load_attributes.remove(0) + + data = Data.structural_loads[self.structural_load] + blenderbim.bim.helper.import_attributes(data["type"], props.structural_load_attributes, data) + props.active_structural_load_id = self.structural_load + return {"FINISHED"} + + +class DisableEditingStructuralLoad(bpy.types.Operator): + bl_idname = "bim.disable_editing_structural_load" + bl_label = "Disable Editing Structural Load" + + def execute(self, context): + context.scene.BIMStructuralProperties.active_structural_load_id = 0 + return {"FINISHED"} + + +class RemoveStructuralLoad(bpy.types.Operator): + bl_idname = "bim.remove_structural_load" + bl_label = "Remove Structural Load" + structural_load: bpy.props.IntProperty() + + def execute(self, context): + props = context.scene.BIMStructuralProperties + self.file = IfcStore.get_file() + ifcopenshell.api.run( + "structural.remove_structural_load", + self.file, + **{"structural_load": self.file.by_id(self.structural_load)}, + ) + Data.load(IfcStore.get_file()) + bpy.ops.bim.load_structural_loads() + return {"FINISHED"} + + +class EditStructuralLoad(bpy.types.Operator): + bl_idname = "bim.edit_structural_load" + bl_label = "Edit Structural Load" + + def execute(self, context): + props = context.scene.BIMStructuralProperties + attributes = blenderbim.bim.helper.export_attributes(props.structural_load_attributes) + self.file = IfcStore.get_file() + ifcopenshell.api.run( + "structural.edit_structural_load", + self.file, + **{ + "structural_load": self.file.by_id(props.active_structural_load_id), + "attributes": attributes, + }, + ) + Data.load(IfcStore.get_file()) + bpy.ops.bim.load_structural_loads() + return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/structural/prop.py b/src/blenderbim/blenderbim/bim/module/structural/prop.py index c38feac30c..e7faac4bba 100644 --- a/src/blenderbim/blenderbim/bim/module/structural/prop.py +++ b/src/blenderbim/blenderbim/bim/module/structural/prop.py @@ -2,6 +2,7 @@ import bpy from blenderbim.bim.ifc import IfcStore from math import radians from blenderbim.bim.prop import StrProperty, Attribute +from ifcopenshell.api.structural.data import Data from bpy.types import PropertyGroup from bpy.props import ( PointerProperty, @@ -14,8 +15,18 @@ from bpy.props import ( CollectionProperty, ) +structuralloadtypes_enum = [] +applicablestructuralloads_enum = [] -def getApplicableStructuralActivityTypes(self, context): + +def purge(): + global structuralloadtypes_enum + global applicablestructuralloads_enum + structuralloadtypes_enum = [] + applicablestructuralloads_enum = [] + + +def getApplicableStructuralLoadTypes(self, context): ifc_file = IfcStore.get_file() element_classes = set( [ @@ -37,6 +48,31 @@ def getApplicableStructuralActivityTypes(self, context): return types +def updateApplicableStructuralLoadTypes(self, context): + global applicablestructuralloads_enum + applicablestructuralloads_enum.clear() + + +def getApplicableStructuralLoads(self, context): + global applicablestructuralloads_enum + file = IfcStore.get_file() + if len(applicablestructuralloads_enum) < 1 and file: + for ifc_definition_id, load in Data.structural_loads.items(): + if not load["Name"] or load["type"] != self.applicable_structural_load_types: + continue + applicablestructuralloads_enum.append((str(ifc_definition_id), load["Name"], "")) + return applicablestructuralloads_enum + + +def getStructuralLoadTypes(self, context): + global structuralloadtypes_enum + file = IfcStore.get_file() + if len(structuralloadtypes_enum) < 1 and file: + declaration = IfcStore.get_schema().declaration_by_name("IfcStructuralLoadStatic") + structuralloadtypes_enum.extend([(d.name(), d.name(), "") for d in declaration.subtypes()]) + return structuralloadtypes_enum + + def updateAxisAngle(self, context): if not self.axis_empty: return @@ -73,6 +109,11 @@ class StructuralActivity(PropertyGroup): ifc_definition_id: IntProperty(name="IFC Definition ID") +class StructuralLoad(PropertyGroup): + name: StringProperty(name="Name") + ifc_definition_id: IntProperty(name="IFC Definition ID") + + class BIMStructuralProperties(PropertyGroup): structural_analysis_model_attributes: CollectionProperty( name="Structural Analysis Model Attributes", type=Attribute @@ -87,11 +128,21 @@ class BIMStructuralProperties(PropertyGroup): 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" + applicable_structural_load_types: EnumProperty( + items=getApplicableStructuralLoadTypes, name="Applicable Structural Load Types", + update=updateApplicableStructuralLoadTypes + ) + applicable_structural_loads: EnumProperty( + items=getApplicableStructuralLoads, name="Applicable Structural Loads" ) load_group_activities: CollectionProperty(name="Load Group Activities", type=StructuralActivity) active_load_group_activity_index: IntProperty(name="Active Load Group Activity Index") + structural_loads: CollectionProperty(name="Structural Loads", type=StructuralLoad) + active_structural_load_index: IntProperty(name="Active Structural Load Index") + active_structural_load_id: IntProperty(name="Active Structural Load Id") + is_editing_loads: BoolProperty(name="Is Editing Loads", default=False) + structural_load_types: EnumProperty(items=getStructuralLoadTypes, name="Structural Load Types") + structural_load_attributes: CollectionProperty(name="Structural Load Attributes", type=Attribute) class BIMObjectStructuralProperties(PropertyGroup): diff --git a/src/blenderbim/blenderbim/bim/module/structural/ui.py b/src/blenderbim/blenderbim/bim/module/structural/ui.py index e8d2080816..6a1737f457 100644 --- a/src/blenderbim/blenderbim/bim/module/structural/ui.py +++ b/src/blenderbim/blenderbim/bim/module/structural/ui.py @@ -1,4 +1,5 @@ import bpy +import blenderbim.bim.helper from bpy.types import Panel, UIList from blenderbim.bim.ifc import IfcStore from ifcopenshell.api.structural.data import Data @@ -402,7 +403,8 @@ class BIM_PT_structural_load_cases(Panel): 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="") + row.prop(self.props, "applicable_structural_load_types", text="") + row.prop(self.props, "applicable_structural_loads", text="") op = row.operator("bim.add_structural_activity", text="", icon="ADD") op.load_group = load_group["id"] layout.template_list( @@ -422,3 +424,65 @@ class BIM_UL_structural_activities(UIList): row = layout.row(align=True) row.label(text=item.name) row.label(text=item.applied_load_class) + + +class BIM_PT_structural_loads(Panel): + bl_label = "IFC Structural Loads" + bl_idname = "BIM_PT_structural_loads" + bl_options = {"DEFAULT_CLOSED"} + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "scene" + + @classmethod + def poll(cls, context): + return IfcStore.get_file() + + def draw(self, context): + if not Data.is_loaded: + Data.load(IfcStore.get_file()) + self.props = context.scene.BIMStructuralProperties + + row = self.layout.row(align=True) + row.label( + text="{} Structural Loads Found".format(len(Data.structural_loads)), icon="GHOST_ENABLED" + ) + if self.props.is_editing_loads: + row.prop(self.props, "structural_load_types", text="") + row.operator("bim.add_structural_load", text="", icon="ADD").ifc_class = self.props.structural_load_types + row.operator("bim.disable_structural_load_editing_ui", text="", icon="CANCEL") + else: + row.operator("bim.load_structural_loads", text="", icon="GREASEPENCIL") + + if self.props.is_editing_loads: + self.layout.template_list( + "BIM_UL_structural_loads", + "", + self.props, + "structural_loads", + self.props, + "active_structural_load_index", + ) + + if self.props.active_structural_load_id: + blenderbim.bim.helper.draw_attributes(self.props.structural_load_attributes, self.layout) + + +class BIM_UL_structural_loads(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=Data.structural_loads[item.ifc_definition_id]["type"]) + + if context.scene.BIMStructuralProperties.active_structural_load_id == item.ifc_definition_id: + row.operator("bim.edit_structural_load", text="", icon="CHECKMARK") + row.operator("bim.disable_editing_structural_load", text="", icon="CANCEL") + elif context.scene.BIMStructuralProperties.active_structural_load_id: + op = row.operator("bim.remove_structural_load", text="", icon="X") + op.structural_load = item.ifc_definition_id + else: + op = row.operator("bim.enable_editing_structural_load", text="", icon="GREASEPENCIL") + op.structural_load = item.ifc_definition_id + op = row.operator("bim.remove_structural_load", text="", icon="X") + op.structural_load = item.ifc_definition_id diff --git a/src/ifcopenshell-python/ifcopenshell/api/structural/add_structural_activity.py b/src/ifcopenshell-python/ifcopenshell/api/structural/add_structural_activity.py index 7fe08a17a1..836d6be578 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/structural/add_structural_activity.py +++ b/src/ifcopenshell-python/ifcopenshell/api/structural/add_structural_activity.py @@ -21,8 +21,7 @@ class Usecase: ifc_class=self.settings["ifc_class"], predefined_type=self.settings["predefined_type"], ) - # TODO - # activity.AppliedLoad = self.settings["applied_load"] + 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") diff --git a/src/ifcopenshell-python/ifcopenshell/api/structural/add_structural_load.py b/src/ifcopenshell-python/ifcopenshell/api/structural/add_structural_load.py new file mode 100644 index 0000000000..4327970a70 --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/structural/add_structural_load.py @@ -0,0 +1,15 @@ +import ifcopenshell.api + + +class Usecase: + def __init__(self, file, **settings): + self.file = file + self.settings = { + "name": None, + "ifc_class": "IfcStructuralLoadLinearForce", + } + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + return self.file.create_entity(self.settings["ifc_class"], Name=self.settings["name"]) diff --git a/src/ifcopenshell-python/ifcopenshell/api/structural/data.py b/src/ifcopenshell-python/ifcopenshell/api/structural/data.py index 653694e808..0104932c10 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/structural/data.py +++ b/src/ifcopenshell-python/ifcopenshell/api/structural/data.py @@ -7,7 +7,7 @@ class Data: connects_structural_members = {} members = {} structural_activities = {} - applied_loads = {} + structural_loads = {} connects_structural_activities = {} load_cases = {} @@ -24,7 +24,7 @@ class Data: cls.connects_structural_members = {} cls.members = {} cls.structural_activities = {} - cls.applied_loads = {} + cls.structural_loads = {} cls.connects_structural_activities = {} cls.load_cases = {} @@ -49,6 +49,7 @@ class Data: cls.load_structural_load_case_combinations() cls.load_structural_load_groups() cls.load_structural_activities() + cls.load_structural_loads() cls.is_loaded = True @classmethod @@ -195,8 +196,14 @@ class Data: cls.connects_structural_members[rel.id()] = rel_data @classmethod - def load_applied_load(cls, applied_load): - cls.applied_loads[applied_load.id()] = applied_load.get_info() + def load_structural_loads(cls): + cls.structural_loads = {} + for load in cls._file.by_type("IfcStructuralLoad"): + cls.load_structural_load(load) + + @classmethod + def load_structural_load(cls, load): + cls.structural_loads[load.id()] = load.get_info() @classmethod def load_connects_structural_activity(cls, rel): @@ -206,7 +213,7 @@ class Data: rel_data["RelatedStructuralActivity"] = rel.RelatedStructuralActivity.id() if rel.RelatedStructuralActivity.AppliedLoad: - cls.load_applied_load(rel.RelatedStructuralActivity.AppliedLoad) + cls.load_structural_load(rel.RelatedStructuralActivity.AppliedLoad) # rel_data["AppliedCondition"] = rel.RelatedStructuralActivity.AppliedLoad.id() cls.connects_structural_activities[rel.id()] = rel_data @@ -238,4 +245,4 @@ class Data: data["ConnectedBy"].append(rel.id()) - cls.members[member.id()] = data \ No newline at end of file + cls.members[member.id()] = data diff --git a/src/ifcopenshell-python/ifcopenshell/api/structural/edit_structural_load.py b/src/ifcopenshell-python/ifcopenshell/api/structural/edit_structural_load.py new file mode 100644 index 0000000000..bf60bc339c --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/structural/edit_structural_load.py @@ -0,0 +1,13 @@ +class Usecase: + def __init__(self, file, **settings): + self.file = file + self.settings = { + "structural_load": None, + "attributes": {} + } + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + for name, value in self.settings["attributes"].items(): + setattr(self.settings["structural_load"], name, value) diff --git a/src/ifcopenshell-python/ifcopenshell/api/structural/remove_structural_load.py b/src/ifcopenshell-python/ifcopenshell/api/structural/remove_structural_load.py new file mode 100644 index 0000000000..8abe47d096 --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/structural/remove_structural_load.py @@ -0,0 +1,9 @@ +class Usecase: + def __init__(self, file, **settings): + self.file = file + self.settings = {"structural_load": None} + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + self.file.remove(self.settings["structural_load"])