diff --git a/src/bonsai/bonsai/core/material.py b/src/bonsai/bonsai/core/material.py index 96c624de64..b70bed7bff 100644 --- a/src/bonsai/bonsai/core/material.py +++ b/src/bonsai/bonsai/core/material.py @@ -142,8 +142,35 @@ def assign_material( else: element_material_type = material_type - ifc.run("material.assign_material", products=[element], type=element_material_type, material=material) - assigned_material = material_tool.get_material(element) + # TODO: this whole dance is a stopgap and wants rewriting. + # + # material.assign_material creates material sets with no items in them, + # ignoring the material it was handed -- an IfcMaterialLayerSet with no + # MaterialLayers is not valid IFC, since the list is mandatory and + # [1:?]. So we repair it below, after the fact. Worse, the API rejects a + # plain IfcMaterial outright when asked for a usage, which is exactly + # what the Object Materials dropdown gives us, so we cannot even pass it + # on and have to let the API invent an empty set and then fill it in. + # + # The fix is for assign_material to build the set around the material it + # is given, rather than leaving an invalid one behind for its callers to + # patch up. That is a wider change than it looks: add_material_set has + # the same behaviour, and the create-empty-then-add-items idiom is + # spread through the API's own docstrings, examples and tests. Until + # that is untangled, keep the repair here where it is at least visible. + + # Only a usage refuses a plain IfcMaterial; every other type still wants + # it, and IfcMaterial and IfcMaterialList cannot be created without it. + pass_material = material_tool.is_a_material_set(material) or not element_material_type.endswith("Usage") + ifc.run( + "material.assign_material", + products=[element], + type=element_material_type, + material=material if pass_material else None, + ) + # A usage points at the set rather than being one, and it is the set + # that needs an item adding to it below. + assigned_material = material_tool.get_material(element, should_skip_usage=True) assert assigned_material # Type checker. if material_tool.is_a_material_set(material): diff --git a/src/bonsai/bonsai/core/tool.py b/src/bonsai/bonsai/core/tool.py index 6280e78309..7ba5e715db 100644 --- a/src/bonsai/bonsai/core/tool.py +++ b/src/bonsai/bonsai/core/tool.py @@ -651,7 +651,7 @@ class Material: def get_default_material(cls): pass def get_elements_by_material(cls, material): pass def get_material_attributes(cls): pass - def get_material(cls, element, should_inherit: bool = False): pass + def get_material(cls, element, should_inherit: bool = False, should_skip_usage: bool = False): pass def get_object_ui_active_material(cls): pass def get_object_ui_material_type(cls): pass def get_style(cls, material): pass diff --git a/src/bonsai/bonsai/tool/material.py b/src/bonsai/bonsai/tool/material.py index 300df8d4ce..695759227c 100644 --- a/src/bonsai/bonsai/tool/material.py +++ b/src/bonsai/bonsai/tool/material.py @@ -220,9 +220,14 @@ class Material(bonsai.core.tool.Material): @classmethod def get_material( - cls, element: ifcopenshell.entity_instance, should_inherit: bool = False + cls, + element: ifcopenshell.entity_instance, + should_inherit: bool = False, + should_skip_usage: bool = False, ) -> Union[ifcopenshell.entity_instance, None]: - return ifcopenshell.util.element.get_material(element, should_inherit=should_inherit) + return ifcopenshell.util.element.get_material( + element, should_inherit=should_inherit, should_skip_usage=should_skip_usage + ) @classmethod def is_a_material_set(cls, material: ifcopenshell.entity_instance) -> bool: diff --git a/src/bonsai/test/bim/test_feature.py b/src/bonsai/test/bim/test_feature.py index 34150fbe98..88697b7c92 100644 --- a/src/bonsai/test/bim/test_feature.py +++ b/src/bonsai/test/bim/test_feature.py @@ -1000,6 +1000,7 @@ def i_click_button_and_expect_error_error_msg(button, error_msg): @given(parsers.parse('I evaluate expression "{expression}"')) @when(parsers.parse('I evaluate expression "{expression}"')) +@then(parsers.parse('I evaluate expression "{expression}"')) def i_evaluate_expression(expression): expression = replace_variables(expression) exec(expression)