diff --git a/src/bonsai/test/bim/feature/qto.feature b/src/bonsai/test/bim/feature/qto.feature index c3cbced3b9..d8eaeef40f 100644 --- a/src/bonsai/test/bim/feature/qto.feature +++ b/src/bonsai/test/bim/feature/qto.feature @@ -5,29 +5,33 @@ Scenario: Calculate circle radius Given an empty Blender session And I add a cube And the object "Cube" is selected - When I press "bim.calculate_circle_radius" - Then "scene.BIMQtoProperties.qto_result" is "1.732" + And I look at the "Simple Quantity Calculator" panel + When I click "Calculate Circle Radius" + Then I see the "Results" property is "1.732" Scenario: Calculate edge lengths Given an empty Blender session And I add a cube And the object "Cube" is selected - When I press "bim.calculate_edge_lengths" - Then "scene.BIMQtoProperties.qto_result" is "24.0" + And I look at the "Simple Quantity Calculator" panel + When I click "Calculate Edge Lengths" + Then I see the "Results" property is "24.0" Scenario: Calculate face areas Given an empty Blender session And I add a cube And the object "Cube" is selected - When I press "bim.calculate_face_areas" - Then "scene.BIMQtoProperties.qto_result" is "24.0" + And I look at the "Simple Quantity Calculator" panel + When I click "Calculate Face Areas" + Then I see the "Results" property is "24.0" Scenario: Calculate object volumes Given an empty Blender session And I add a cube And the object "Cube" is selected - When I press "bim.calculate_object_volumes" - Then "scene.BIMQtoProperties.qto_result" is "8.0" + And I look at the "Simple Quantity Calculator" panel + When I click "Calculate Object Volumes" + Then I see the "Results" property is "8.0" Scenario: Execute qto method - formwork areas Given an empty Blender session @@ -45,13 +49,40 @@ Scenario: Execute qto method - side formwork areas When I press "bim.calculate_side_formwork_area" Then "scene.BIMQtoProperties.qto_result" is "16.0" -Scenario: Calculate all quantities +Scenario: Perform quantity take-off - no objects + Given an empty IFC project + When I look at the "Quantity Take-off" panel + Then I see "Quantifying All Objects" + When I click "Perform Quantity Take-off" + Then nothing happens + +Scenario: Perform quantity take-off Given an empty IFC project And I add a cube And the object "Cube" is selected - And I press "bim.assign_class(ifc_class='IfcWall', predefined_type='SOLIDWALL')" - When I press "bim.perform_quantity_take_off" - And the variable "qset_id" is "{ifc}.by_type('IfcElementQuantity')[0].id()" - And I press "bim.enable_pset_editing(pset_id={qset_id}, obj='IfcWall/Cube', obj_type='Object')" - Then "active_object.PsetProperties.active_pset_name" is "Qto_WallBaseQuantities" - And "active_object.PsetProperties.properties['Length'].metadata.float_value" is roughly "2000.0" + And I look at the "Class" panel + And I set the "Products" property to "IfcElement" + And I set the "Class" property to "IfcWall" + And I click "Assign IFC Class" + And I look at the "Quantity Take-off" panel + When I see "Quantifying 1 Selected Objects" + And I click "Perform Quantity Take-off" + When I look at the "Quantity Sets" panel + Then I see "Qto_WallBaseQuantities" + And I see "NetVolume" + +Scenario: Perform quantity take-off - using Blender engine + Given an empty IFC project + And I add a cube + And the object "Cube" is selected + And I look at the "Class" panel + And I set the "Products" property to "IfcElement" + And I set the "Class" property to "IfcWall" + And I click "Assign IFC Class" + And I look at the "Quantity Take-off" panel + When I set the "qto_rule" property to "IFC4 Base Quantities - Blender" + And I see "Quantifying 1 Selected Objects" + And I click "Perform Quantity Take-off" + When I look at the "Quantity Sets" panel + Then I see "Qto_WallBaseQuantities" + And I see "NetVolume" diff --git a/src/bonsai/test/bim/feature/search.feature b/src/bonsai/test/bim/feature/search.feature index 6d40e71e1a..729e0b1400 100644 --- a/src/bonsai/test/bim/feature/search.feature +++ b/src/bonsai/test/bim/feature/search.feature @@ -26,21 +26,21 @@ Scenario: Colour by property - default class query Given an empty IFC project When I look at the "Colour By Property" panel And I click "Colour by Property" - Then nothing happens + Then the "BIM_UL_colourscheme" list has 4 items Scenario: Colour by property - no query Given an empty IFC project When I look at the "Colour By Property" panel And I set the "Query" property to " " And I click "Colour by Property" and expect error "Error: No Query Provided" - Then nothing happens + Then I don't see the "BIM_UL_colourscheme" list Scenario: Reset colours Given an empty IFC project And I look at the "Colour By Property" panel And I click "Colour by Property" When I click "Reset Colours" - Then nothing happens + Then I don't see the "BIM_UL_colourscheme" list Scenario: Flat colours Given an empty IFC project diff --git a/src/bonsai/test/bim/test_feature.py b/src/bonsai/test/bim/test_feature.py index 5ef289d1a7..c96ccc743b 100644 --- a/src/bonsai/test/bim/test_feature.py +++ b/src/bonsai/test/bim/test_feature.py @@ -60,6 +60,7 @@ class PanelSpy: self.spied_labels = [] self.spied_props = [] self.spied_operators = [] + self.spied_lists = [] self.panel.draw(self, bpy.context) def __getattr__(self, attr): @@ -72,7 +73,17 @@ class PanelSpy: if self.spied_attr in ("row", "column", "box"): return self elif self.spied_attr == "template_list": - return lambda *args, **kwargs: None # Handle UI list spies + listtype_name, list_id, dataptr, propname, active_dataptr, active_propname = args + spied_data = { + "listtype_name": listtype_name, + "list_id": list_id, + "dataptr": dataptr, + "propname": propname, + "active_dataptr": active_dataptr, + "active_propname": active_propname, + } + self.spied_lists.append(spied_data) + return TemplateListSpy(spied_data) elif self.spied_attr == "context_pointer_set": return lambda *args, **kwargs: None elif self.spied_attr == "label": @@ -82,10 +93,29 @@ class PanelSpy: props, name = args text = kwargs.get("text", props.bl_rna.properties[name].name) icon = kwargs.get("icon", None) + prop_type = props.bl_rna.properties[name].type + enum_items = [] + if prop_type == "ENUM": + prop_keywords = props.__annotations__[name].keywords + items = prop_keywords.get("items") + if items is not None: + if isinstance(items, (list, tuple)): + enum_items = items + else: + # items are retrieved through a callback, not a static list / tuple : + enum_items = items(props, bpy.context) value = getattr(props, name) if text: self.spied_labels.append(text) - spied_prop = {"props": props, "name": name, "text": text, "icon": icon, "value": value} + spied_prop = { + "props": props, + "name": name, + "text": text, + "icon": icon, + "value": value, + "prop_type": prop_type, + "enum_items": enum_items, + } self.spied_props.append(spied_prop) elif self.spied_attr == "operator": operator = args[0] @@ -116,6 +146,11 @@ class OperatorSpy: self.spied_data["kwargs"][name] = value +class TemplateListSpy: + def __init__(self, spied_data): + self.spied_data = spied_data + + panel_name_cache = {} panel_spy = None @@ -215,6 +250,14 @@ def i_dont_see_text(text): assert not [l for l in panel_spy.spied_labels if text in l], f"Text {text} found in {panel_spy.spied_labels}" +@given(parsers.parse('I don\'t see the "{name}" list')) +@when(parsers.parse('I don\'t see the "{name}" list')) +@then(parsers.parse('I don\'t see the "{name}" list')) +def i_dont_see_the_name_list(name): + panel_spy.refresh_spy() + assert name not in [l["listtype_name"] for l in panel_spy.spied_lists] + + @given(parsers.parse('I see the "{prop}" property')) @when(parsers.parse('I see the "{prop}" property')) @then(parsers.parse('I see the "{prop}" property')) @@ -257,23 +300,39 @@ def i_set_the_prop_property_to_value(prop, value): panel_spy.refresh_spy() for spied_prop in panel_spy.spied_props: if prop in (spied_prop["name"], spied_prop["text"], spied_prop["icon"]): - if value == "TRUE": - setattr(spied_prop["props"], spied_prop["name"], True) - elif value == "FALSE": - setattr(spied_prop["props"], spied_prop["name"], False) + if spied_prop["prop_type"] == "BOOLEAN": + if value == "TRUE": + setattr(spied_prop["props"], spied_prop["name"], True) + elif value == "FALSE": + setattr(spied_prop["props"], spied_prop["name"], False) + elif spied_prop["prop_type"] == "FLOAT": + setattr(spied_prop["props"], spied_prop["name"], float(value)) + elif spied_prop["prop_type"] == "INTEGER": + setattr(spied_prop["props"], spied_prop["name"], int(value)) + elif spied_prop["prop_type"] == "ENUM": + enum_identifier = [i for i in spied_prop["enum_items"] if i[1] == value] + if not enum_identifier: + assert False, f"Could not find value {value} in enum {spied_prop['enum_items']}" + setattr(spied_prop["props"], spied_prop["name"], enum_identifier[0][0]) else: - try: - setattr(spied_prop["props"], spied_prop["name"], value) - except: - try: - setattr(spied_prop["props"], spied_prop["name"], int(value)) - except: - setattr(spied_prop["props"], spied_prop["name"], float(value)) + setattr(spied_prop["props"], spied_prop["name"], value) panel_spy.is_spy_dirty = True return assert False, f"Property {prop} not found in {panel_spy.spied_props}" +@then(parsers.parse('The "{name}" list has {total} items')) +def the_name_list_has_total_items(name, total): + total = int(total) + panel_spy.refresh_spy() + for spied_list in panel_spy.spied_lists: + if name == spied_list["listtype_name"]: + actual_total = len(getattr(spied_list["dataptr"], spied_list["propname"])) + assert actual_total == total, f"The actual number of items in {name} is {actual_total} not {total}" + return + assert False, f"List {name} not found in {panel_spy.spied_lists}" + + @when("I load a new pset template file") def i_load_a_new_pset_template_file(): IfcStore.pset_template_path = bpy.context.scene.BIMPsetTemplateProperties.pset_template_files