diff --git a/src/blenderbim/blenderbim/bim/module/material/operator.py b/src/blenderbim/blenderbim/bim/module/material/operator.py index 1399562c0a..02e1e30c73 100644 --- a/src/blenderbim/blenderbim/bim/module/material/operator.py +++ b/src/blenderbim/blenderbim/bim/module/material/operator.py @@ -169,7 +169,9 @@ class UnassignMaterial(bpy.types.Operator, tool.Ifc.Operator): for obj in objects: element = tool.Ifc.get_entity(obj) if element: - ifcopenshell.api.run("material.unassign_material", tool.Ifc.get(), product=element) + material = ifcopenshell.util.element.get_material(element, should_inherit=False) + if "Usage" not in material.is_a(): + ifcopenshell.api.run("material.unassign_material", tool.Ifc.get(), product=element) class AddConstituent(bpy.types.Operator, tool.Ifc.Operator): diff --git a/src/blenderbim/blenderbim/bim/module/model/profile.py b/src/blenderbim/blenderbim/bim/module/model/profile.py index 0944ba858c..75a8579f6f 100644 --- a/src/blenderbim/blenderbim/bim/module/model/profile.py +++ b/src/blenderbim/blenderbim/bim/module/model/profile.py @@ -418,7 +418,9 @@ class DumbProfileJoiner: self.body = copy.deepcopy(body) material = ifcopenshell.util.element.get_material(element, should_skip_usage=False) usage = None - if not material or (not material.is_a("IfcMaterialProfileSet") and not material.is_a("IfcMaterialProfileSetUsage")): + if not material: + return + if "ProfileSet" not in material.is_a(): return if material.is_a("IfcMaterialProfileSetUsage"): usage = material diff --git a/src/blenderbim/test/bim/feature/material.feature b/src/blenderbim/test/bim/feature/material.feature index db0bf23019..67ef617b4f 100644 --- a/src/blenderbim/test/bim/feature/material.feature +++ b/src/blenderbim/test/bim/feature/material.feature @@ -141,6 +141,7 @@ Scenario: Assign material - material layer set When I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterialLayerSet" And I press "bim.assign_material" Then the object "IfcWallType/Empty" does not have the material "Default" + And the object "IfcWallType/Empty" has a "0.1" thick layered material containing the material "Default" Scenario: Unassign material - material layer set Given an empty IFC project @@ -155,6 +156,27 @@ Scenario: Unassign material - material layer set When I press "bim.unassign_material" Then nothing happens +Scenario: Unassign material - material layer set usages will not be removed + 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_material(obj='')" + And I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterialLayerSet" + And I press "bim.assign_material" + And the object "IfcWall/Cube" is selected + 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 "0.1" thick layered material containing the material "Default" + When I press "bim.unassign_material" + Then the object "IfcWall/Cube" has a "0.1" thick layered material containing the material "Default" + Scenario: Enable editing assigned material - material layer set Given an empty IFC project And I add a cube diff --git a/src/blenderbim/test/bim/feature/type.feature b/src/blenderbim/test/bim/feature/type.feature index b274ab2bf4..3636e75927 100644 --- a/src/blenderbim/test/bim/feature/type.feature +++ b/src/blenderbim/test/bim/feature/type.feature @@ -1,6 +1,25 @@ @type Feature: Type +Scenario: Enable editing type + 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" + When I press "bim.enable_editing_type" + Then nothing happens + +Scenario: Disable editing type + 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 press "bim.enable_editing_type" + When I press "bim.disable_editing_type" + Then nothing happens + Scenario: Assign type - assign to an empty type Given an empty IFC project And I add a cube @@ -31,7 +50,7 @@ Scenario: Assign type - assign to a type with representation maps 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 +Scenario: Assign type - assign to a type with a material layer set, which automatically recreates the shape Given an empty IFC project And I add a cube And the object "Cube" is selected @@ -45,17 +64,50 @@ Scenario: Assign type - assign to a type with a material layer set And I press "bim.add_material(obj='')" 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" + And the object "IfcWall/Cube" has the material "Default" + And the object "IfcWall/Cube" has a "0.1" thick layered material containing the material "Default" + And the object "IfcWall/Cube" dimensions are "2,.1,3" + +Scenario: Assign type - assign to a dfferent 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 the variable "type" is "{ifc}.by_type('IfcWallType')[-1].id()" + And I press "bim.add_material(obj='')" + And I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterialLayerSet" + And I press "bim.assign_material" + 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 set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterialLayerSet" + And I press "bim.assign_material" + And the variable "type2" is "[w for w in {ifc}.by_type('IfcWallType') if w.id() != {type}][0].id()" + And the variable "layer_set" is "ifcopenshell.util.element.get_material({ifc}.by_id({type2})).id()" + And the variable "layer" is "{ifc}.by_id({layer_set}).MaterialLayers[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.2" + And I press "bim.edit_material_set_item(material_set_item={layer})" + And I press "bim.edit_assigned_material(material_set={layer_set})" + When 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" + And the object "IfcWall/Cube" has the material "Default" + And the object "IfcWall/Cube" has a "0.1" thick layered material containing the material "Default" + And the object "IfcWall/Cube" dimensions are "2,.1,3" + When I press "bim.assign_type(relating_type={type2}, related_object='IfcWall/Cube')" + Then the object "IfcWall/Cube" has a "0.2" thick layered material containing the material "Default" + And the object "IfcWall/Cube" dimensions are "2,.2,3" Scenario: Assign type - assign to a type with a material profile set Given an empty IFC project diff --git a/src/blenderbim/test/bim/test_feature.py b/src/blenderbim/test/bim/test_feature.py index 1aa4c06bc4..b75c7e18d1 100644 --- a/src/blenderbim/test/bim/test_feature.py +++ b/src/blenderbim/test/bim/test_feature.py @@ -48,6 +48,10 @@ def replace_variables(value): return value +def is_x(number, x): + return abs(number - x) < 1e-6 + + @given("an untestable scenario") def an_untestable_scenario(): pass @@ -498,6 +502,27 @@ def the_object_name_has_the_material_material(name, material): assert material in [ms.material.name for ms in the_object_name_exists(name).material_slots] +@then( + parsers.parse('the object "{name}" has a "{thickness}" thick layered material containing the material "{material}"') +) +def the_object_name_has_the_thickness_thick_layered_material_containing_the_material_material( + name, thickness, material +): + material_name = material + element = tool.Ifc.get_entity(the_object_name_exists(name)) + material = ifcopenshell.util.element.get_material(element) + assert material and "LayerSet" in material.is_a() + if material.is_a("IfcMaterialLayerSetUsage"): + material = material.ForLayerSet + total_thickness = 0 + material_names = [] + for layer in material.MaterialLayers or []: + total_thickness += layer.LayerThickness + material_names.append(layer.Material.Name) + assert is_x(total_thickness, float(thickness)) + assert material_name in material_names + + @then(parsers.parse('the object "{name}" does not have the material "{material}"')) def the_object_name_does_not_have_the_material_material(name, material): assert material not in [ms.material.name for ms in the_object_name_exists(name).material_slots] @@ -548,7 +573,10 @@ def the_object_name_has_no_scale(name): @then(parsers.parse('the object "{name}" dimensions are "{dimensions}"')) def the_object_name_dimensions_are_dimensions(name, dimensions): - assert list(the_object_name_exists(name).dimensions) == [float(co) for co in dimensions.split(",")] + actual_dimensions = list(the_object_name_exists(name).dimensions) + expected_dimensions = [float(co) for co in dimensions.split(",")] + for i, number in enumerate(actual_dimensions): + assert is_x(number, expected_dimensions[i]) @then(parsers.parse('the object "{name}" bottom left corner is at "{location}"')) diff --git a/src/ifcopenshell-python/ifcopenshell/api/material/unassign_material.py b/src/ifcopenshell-python/ifcopenshell/api/material/unassign_material.py index e4517f500b..b79318e984 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/material/unassign_material.py +++ b/src/ifcopenshell-python/ifcopenshell/api/material/unassign_material.py @@ -75,11 +75,8 @@ class Usecase: for rel in self.settings["product"].HasAssociations: if rel.is_a("IfcRelAssociatesMaterial"): if rel.RelatingMaterial.is_a() in ["IfcMaterialLayerSetUsage", "IfcMaterialProfileSetUsage"]: - # Usages cannot be unassigned, as all occurrences of a type - # which has a parametric material must use the parametric - # material. Instead, unassign the parametric material from - # the type. - continue + # Warning: this may leave the model in a non-compliant state. + self.file.remove(rel.RelatingMaterial) if len(rel.RelatedObjects) == 1: self.file.remove(rel) continue