From 9ffe92eae5384a46403384c2cb8f691cd400b4e6 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Fri, 22 Oct 2021 15:43:04 +1100 Subject: [PATCH] Assigning a parametric layer type (for walls for now) now attempts to switch from non parametric to parametric geometry. --- .../bim/module/geometry/operator.py | 21 +++--- .../blenderbim/bim/module/model/handler.py | 8 +- .../blenderbim/bim/module/model/slab.py | 10 +-- .../blenderbim/bim/module/model/wall.py | 12 ++- src/blenderbim/blenderbim/tool/type.py | 2 +- .../test/bim/feature/geometry.feature | 1 + src/blenderbim/test/bim/feature/type.feature | 74 ++++++++++++++++++- src/blenderbim/test/tool/test_type.py | 7 +- .../ifcopenshell/api/type/assign_type.py | 38 ++-------- 9 files changed, 108 insertions(+), 65 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/geometry/operator.py b/src/blenderbim/blenderbim/bim/module/geometry/operator.py index 7b9a40afcd..0845274658 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/operator.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/operator.py @@ -200,9 +200,6 @@ class UpdateRepresentation(bpy.types.Operator): ) ) - if not self.ifc_representation_class: - self.ifc_representation_class = self.auto_detect_ifc_representation_class(product, old_representation) or "" - representation_data = { "context": context_of_items, "blender_object": obj, @@ -214,6 +211,9 @@ class UpdateRepresentation(bpy.types.Operator): "ifc_representation_class": self.ifc_representation_class, } + if not self.ifc_representation_class: + self.auto_detect_ifc_representation_class(product, old_representation, representation_data) + new_representation = ifcopenshell.api.run("geometry.add_representation", self.file, **representation_data) [ @@ -248,24 +248,27 @@ class UpdateRepresentation(bpy.types.Operator): if obj.data.BIMMeshProperties.ifc_parameters: bpy.ops.bim.get_representation_ifc_parameters() - def auto_detect_ifc_representation_class(self, element, representation): + def auto_detect_ifc_representation_class(self, element, representation, data): material = ifcopenshell.util.element.get_material(element) if material.is_a("IfcMaterialProfileSetUsage"): - return "IfcExtrudedAreaSolid/IfcMaterialProfileSetUsage" + data["ifc_representation_class"] = "IfcExtrudedAreaSolid/IfcMaterialProfileSetUsage" + data["profile_set_usage"] = material + return extruded_areas = [e for e in self.file.traverse(representation) if e.is_a() == "IfcExtrudedAreaSolid"] if len(extruded_areas) != 1: - return None # It's too complex for us to derive topologically right now + return # It's too complex for us to derive topologically right now profile_def = extruded_areas[0].SweptArea if profile_def.is_a() == "IfcRectangleProfileDef": - return "IfcExtrudedAreaSolid/IfcRectangleProfileDef" + data["ifc_representation_class"] = "IfcExtrudedAreaSolid/IfcRectangleProfileDef" elif profile_def.is_a() == "IfcCircleProfileDef": - return "IfcExtrudedAreaSolid/IfcCircleProfileDef" - return "IfcExtrudedAreaSolid/IfcArbitraryProfileDefWithVoids" + data["ifc_representation_class"] = "IfcExtrudedAreaSolid/IfcCircleProfileDef" + else: + data["ifc_representation_class"] = "IfcExtrudedAreaSolid/IfcArbitraryProfileDefWithVoids" class UpdateParametricRepresentation(bpy.types.Operator): diff --git a/src/blenderbim/blenderbim/bim/module/model/handler.py b/src/blenderbim/blenderbim/bim/module/model/handler.py index 10abf5b2a4..7e2fcc59ca 100644 --- a/src/blenderbim/blenderbim/bim/module/model/handler.py +++ b/src/blenderbim/blenderbim/bim/module/model/handler.py @@ -69,10 +69,10 @@ def load_post(*args): ifcopenshell.api.add_post_listener( "geometry.add_representation", "BlenderBIM.DumbWall.CalculateQuantities", wall.calculate_quantities ) - ifcopenshell.api.add_pre_listener( + ifcopenshell.api.add_post_listener( "material.edit_layer", "BlenderBIM.DumbWall.RegenerateFromLayer", wall.DumbWallPlaner().regenerate_from_layer ) - ifcopenshell.api.add_pre_listener( + ifcopenshell.api.add_post_listener( "type.assign_type", "BlenderBIM.DumbWall.RegenerateFromType", wall.DumbWallPlaner().regenerate_from_type ) @@ -85,10 +85,10 @@ def load_post(*args): ifcopenshell.api.add_post_listener( "geometry.add_representation", "BlenderBIM.DumbSlab.CalculateQuantities", slab.calculate_quantities ) - ifcopenshell.api.add_pre_listener( + ifcopenshell.api.add_post_listener( "material.edit_layer", "BlenderBIM.DumbSlab.RegenerateFromLayer", slab.DumbSlabPlaner().regenerate_from_layer ) - ifcopenshell.api.add_pre_listener( + ifcopenshell.api.add_post_listener( "type.assign_type", "BlenderBIM.DumbSlab.RegenerateFromType", slab.DumbSlabPlaner().regenerate_from_type ) diff --git a/src/blenderbim/blenderbim/bim/module/model/slab.py b/src/blenderbim/blenderbim/bim/module/model/slab.py index 2a7b294513..0436572309 100644 --- a/src/blenderbim/blenderbim/bim/module/model/slab.py +++ b/src/blenderbim/blenderbim/bim/module/model/slab.py @@ -328,7 +328,7 @@ class DumbSlabPlaner: if not total_thickness: continue for inverse in ifc_file.get_inverse(layer_set): - if not inverse.is_a("IfcMaterialLayerSetUsage"): + if not inverse.is_a("IfcMaterialLayerSetUsage") or inverse.LayerSetDirection != "AXIS3": continue if ifc_file.schema == "IFC2X3": for rel in ifc_file.get_inverse(inverse): @@ -347,13 +347,11 @@ class DumbSlabPlaner: if not new_material or not new_material.is_a("IfcMaterialLayerSet"): return new_thickness = sum([l.LayerThickness for l in new_material.MaterialLayers]) - self.change_thickness(settings["related_object"], new_thickness) + material = ifcopenshell.util.element.get_material(settings["related_object"]) + if material and material.is_a("IfcMaterialLayerSetUsage") and material.LayerSetDirection == "AXIS3": + self.change_thickness(settings["related_object"], new_thickness) def change_thickness(self, element, thickness): - parametric = ifcopenshell.util.element.get_psets(element).get("EPset_Parametric") - if not parametric or parametric["Engine"] != "BlenderBIM.DumbLayer3": - return - obj = IfcStore.get_element(element.id()) if not obj: return diff --git a/src/blenderbim/blenderbim/bim/module/model/wall.py b/src/blenderbim/blenderbim/bim/module/model/wall.py index 675103ecec..d47d4e80fa 100644 --- a/src/blenderbim/blenderbim/bim/module/model/wall.py +++ b/src/blenderbim/blenderbim/bim/module/model/wall.py @@ -925,7 +925,7 @@ class DumbWallPlaner: if not total_thickness: continue for inverse in ifc_file.get_inverse(layer_set): - if not inverse.is_a("IfcMaterialLayerSetUsage"): + if not inverse.is_a("IfcMaterialLayerSetUsage") or inverse.LayerSetDirection != "AXIS2": continue if ifc_file.schema == "IFC2X3": for rel in ifc_file.get_inverse(inverse): @@ -944,13 +944,11 @@ class DumbWallPlaner: if not new_material or not new_material.is_a("IfcMaterialLayerSet"): return new_thickness = sum([l.LayerThickness for l in new_material.MaterialLayers]) - self.change_thickness(settings["related_object"], new_thickness) + material = ifcopenshell.util.element.get_material(settings["related_object"]) + if material and material.is_a("IfcMaterialLayerSetUsage") and material.LayerSetDirection == "AXIS2": + self.change_thickness(settings["related_object"], new_thickness) def change_thickness(self, element, thickness): - parametric = ifcopenshell.util.element.get_psets(element).get("EPset_Parametric") - if not parametric or parametric["Engine"] != "BlenderBIM.DumbLayer2": - return - obj = IfcStore.get_element(element.id()) if not obj: return @@ -998,7 +996,7 @@ class DumbWallPlaner: if not slide_vector: continue slide_vector *= slide_magnitude / abs(slide_vector.y) - results.append({"vert":vert, "vector":slide_vector}) + results.append({"vert": vert, "vector": slide_vector}) return results # An end face is a quad that is on one end of the wall or the other. It must diff --git a/src/blenderbim/blenderbim/tool/type.py b/src/blenderbim/blenderbim/tool/type.py index a4885d359c..3798fcf389 100644 --- a/src/blenderbim/blenderbim/tool/type.py +++ b/src/blenderbim/blenderbim/tool/type.py @@ -57,7 +57,7 @@ class Type(blenderbim.core.tool.Type): if material.is_a("IfcMaterialProfileSetUsage"): return "IfcExtrudedAreaSolid/IfcMaterialProfileSetUsage" elif material.is_a("IfcMaterialLayerSetUsage"): - return "IfcExtrudedAreaSolid/IfcExtrudedAreaSolid/IfcArbitraryProfileDefWithVoids" + return "IfcExtrudedAreaSolid/IfcArbitraryProfileDefWithVoids" @classmethod def get_profile_set_usage(cls, element): diff --git a/src/blenderbim/test/bim/feature/geometry.feature b/src/blenderbim/test/bim/feature/geometry.feature index 44cd9eab4a..d76a5cb5d8 100644 --- a/src/blenderbim/test/bim/feature/geometry.feature +++ b/src/blenderbim/test/bim/feature/geometry.feature @@ -87,6 +87,7 @@ Scenario: Override duplicate move - with active IFC data And the object "IfcWall/Cube" is an "IfcWall" And the object "IfcWall/Cube.001" exists And the object "IfcWall/Cube.001" is an "IfcWall" + And the object "IfcWall/Cube.001" has a "Tessellation" representation of "Model/Body/MODEL_VIEW" And the object "IfcBuildingStorey/My Storey.001" exists And the object "IfcBuildingStorey/My Storey.001" is an "IfcBuildingStorey" diff --git a/src/blenderbim/test/bim/feature/type.feature b/src/blenderbim/test/bim/feature/type.feature index 0e60f58801..184c8dd035 100644 --- a/src/blenderbim/test/bim/feature/type.feature +++ b/src/blenderbim/test/bim/feature/type.feature @@ -1,7 +1,7 @@ @type Feature: Type -Scenario: Assign type +Scenario: Assign type - assign to an empty type Given an empty IFC project And I add a cube And the object "Cube" is selected @@ -14,4 +14,74 @@ Scenario: Assign type And I press "bim.assign_class" When the variable "type" is "{ifc}.by_type('IfcWallType')[0].id()" And I press "bim.assign_type(relating_type={type}, related_object="IfcWall/Cube")" - Then nothing happens + Then the object "IfcWall/Cube" has a "Tessellation" representation of "Model/Body/MODEL_VIEW" + +Scenario: Assign type - assign to a type with representation maps + Given an empty IFC project + And I add a cube + And the object "Cube" is selected + And I set "scene.BIMRootProperties.ifc_class" to "IfcWall" + And I press "bim.assign_class" + And I add a cube + And the object "Cube" is selected + And I set "scene.BIMRootProperties.ifc_product" to "IfcElementType" + And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType" + And I press "bim.assign_class" + When the variable "type" is "{ifc}.by_type('IfcWallType')[0].id()" + And I press "bim.assign_type(relating_type={type}, related_object="IfcWall/Cube")" + Then the object "IfcWall/Cube" has a "MappedRepresentation" representation of "Model/Body/MODEL_VIEW" + +Scenario: Assign type - assign to a type with a material layer set + Given an empty IFC project + And I add a cube + And the object "Cube" is selected + And I set "scene.BIMRootProperties.ifc_class" to "IfcWall" + And I press "bim.assign_class" + And I add an empty + And the object "Empty" is selected + And I set "scene.BIMRootProperties.ifc_product" to "IfcElementType" + And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType" + And I press "bim.assign_class" + And I press "bim.add_default_material" + And I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterialLayerSet" + And I press "bim.assign_material" + And I press "bim.enable_editing_assigned_material" + And the variable "layer_set" is "{ifc}.by_type("IfcMaterialLayerSet")[0].id()" + And I press "bim.add_layer(layer_set={layer_set})" + And the variable "layer" is "{ifc}.by_type("IfcMaterialLayer")[0].id()" + And I press "bim.enable_editing_material_set_item(material_set_item={layer})" + And I set "active_object.BIMObjectMaterialProperties.material_set_item_attributes[0].float_value" to "0.1" + And I press "bim.edit_material_set_item(material_set_item={layer})" + And I press "bim.edit_assigned_material(material_set={layer_set})" + When the variable "type" is "{ifc}.by_type('IfcWallType')[0].id()" + And I press "bim.assign_type(relating_type={type}, related_object="IfcWall/Cube")" + Then the object "IfcWall/Cube" has a "SweptSolid" representation of "Model/Body/MODEL_VIEW" + +Scenario: Assign type - assign to a type with a material profile set + Given an empty IFC project + And I add a cube + And the object "Cube" is selected + And I set "scene.BIMRootProperties.ifc_class" to "IfcWall" + And I press "bim.assign_class" + And I add an empty + And the object "Empty" is selected + And I set "scene.BIMRootProperties.ifc_product" to "IfcElementType" + And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType" + And I press "bim.assign_class" + And I press "bim.add_default_material" + And I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterialProfileSet" + And I press "bim.assign_material" + And I press "bim.enable_editing_assigned_material" + And the variable "profile_set" is "{ifc}.by_type("IfcMaterialProfileSet")[0].id()" + And I press "bim.add_profile(profile_set={profile_set})" + And the variable "profile" is "{ifc}.by_type("IfcMaterialProfile")[0].id()" + And I press "bim.enable_editing_material_set_item(material_set_item={profile})" + And I set "active_object.BIMObjectMaterialProperties.profile_classes" to "IfcParameterizedProfileDef" + And I set "active_object.BIMObjectMaterialProperties.parameterized_profile_classes" to "IfcCircleProfileDef" + And I press "bim.assign_parameterized_profile(ifc_class='IfcCircleProfileDef', material_profile={profile})" + And I set "active_object.BIMObjectMaterialProperties.material_set_item_profile_attributes[2].float_value" to "0.2" + And I press "bim.edit_material_set_item(material_set_item={profile})" + And I press "bim.edit_assigned_material(material_set={profile_set})" + When the variable "type" is "{ifc}.by_type('IfcWallType')[0].id()" + And I press "bim.assign_type(relating_type={type}, related_object="IfcWall/Cube")" + Then the object "IfcWall/Cube" has a "SweptSolid" representation of "Model/Body/MODEL_VIEW" diff --git a/src/blenderbim/test/tool/test_type.py b/src/blenderbim/test/tool/test_type.py index 87c7f27f71..dc3783d211 100644 --- a/src/blenderbim/test/tool/test_type.py +++ b/src/blenderbim/test/tool/test_type.py @@ -103,12 +103,9 @@ class TestGetIfcRepresentationClass(NewFile): ifc.createIfcRelAssociatesMaterial( RelatingMaterial=ifc.createIfcMaterialLayerSetUsage(), RelatedObjects=[element] ) - assert ( - subject.get_ifc_representation_class(element) - == "IfcExtrudedAreaSolid/IfcExtrudedAreaSolid/IfcArbitraryProfileDefWithVoids" - ) + assert subject.get_ifc_representation_class(element) == "IfcExtrudedAreaSolid/IfcArbitraryProfileDefWithVoids" - def test_returning_null_for_non_parametric_represntations(self): + def test_returning_null_for_non_parametric_representations(self): ifc = ifcopenshell.file() assert subject.get_ifc_representation_class(ifc.createIfcColumn()) is None diff --git a/src/ifcopenshell-python/ifcopenshell/api/type/assign_type.py b/src/ifcopenshell-python/ifcopenshell/api/type/assign_type.py index 435db477ea..9d1e1d912e 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/type/assign_type.py +++ b/src/ifcopenshell-python/ifcopenshell/api/type/assign_type.py @@ -54,40 +54,16 @@ class Usecase: } ) - ifcopenshell.api.run( - "type.map_type_representations", - self.file, - related_object=self.settings["related_object"], - relating_type=self.settings["relating_type"], - ) + if self.settings["relating_type"].RepresentationMaps: + ifcopenshell.api.run( + "type.map_type_representations", + self.file, + related_object=self.settings["related_object"], + relating_type=self.settings["relating_type"], + ) self.map_material_usages() return types - def map_representations(self): - if not self.settings["relating_type"].RepresentationMaps: - return - representations = [] - if self.settings["related_object"].Representation: - representations = self.settings["related_object"].Representation.Representations - for representation in representations: - # TODO: check if this is right? Surely this can be a single usecase? - ifcopenshell.api.run( - "geometry.unassign_representation", - self.file, - **{"product": self.settings["related_object"], "representation": representation} - ) - ifcopenshell.api.run("geometry.remove_representation", self.file, **{"representation": representation}) - for representation_map in self.settings["relating_type"].RepresentationMaps: - representation = representation_map.MappedRepresentation - mapped_representation = ifcopenshell.api.run( - "geometry.map_representation", self.file, **{"representation": representation} - ) - ifcopenshell.api.run( - "geometry.assign_representation", - self.file, - **{"product": self.settings["related_object"], "representation": mapped_representation} - ) - def map_material_usages(self): type_material = ifcopenshell.util.element.get_material(self.settings["relating_type"]) if not type_material: