From 6e4e6a88a4f5b7fac2ff1f3d5164cc668f7e000e Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Fri, 25 Jun 2021 17:25:26 +1000 Subject: [PATCH] Add support for parametric updating of cardinal points 1-9 for profile set usages --- .../bim/module/material/operator.py | 4 +- .../blenderbim/bim/module/model/handler.py | 5 + .../blenderbim/bim/module/model/product.py | 21 +++ .../api/material/edit_profile_usage.py | 146 ++++++++++++++++++ 4 files changed, 174 insertions(+), 2 deletions(-) create mode 100644 src/ifcopenshell-python/ifcopenshell/api/material/edit_profile_usage.py diff --git a/src/blenderbim/blenderbim/bim/module/material/operator.py b/src/blenderbim/blenderbim/bim/module/material/operator.py index 8cfb2ccf60..d834166586 100644 --- a/src/blenderbim/blenderbim/bim/module/material/operator.py +++ b/src/blenderbim/blenderbim/bim/module/material/operator.py @@ -426,9 +426,9 @@ class EditAssignedMaterial(bpy.types.Operator): attributes = blenderbim.bim.helper.export_attributes(props.material_set_usage_attributes) attributes["CardinalPoint"] = int(attributes["CardinalPoint"]) if attributes["CardinalPoint"] else None ifcopenshell.api.run( - "material.edit_assigned_material", + "material.edit_profile_usage", self.file, - **{"element": material_set_usage, "attributes": attributes}, + **{"usage": material_set_usage, "attributes": attributes}, ) if material_set_usage.is_a("IfcMaterialLayerSetUsage"): Data.load_layer_usages() diff --git a/src/blenderbim/blenderbim/bim/module/model/handler.py b/src/blenderbim/blenderbim/bim/module/model/handler.py index 97f43322cb..91f6182871 100644 --- a/src/blenderbim/blenderbim/bim/module/model/handler.py +++ b/src/blenderbim/blenderbim/bim/module/model/handler.py @@ -11,6 +11,11 @@ def load_post(*args): ifcopenshell.api.add_post_listener( "geometry.add_representation", "BlenderBIM.Product.GenerateBox", product.generate_box ) + ifcopenshell.api.add_post_listener( + "material.edit_profile_usage", + "BlenderBIM.Product.RegenerateProfileUsage", + product.regenerate_profile_usage, + ) IfcStore.add_element_listener(wall.element_listener) ifcopenshell.api.add_pre_listener( diff --git a/src/blenderbim/blenderbim/bim/module/model/product.py b/src/blenderbim/blenderbim/bim/module/model/product.py index c7e808c7a0..d25d62134e 100644 --- a/src/blenderbim/blenderbim/bim/module/model/product.py +++ b/src/blenderbim/blenderbim/bim/module/model/product.py @@ -94,3 +94,24 @@ def generate_box(usecase_path, ifc_file, settings): should_run_listeners=False, **{"product": product, "representation": new_box} ) + +def regenerate_profile_usage(usecase_path, ifc_file, settings): + elements = [] + if ifc_file.schema == "IFC2X3": + for rel in ifc_file.get_inverse(settings["usage"]): + if not rel.is_a("IfcRelAssociatesMaterial"): + continue + for element in rel.RelatedObjects: + elements.append(element) + else: + for rel in settings["usage"].AssociatedTo: + for element in rel.RelatedObjects: + elements.append(element) + + for element in elements: + obj = IfcStore.get_element(element.id()) + if not obj: + continue + representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW") + if representation: + bpy.ops.bim.switch_representation(obj=obj.name, ifc_definition_id=representation.id(), should_reload=True) diff --git a/src/ifcopenshell-python/ifcopenshell/api/material/edit_profile_usage.py b/src/ifcopenshell-python/ifcopenshell/api/material/edit_profile_usage.py new file mode 100644 index 0000000000..2c2039e2ce --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/material/edit_profile_usage.py @@ -0,0 +1,146 @@ +import ifcopenshell.geom +import ifcopenshell.util.representation + + +class Usecase: + def __init__(self, file, **settings): + self.file = file + self.settings = {"usage": None, "attributes": {}} + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + self.cardinal_point = self.settings["attributes"].get("CardinalPoint") + if self.cardinal_point and self.cardinal_point != self.settings["usage"].CardinalPoint: + self.update_cardinal_point() + + for name, value in self.settings["attributes"].items(): + setattr(self.settings["usage"], name, value) + + def update_cardinal_point(self): + material_set = self.settings["usage"].ForProfileSet + self.profile = material_set.CompositeProfile + if not self.profile and material_set.MaterialProfiles: + self.profile = material_set.MaterialProfiles[0].Profile + if not self.profile: + return + + self.position = self.calculate_position() + + if self.file.schema == "IFC2X3": + for rel in self.file.get_inverse(self.settings["usage"]): + if not rel.is_a("IfcRelAssociatesMaterial"): + continue + for element in rel.RelatedObjects: + self.update_representation(element) + else: + for rel in self.settings["usage"].AssociatedTo: + for element in rel.RelatedObjects: + self.update_representation(element) + + def calculate_position(self): + self.dummy = ifcopenshell.file(schema=self.file.schema) + dummy_profile = self.dummy.add(self.profile) + # We clear all radiuses so that we can calculate geometric centroid easily + for i, attribute in enumerate(dummy_profile): + name = dummy_profile.attribute_name(i) + if "Radius" in name and name != "RoundingRadius": + dummy_profile[i] = None + dummy_solid = self.dummy.create_entity("IfcExtrudedAreaSolid", **{ + "SweptArea": dummy_profile, + "ExtrudedDirection": self.dummy.createIfcDirection((0., 0., 1.)), + "Depth": 1 + }) + self.settings_2d = ifcopenshell.geom.settings() + self.settings_2d.set(self.settings_2d.INCLUDE_CURVES, True) + shape = ifcopenshell.geom.create_shape(self.settings_2d, dummy_solid) + + if self.cardinal_point == 1: + return self.get_bottom_left(shape) + elif self.cardinal_point == 2: + return self.get_bottom_centre(shape) + elif self.cardinal_point == 3: + return self.get_bottom_right(shape) + elif self.cardinal_point == 4: + return self.get_mid_depth_left(shape) + elif self.cardinal_point == 5: + return self.get_mid_depth_centre(shape) + elif self.cardinal_point == 6: + return self.get_mid_depth_right(shape) + elif self.cardinal_point == 7: + return self.get_top_left(shape) + elif self.cardinal_point == 8: + return self.get_top_centre(shape) + elif self.cardinal_point == 9: + return self.get_top_right(shape) + + def get_bottom_left(self, shape): + v = shape.verts + x = [v[i] for i in range(0, len(v), 3)] + y = [v[i+1] for i in range(0, len(v), 3)] + width = max(x) - min(x) + height = max(y) - min(y) + return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((-width/2, height/2, 0.))) + + def get_bottom_centre(self, shape): + v = shape.verts + y = [v[i+1] for i in range(0, len(v), 3)] + height = max(y) - min(y) + return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((0., height/2, 0.))) + + def get_bottom_right(self, shape): + v = shape.verts + x = [v[i] for i in range(0, len(v), 3)] + y = [v[i+1] for i in range(0, len(v), 3)] + width = max(x) - min(x) + height = max(y) - min(y) + return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((width/2, height/2, 0.))) + + def get_mid_depth_left(self, shape): + v = shape.verts + x = [v[i] for i in range(0, len(v), 3)] + width = max(x) - min(x) + return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((-width/2, 0., 0.))) + + def get_mid_depth_centre(self, shape): + return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((0., 0., 0.))) + + def get_mid_depth_right(self, shape): + v = shape.verts + x = [v[i] for i in range(0, len(v), 3)] + width = max(x) - min(x) + return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((width/2, 0., 0.))) + + def get_top_left(self, shape): + v = shape.verts + x = [v[i] for i in range(0, len(v), 3)] + y = [v[i+1] for i in range(0, len(v), 3)] + width = max(x) - min(x) + height = max(y) - min(y) + return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((-width/2, -height/2, 0.))) + + def get_top_centre(self, shape): + v = shape.verts + y = [v[i+1] for i in range(0, len(v), 3)] + height = max(y) - min(y) + return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((0., -height/2, 0.))) + + def get_top_right(self, shape): + v = shape.verts + x = [v[i] for i in range(0, len(v), 3)] + y = [v[i+1] for i in range(0, len(v), 3)] + width = max(x) - min(x) + height = max(y) - min(y) + return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((width/2, -height/2, 0.))) + + def update_representation(self, element): + representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW") + if not representation: + return + + for subelement in self.file.traverse(representation): + if subelement.is_a("IfcSweptAreaSolid") and subelement.SweptArea == self.profile: + self.update_swept_area_solid(subelement) + + def update_swept_area_solid(self, element): + element.Position = self.position