More bSDD tests to cover all current functionality

This commit is contained in:
Dion Moult
2025-05-18 12:05:28 +10:00
parent 4bc88eac2f
commit 1de2515fcd
4 changed files with 406 additions and 63 deletions
+1 -1
View File
@@ -84,7 +84,7 @@ class BIMBSDDProperties(PropertyGroup):
use_only_ifc_properties: BoolProperty( use_only_ifc_properties: BoolProperty(
name="Only IFC Properties", name="Only IFC Properties",
description="Whether to display and assign only properties from IFC dictionary", description="Whether to display and assign only properties from IFC dictionary",
default=True, default=False,
) )
load_preview_dictionaries: BoolProperty( load_preview_dictionaries: BoolProperty(
name="Load Preview Dictionaries", description="Load dictionaries marked as Preview status", default=False name="Load Preview Dictionaries", description="Load dictionaries marked as Preview status", default=False
+73 -2
View File
@@ -8,7 +8,7 @@ Scenario: Load bSDD dictionaries
Then I see "Selected dictionary" Then I see "Selected dictionary"
And I see "LCA" in the "1st" list And I see "LCA" in the "1st" list
Scenario: Add dictionaries as classification systems Scenario: Add classification from bSDD - add all dictionaries
Given an empty IFC project Given an empty IFC project
And I look at the "buildingSMART Data Dictionary" panel And I look at the "buildingSMART Data Dictionary" panel
And I click "Load bSDD Dictionaries" And I click "Load bSDD Dictionaries"
@@ -21,7 +21,7 @@ Scenario: Add dictionaries as classification systems
Then I see "LCA" Then I see "LCA"
And I see "BonsaiTestDict" And I see "BonsaiTestDict"
Scenario: Add a single dictionary as a classification systems Scenario: Add classification from bSDD - add single dictionary
Given an empty IFC project Given an empty IFC project
And I look at the "buildingSMART Data Dictionary" panel And I look at the "buildingSMART Data Dictionary" panel
And I click "Load bSDD Dictionaries" And I click "Load bSDD Dictionaries"
@@ -32,3 +32,74 @@ Scenario: Add a single dictionary as a classification systems
And I click "Add Classification From bSDD" And I click "Add Classification From bSDD"
Then I see "BonsaiTestDict" Then I see "BonsaiTestDict"
And I don't see "LCA" And I don't see "LCA"
Scenario: Search bSDD classifications - search all dictionaries
Given an empty IFC project
And I look at the "buildingSMART Data Dictionary" panel
And I click "Load bSDD Dictionaries"
When I click "is_active" in the row where I see "LCA" in the "1st" list
And I click "is_active" in the row where I see "BonsaiTestDict" in the "1st" list
And I select the object "IfcSite/My Site"
And I look at the "Classification References" panel
And I set the "classification_source" property to "buildingSMART Data Dictionary"
And I set the "active_dictionary" property to "All Dictionaries"
And I click "VIEWZOOM"
Then I see "Acidification" in the "1st" list
And I see "BonsaiReferenceA" in the "1st" list
Scenario: Search bSDD classifications - search single dictionary
Given an empty IFC project
And I look at the "buildingSMART Data Dictionary" panel
And I click "Load bSDD Dictionaries"
And I click "is_active" in the row where I see "BonsaiTestDict" in the "1st" list
And I select the object "IfcSite/My Site"
And I look at the "Classification References" panel
When I set the "classification_source" property to "buildingSMART Data Dictionary"
And I set the "active_dictionary" property to "BonsaiTestDict"
And I click "VIEWZOOM"
Then I see "BonsaiReferenceA" in the "1st" list
And I don't see "Acidification" in the "1st" list
Scenario: Add classification reference from bSDD - add classification only
Given an empty IFC project
And I look at the "buildingSMART Data Dictionary" panel
And I click "Load bSDD Dictionaries"
When I click "is_active" in the row where I see "LCA" in the "1st" list
And I click "is_active" in the row where I see "BonsaiTestDict" in the "1st" list
And I select the object "IfcSite/My Site"
And I look at the "Classification References" panel
And I set the "classification_source" property to "buildingSMART Data Dictionary"
And I set the "active_dictionary" property to "All Dictionaries"
And I click "VIEWZOOM"
Then I see "Acidification" in the "1st" list
When I select the row where I see "Acidification" in the "1st" list
And I click "Add Classification Reference"
Then I see "Acidification"
And I don't see "BonsaiReferenceA"
Scenario: Get bSDD classification properties - get associated properties then add classification
Given an empty IFC project
And I look at the "buildingSMART Data Dictionary" panel
And I click "Load bSDD Dictionaries"
When I click "is_active" in the row where I see "LCA" in the "1st" list
And I select the object "IfcSite/My Site"
And I look at the "Classification References" panel
And I set the "classification_source" property to "buildingSMART Data Dictionary"
And I set the "active_dictionary" property to "All Dictionaries"
And I click "VIEWZOOM"
When I select the row where I see "Acidification" in the "1st" list
And I click "COPY_ID"
Then I see "SizeSet"
And I see "Height"
And I see "Volume"
And I see "No References"
When I set the "Height" property to "42"
And I click "Add Classification Reference"
Then I see "Acidification"
And I don't see "No References"
When I look at the "Property Sets" panel
Then I see "SizeSet"
And I see "Height"
And I see "42"
And I see "Volume"
And I see "0"
+274
View File
@@ -0,0 +1,274 @@
# Bonsai - OpenBIM Blender Add-on
# Copyright (C) 2025 Dion Moult <dion@thinkmoult.com>
#
# This file is part of Bonsai.
#
# Bonsai is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Bonsai is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
from typing import Union, Any, Optional
class bSDDClientStub:
def get_dictionary(self, dictionary_uri=None, include_test_dictionaries=False):
dicts = {
"dictionaries": [
{
"availableLanguages": [{"code": "EN", "name": "English"}],
"code": "LCA",
"uri": "https://identifier.buildingsmart.org/uri/LCA",
"name": "LCA indicators and modules",
"version": "3.0",
"organizationCodeOwner": "LCA",
"organizationNameOwner": "buildingSMART Sustainability Strategic Group",
"defaultLanguageCode": "EN",
"isLatestVersion": True,
"isVerified": False,
"isPrivate": False,
"license": "No license (rights reserved)",
"licenseUrl": "https://technical.buildingsmart.org/services/bsdd/license/",
"qualityAssuranceProcedure": "EN ISO 23386:2020",
"status": "Active",
"moreInfoUrl": "https://www.lignum.ch/leistungen/projekte/buildingsmart-data-dictionary-bsdd/",
"releaseDate": "2023-12-01T14:14:19Z",
"lastUpdatedUtc": "2023-12-01T14:17:53Z",
},
{
"availableLanguages": [{"code": "EN", "name": "English"}],
"code": "BonsaiTestDict",
"uri": "https://identifier.buildingsmart.org/uri/BonsaiTestDict",
"name": "BonsaiTestDict",
"version": "3.0",
"organizationCodeOwner": "LCA",
"organizationNameOwner": "buildingSMART Sustainability Strategic Group",
"defaultLanguageCode": "EN",
"isLatestVersion": True,
"isVerified": False,
"isPrivate": False,
"license": "No license (rights reserved)",
"licenseUrl": "https://technical.buildingsmart.org/services/bsdd/license/",
"qualityAssuranceProcedure": "EN ISO 23386:2020",
"status": "Active",
"moreInfoUrl": "https://www.lignum.ch/leistungen/projekte/buildingsmart-data-dictionary-bsdd/",
"releaseDate": "2023-12-01T14:14:19Z",
"lastUpdatedUtc": "2023-12-01T14:17:53Z",
},
],
"totalCount": 2,
"offset": 0,
"count": 2,
}
if not dictionary_uri:
return dicts
for dictionary in dicts["dictionaries"]:
if dictionary["uri"] == dictionary_uri:
dicts["dictionaries"] = [dictionary]
return dicts
assert False, f"Could not find dictionary uri {dictionary_uri}"
def get_classes(
self,
dictionary_uri: str,
use_nested_classes: bool = True,
class_type="Class",
search_text: str = "",
related_ifc_entity: str = "",
language_code: str = "",
version: int = 1,
offset=0,
limit=1000,
):
classes = {
"classes": [],
"classesTotalCount": 2,
"classesOffset": 0,
"classesCount": 2,
"code": "LCA",
"uri": "https://identifier.buildingsmart.org/uri/LCA/LCA/3.0",
"name": "LCA indicators and modules",
"version": "3.0",
"organizationCodeOwner": "LCA",
"organizationNameOwner": "buildingSMART Sustainability Strategic Group",
"defaultLanguageCode": "EN",
"isLatestVersion": True,
"isVerified": False,
"isPrivate": False,
"license": "No license (rights reserved)",
"licenseUrl": "https://technical.buildingsmart.org/services/bsdd/license/",
"qualityAssuranceProcedure": "EN ISO 23386:2020",
"status": "Preview",
"moreInfoUrl": "https://www.lignum.ch/leistungen/projekte/buildingsmart-data-dictionary-bsdd/",
"releaseDate": "2023-12-01T14:14:19Z",
"lastUpdatedUtc": "2023-12-01T14:17:53Z",
}
if dictionary_uri == "https://identifier.buildingsmart.org/uri/LCA":
classes["classes"].extend(
[
{
"uri": "https://identifier.buildingsmart.org/uri/LCA/LCA/3.0/class/Acidification",
"code": "Acidification",
"name": "Acidification",
"classType": "Class",
"referenceCode": "Acidification",
"descriptionPart": "",
},
{
"uri": "https://identifier.buildingsmart.org/uri/LCA/LCA/3.0/class/Biogeniccarboncontentatthefactorygate",
"code": "Biogeniccarboncontentatthefactorygate",
"name": "Biogenic carbon content at the factory gate",
"classType": "Class",
"referenceCode": "Biogeniccarboncontentatthefactorygate",
"descriptionPart": "",
},
]
)
elif dictionary_uri == "https://identifier.buildingsmart.org/uri/BonsaiTestDict":
classes["classes"].extend(
[
{
"uri": "https://identifier.buildingsmart.org/uri/BonsaiTestDict/class/BonsaiReferenceA",
"code": "BonsaiReferenceA",
"name": "BonsaiReferenceA",
"classType": "Class",
"referenceCode": "BonsaiReferenceA",
"descriptionPart": "",
},
{
"uri": "https://identifier.buildingsmart.org/uri/BonsaiTestDict/class/BonsaiReferenceB",
"code": "BonsaiReferenceB",
"name": "BonsaiReferenceB",
"classType": "Class",
"referenceCode": "BonsaiReferenceB",
"descriptionPart": "",
},
]
)
if offset != 0:
classes["classes"].clear()
classes["classesTotalCount"] = 0
classes["classesCount"] = 0
return classes
def get_class(
self,
class_uri: str,
include_class_properties: bool = True,
include_child_class_reference: bool = True,
include_class_relations: bool = True,
include_reverse_relations: bool = False,
reverse_relation_dictionary_uris: Optional[list[str]] = None,
language_code: str = "",
version: int = 1,
):
result = {
"classType": "Class",
"referenceCode": "apple",
"relatedIfcEntityNames": ["IfcCommunicationsAppliance"],
"parentClassReference": {
"uri": "https://identifier.buildingsmart.org/uri/bs-agri/fruitvegs/1.1/class/fruit",
"name": "Fruit",
"code": "fruit",
},
"hierarchy": [
{
"level": 1,
"name": "Fruit",
"code": "fruit",
"uri": "https://identifier.buildingsmart.org/uri/bs-agri/fruitvegs/1.1/class/fruit",
},
{
"level": 2,
"name": "Apple",
"code": "apple",
"uri": "https://identifier.buildingsmart.org/uri/bs-agri/fruitvegs/1.1/class/apple",
},
],
"classProperties": [
{
"name": "Height",
"uri": "https://identifier.buildingsmart.org/uri/bs-agri/fruitvegs/1.1/class/apple/prop/SizeSet/height",
"description": "The height of an apple",
"definition": "Distance from bottom to top of something standing upright",
"dataType": "Real",
"dimension": "1 0 0 0 0 0 0",
"dimensionLength": 1,
"dimensionMass": 0,
"dimensionTime": 0,
"dimensionElectricCurrent": 0,
"dimensionThermodynamicTemperature": 0,
"dimensionAmountOfSubstance": 0,
"dimensionLuminousIntensity": 0,
"isRequired": True,
"isWritable": True,
"maxInclusive": 25,
"minExclusive": 1,
"physicalQuantity": "Height",
"propertyCode": "height",
"propertyDictionaryName": "Fruit and vegetables",
"propertyDictionaryUri": "https://identifier.buildingsmart.org/uri/bs-agri/fruitvegs/1.1",
"propertyUri": "https://identifier.buildingsmart.org/uri/bs-agri/fruitvegs/1.1/prop/height",
"propertySet": "SizeSet",
"propertyStatus": "Active",
"propertyValueKind": "Single",
"units": ["cm"],
"qudtCodes": ["CentiM"],
},
{
"name": "Volume",
"uri": "https://identifier.buildingsmart.org/uri/bs-agri/fruitvegs/1.1/class/apple/prop/SizeSet/volume",
"description": "The volume of an apple",
"definition": "Volume is a scalar quantity expressing the amount of three-dimensional space enclosed by a closed surface. Volume is often quantified numerically using the SI derived unit, the cubic metre",
"dataType": "Real",
"dimension": "3 0 0 0 0 0 0",
"dimensionLength": 3,
"dimensionMass": 0,
"dimensionTime": 0,
"dimensionElectricCurrent": 0,
"dimensionThermodynamicTemperature": 0,
"dimensionAmountOfSubstance": 0,
"dimensionLuminousIntensity": 0,
"example": "For example, the space that a substance or 3D shape occupies or contains.",
"isRequired": True,
"isWritable": True,
"maxInclusive": 20,
"minExclusive": 1,
"physicalQuantity": "Volume",
"propertyCode": "volume",
"propertyDictionaryName": "Fruit and vegetables",
"propertyDictionaryUri": "https://identifier.buildingsmart.org/uri/bs-agri/fruitvegs/1.1",
"propertyUri": "https://identifier.buildingsmart.org/uri/bs-agri/fruitvegs/1.1/prop/volume",
"propertySet": "SizeSet",
"propertyStatus": "Active",
"propertyValueKind": "Single",
"units": ["cm³"],
"qudtCodes": ["CentiM3"],
},
],
"dictionaryUri": "https://identifier.buildingsmart.org/uri/bs-agri/fruitvegs/1.1",
"activationDateUtc": "2022-09-26T00:00:00Z",
"code": "apple",
"creatorLanguageCode": "en-GB",
"countriesOfUse": [],
"definition": "The round fruit of a tree of the rose family, which typically has thin green or red skin and crisp flesh",
"name": "Apple",
"uri": "https://identifier.buildingsmart.org/uri/bs-agri/fruitvegs/1.1/class/apple",
"replacedObjectCodes": [],
"replacingObjectCodes": [],
"revisionNumber": 0,
"status": "Active",
"subdivisionsOfUse": [],
"versionDateUtc": "2022-09-26T00:00:00Z",
"versionNumber": 1,
}
return result
+58 -60
View File
@@ -22,6 +22,7 @@ import pytest
import traceback import traceback
import webbrowser import webbrowser
import numpy as np import numpy as np
import test.bim.stub
import ifcopenshell import ifcopenshell
import ifcopenshell.util.element import ifcopenshell.util.element
import ifcopenshell.util.representation import ifcopenshell.util.representation
@@ -50,65 +51,7 @@ variables = {
webbrowser.open = lambda x: True webbrowser.open = lambda x: True
class bSDDClientStub: tool.Bsdd.client = test.bim.stub.bSDDClientStub()
def get_dictionary(self, dictionary_uri=None, include_test_dictionaries=False):
dicts = {
"dictionaries": [
{
"availableLanguages": [{"code": "EN", "name": "English"}],
"code": "LCA",
"uri": "https://identifier.buildingsmart.org/uri/LCA/LCA/3.0",
"name": "LCA indicators and modules",
"version": "3.0",
"organizationCodeOwner": "LCA",
"organizationNameOwner": "buildingSMART Sustainability Strategic Group",
"defaultLanguageCode": "EN",
"isLatestVersion": True,
"isVerified": False,
"isPrivate": False,
"license": "No license (rights reserved)",
"licenseUrl": "https://technical.buildingsmart.org/services/bsdd/license/",
"qualityAssuranceProcedure": "EN ISO 23386:2020",
"status": "Active",
"moreInfoUrl": "https://www.lignum.ch/leistungen/projekte/buildingsmart-data-dictionary-bsdd/",
"releaseDate": "2023-12-01T14:14:19Z",
"lastUpdatedUtc": "2023-12-01T14:17:53Z",
},
{
"availableLanguages": [{"code": "EN", "name": "English"}],
"code": "BonsaiTestDict",
"uri": "https://identifier.buildingsmart.org/uri/BonsaiTestDict",
"name": "BonsaiTestDict",
"version": "3.0",
"organizationCodeOwner": "LCA",
"organizationNameOwner": "buildingSMART Sustainability Strategic Group",
"defaultLanguageCode": "EN",
"isLatestVersion": True,
"isVerified": False,
"isPrivate": False,
"license": "No license (rights reserved)",
"licenseUrl": "https://technical.buildingsmart.org/services/bsdd/license/",
"qualityAssuranceProcedure": "EN ISO 23386:2020",
"status": "Active",
"moreInfoUrl": "https://www.lignum.ch/leistungen/projekte/buildingsmart-data-dictionary-bsdd/",
"releaseDate": "2023-12-01T14:14:19Z",
"lastUpdatedUtc": "2023-12-01T14:17:53Z",
}
],
"totalCount": 2,
"offset": 0,
"count": 2,
}
if not dictionary_uri:
return dicts
for dictionary in dicts["dictionaries"]:
if dictionary["uri"] == dictionary_uri:
dicts["dictionaries"] = [dictionary]
return dicts
assert False, f"Could not find dictionary uri {dictionary_uri}"
tool.Bsdd.client = bSDDClientStub()
class PanelSpy: class PanelSpy:
@@ -220,7 +163,7 @@ class OperatorSpy:
class TemplateListSpy(PanelSpy): class TemplateListSpy(PanelSpy):
def __init__(self, template_list: type[bpy.types.UIList], spied_data: dict): def __init__(self, template_list: type[bpy.types.UIList], spied_data: dict) -> None:
self.spied_data = spied_data self.spied_data = spied_data
self.items = getattr(self.spied_data["dataptr"], self.spied_data["propname"]) self.items = getattr(self.spied_data["dataptr"], self.spied_data["propname"])
self.active_index = getattr(self.spied_data["active_dataptr"], self.spied_data["active_propname"]) self.active_index = getattr(self.spied_data["active_dataptr"], self.spied_data["active_propname"])
@@ -234,6 +177,14 @@ class TemplateListSpy(PanelSpy):
for item in self.items: for item in self.items:
self.rows.append(TemplateListItemSpy(self, item)) self.rows.append(TemplateListItemSpy(self, item))
def set_active_index(self, index: int) -> None:
self.active_index = index
setattr(self.spied_data["active_dataptr"], self.spied_data["active_propname"], self.active_index)
try:
self.active_item = self.items[self.active_index]
except:
assert False, f"Could not set active index {index}"
class TemplateListItemSpy(PanelSpy): class TemplateListItemSpy(PanelSpy):
def __init__(self, parent: TemplateListSpy, item): def __init__(self, parent: TemplateListSpy, item):
@@ -409,6 +360,27 @@ def i_see_text_in_the_nth_list(text, nth):
assert False, f"Could not see '{text}' in any list. We saw:\n{debug}" assert False, f"Could not see '{text}' in any list. We saw:\n{debug}"
@given(parsers.parse('I don\'t see "{text}" in the "{nth}" list'))
@when(parsers.parse('I don\'t see "{text}" in the "{nth}" list'))
@then(parsers.parse('I don\'t see "{text}" in the "{nth}" list'))
def i_dont_see_text_in_the_nth_list(text, nth):
assert panel_spy
panel_spy.refresh_spy()
nth = int("".join([c for c in nth if c.isnumeric()]))
if len(panel_spy.spied_lists) < nth:
assert False, f"{nth} list does not exist. Actual number of lists: {len(panel_spy.spied_lists)}"
debug = []
for i, template_list in enumerate(panel_spy.spied_lists):
if i + 1 != nth:
continue
for row in template_list.rows:
for l in row.spied_labels:
debug.append(l)
if text in l:
debug = "\n".join(debug)
assert False, f"We see saw '{text}' in the {nth} list but should not have. We saw:\n{debug}"
@given(parsers.parse('I click "{button}" in the row where I see "{text}" in the "{nth}" list')) @given(parsers.parse('I click "{button}" in the row where I see "{text}" in the "{nth}" list'))
@when(parsers.parse('I click "{button}" in the row where I see "{text}" in the "{nth}" list')) @when(parsers.parse('I click "{button}" in the row where I see "{text}" in the "{nth}" list'))
@then(parsers.parse('I click "{button}" in the row where I see "{text}" in the "{nth}" list')) @then(parsers.parse('I click "{button}" in the row where I see "{text}" in the "{nth}" list'))
@@ -438,6 +410,32 @@ def i_click_button_in_the_row_where_i_see_text_in_the_nth_list(button, text, nth
assert False, f"Could not see '{text}' in any list. We saw:\n{debug}" assert False, f"Could not see '{text}' in any list. We saw:\n{debug}"
@given(parsers.parse('I select the row where I see "{text}" in the "{nth}" list'))
@when(parsers.parse('I select the row where I see "{text}" in the "{nth}" list'))
@then(parsers.parse('I select the row where I see "{text}" in the "{nth}" list'))
def i_select_the_row_where_i_see_text_in_the_nth_list(text, nth):
assert panel_spy
panel_spy.refresh_spy()
nth = int("".join([c for c in nth if c.isnumeric()]))
if len(panel_spy.spied_lists) < nth:
assert False, f"{nth} list does not exist. Actual number of lists: {len(panel_spy.spied_lists)}"
debug = []
for i, template_list in enumerate(panel_spy.spied_lists):
if i + 1 != nth:
continue
for i, row in enumerate(template_list.rows):
is_row = False
for l in row.spied_labels:
debug.append(l)
if text in l:
is_row = True
if is_row:
template_list.set_active_index(i)
return True
debug = "\n".join(debug)
assert False, f"Could not see '{text}' in any list. We saw:\n{debug}"
@given(parsers.parse('I don\'t see "{text}"')) @given(parsers.parse('I don\'t see "{text}"'))
@when(parsers.parse('I don\'t see "{text}"')) @when(parsers.parse('I don\'t see "{text}"'))
@then(parsers.parse('I don\'t see "{text}"')) @then(parsers.parse('I don\'t see "{text}"'))