From ed8eb7543b7d02043f14637e4afce5886a0ef2e5 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Thu, 28 Mar 2024 22:47:48 +1100 Subject: [PATCH] Fix #4465. Update IfcTester to IDS 0.9.7 --- src/ifctester/README.md | 7 +- src/ifctester/ifctester/facet.py | 161 +++++++++------------ src/ifctester/ifctester/ids.py | 29 ++-- src/ifctester/ifctester/ids.xsd | 104 ++++++++++---- src/ifctester/test/test_facet.py | 237 +++++++++++++++---------------- src/ifctester/test/test_ids.py | 14 +- 6 files changed, 281 insertions(+), 271 deletions(-) diff --git a/src/ifctester/README.md b/src/ifctester/README.md index 59b9a4af20..5b3487675e 100644 --- a/src/ifctester/README.md +++ b/src/ifctester/README.md @@ -16,14 +16,13 @@ my_ids = ids.Ids(title="My IDS") my_spec = ids.Specification(name="My first specification") my_spec.applicability.append(ids.Entity(name="IFCWALL")) property = ids.Property( - name="IsExternal", + baseName="IsExternal", value="TRUE", propertySet="Pset_WallCommon", - datatype="IfcBoolean", + dataType="IfcBoolean", uri="https://identifier.buildingsmart.org/uri/.../prop/LoadBearing", instructions="Walls need to be load bearing.", - minOccurs=1, - maxOccurs="unbounded") + cardinality="required") my_spec.requirements.append(property) my_ids.specifications.append(my_spec) diff --git a/src/ifctester/ifctester/facet.py b/src/ifctester/ifctester/facet.py index d57e56d06b..9f7356ee95 100644 --- a/src/ifctester/ifctester/facet.py +++ b/src/ifctester/ifctester/facet.py @@ -66,22 +66,22 @@ class Facet: for name in self.parameters: value = getattr(self, name.replace("@", "")) if value is not None: + if name == "@dataType": + value = value.upper() results[name] = value if "@" in name else self.to_ids_value(value) if clause_type == "applicability": - for key in ["@uri", "@instructions", "@minOccurs", "@maxOccurs"]: + for key in ["@uri", "@instructions", "@cardinality"]: results.pop(key, None) return results def parse(self, xml): - setattr(self, "minOccurs", 1) - setattr(self, "maxOccurs", 1) + setattr(self, "cardinality", "required") for name, value in xml.items(): name = name.replace("@", "") if isinstance(value, dict) and "simpleValue" in value.keys(): setattr(self, name, value["simpleValue"]) elif isinstance(value, dict) and "restriction" in value.keys(): setattr(self, name, Restriction().parse(value["restriction"][0])) - # TODO handle more than one restriction: return [restriction(r) for r in v["restriction"]] else: setattr(self, name, value) return self @@ -96,9 +96,9 @@ class Facet: templates = self.applicability_templates elif clause_type == "requirement": is_prohibited = False - if specification.maxOccurs == 0: + if specification.cardinality == "prohibited": is_prohibited = not is_prohibited - if requirement.maxOccurs == 0: + if requirement.cardinality == "prohibited": is_prohibited = not is_prohibited templates = self.prohibited_templates if is_prohibited else self.requirement_templates @@ -131,12 +131,7 @@ class Facet: return parameter_dict def get_usage(self): - if self.minOccurs != 0: - return "required" - elif self.minOccurs == 0 and self.maxOccurs != 0: - return "optional" - elif self.maxOccurs == 0: - return "prohibited" + return self.cardinality class Entity(Facet): @@ -197,8 +192,8 @@ class Entity(Facet): class Attribute(Facet): - def __init__(self, name="Name", value=None, minOccurs=None, maxOccurs=None, instructions=None): - self.parameters = ["name", "value", "@minOccurs", "@maxOccurs", "@instructions"] + def __init__(self, name="Name", value=None, cardinality="required", instructions=None): + self.parameters = ["name", "value", "@cardinality", "@instructions"] self.applicability_templates = [ "Data where the {name} is {value}", "Data where the {name} is provided", @@ -211,7 +206,7 @@ class Attribute(Facet): "The {name} shall not be {value}", "The {name} shall not be provided", ] - super().__init__(name, value, minOccurs, maxOccurs, instructions) + super().__init__(name, value, cardinality, instructions) def filter( self, ifc_file: ifcopenshell.file, elements: Union[ifcopenshell.entity_instance, None] @@ -242,7 +237,7 @@ class Attribute(Facet): return results def __call__(self, inst, logger=None): - if self.minOccurs == 0 and self.maxOccurs != 0: + if self.cardinality == "optional": return AttributeResult(True) if isinstance(self.name, str): @@ -323,34 +318,31 @@ class Attribute(Facet): reason = {"type": "VALUE", "actual": value} break - if self.maxOccurs == 0: + if self.cardinality == "prohibited": return AttributeResult(not is_pass, {"type": "PROHIBITED"}) return AttributeResult(is_pass, reason) class Classification(Facet): - def __init__(self, value=None, system=None, uri=None, minOccurs=None, maxOccurs="unbounded", instructions=None): - self.parameters = ["value", "system", "@uri", "@minOccurs", "@maxOccurs", "@instructions"] + def __init__(self, value=None, system=None, uri=None, cardinality="required", instructions=None): + self.parameters = ["value", "system", "@uri", "@cardinality", "@instructions"] self.applicability_templates = [ "Data having a {system} reference of {value}", "Data classified using {system}", "Data classified as {value}", - "Classified data", ] self.requirement_templates = [ "Shall have a {system} reference of {value}", "Shall be classified using {system}", "Shall be classified as {value}", - "Shall be classified", ] self.prohibited_templates = [ "Shall not have a {system} reference of {value}", "Shall not be classified using {system}", "Shall not be classified as {value}", - "Shall not be classified", ] - super().__init__(value, system, uri, minOccurs, maxOccurs, instructions) + super().__init__(value, system, uri, cardinality, instructions) def filter( self, ifc_file: ifcopenshell.file, elements: Union[ifcopenshell.entity_instance, None] @@ -360,8 +352,8 @@ class Classification(Facet): return ifc_file.by_type("IfcObjectDefinition") def __call__(self, inst, logger=None): - if self.minOccurs == 0 and self.maxOccurs != 0: - return ClassificationResult(True) + if self.cardinality == "optional": + return ClassificationResult(True) # Is this really the correct behaviour? leaf_references = ifcopenshell.util.classification.get_references(inst) @@ -381,13 +373,13 @@ class Classification(Facet): if not is_pass: reason = {"type": "VALUE", "actual": values} - if is_pass and self.system: + if is_pass: systems = [ifcopenshell.util.classification.get_classification(r).Name for r in references] is_pass = any([self.system == s for s in systems]) if not is_pass: reason = {"type": "SYSTEM", "actual": systems} - if self.maxOccurs == 0: + if self.cardinality == "prohibited": return ClassificationResult(not is_pass, {"type": "PROHIBITED"}) return ClassificationResult(is_pass, reason) @@ -398,11 +390,10 @@ class PartOf(Facet): name="IFCWALL", predefinedType=None, relation=None, - minOccurs=None, - maxOccurs="unbounded", + cardinality="required", instructions=None, ): - self.parameters = ["name", "predefinedType", "@relation", "@minOccurs", "@maxOccurs", "@instructions"] + self.parameters = ["name", "predefinedType", "@relation", "@cardinality", "@instructions"] self.applicability_templates = [ "An element with an {relation} relationship with an {name}", "An element with an {relation} relationship", @@ -415,7 +406,7 @@ class PartOf(Facet): "An element must not have an {relation} relationship with an {name}", "An element must not have an {relation} relationship", ] - super().__init__(name, predefinedType, relation, minOccurs, maxOccurs, instructions) + super().__init__(name, predefinedType, relation, cardinality, instructions) def filter( self, ifc_file: ifcopenshell.file, elements: Union[ifcopenshell.entity_instance, None] @@ -444,9 +435,6 @@ class PartOf(Facet): return super().parse(xml) def __call__(self, inst, logger=None): - if self.minOccurs == 0 and self.maxOccurs != 0: - return PartOfResult(True) - reason = None if not self.relation: is_pass = False @@ -536,8 +524,14 @@ class PartOf(Facet): nest = self.get_nested_whole(nest) if not is_pass: reason = {"type": "ENTITY", "actual": ancestors} - elif self.relation == "IFCRELVOIDSELEMENT": - building_element = self.get_voided_element(inst) + elif self.relation == "IFCRELVOIDSELEMENT IFCRELFILLSELEMENT": + if inst.is_a("IfcOpeningElement"): + building_element = self.get_voided_element(inst) + else: + building_element = None + opening = self.get_filled_opening(inst) + if opening: + building_element = self.get_voided_element(opening) is_pass = building_element is not None if not is_pass: reason = {"type": "NOVALUE"} @@ -551,23 +545,8 @@ class PartOf(Facet): is_pass = True if not is_pass: reason = {"type": "ENTITY", "actual": building_element} - elif self.relation == "IFCRELFILLSELEMENT": - opening = self.filled_opening(inst) - is_pass = opening is not None - if not is_pass: - reason = {"type": "NOVALUE"} - if is_pass and self.name: - is_pass = False - if opening.is_a().upper() == self.name: - if self.predefinedType: - if ifcopenshell.util.element.get_predefined_type(opening) == self.predefinedType: - is_pass = True - else: - is_pass = True - if not is_pass: - reason = {"type": "ENTITY", "actual": opening} - if self.maxOccurs == 0: + if self.cardinality == "prohibited": return PartOfResult(not is_pass, {"type": "PROHIBITED"}) return PartOfResult(is_pass, reason) @@ -605,37 +584,35 @@ class Property(Facet): def __init__( self, propertySet="Property_Set", - name="PropertyName", + baseName="PropertyName", value=None, - datatype=None, + dataType=None, uri=None, - minOccurs=None, - maxOccurs="unbounded", + cardinality="required", instructions=None, ): self.parameters = [ "propertySet", - "name", + "baseName", "value", - "@datatype", + "@dataType", "@uri", - "@minOccurs", - "@maxOccurs", + "@cardinality", "@instructions", ] self.applicability_templates = [ - "Elements with {name} data of {value} in the dataset {propertySet}", - "Elements with {name} data in the dataset {propertySet}", + "Elements with {baseName} data of {value} in the dataset {propertySet}", + "Elements with {baseName} data in the dataset {propertySet}", ] self.requirement_templates = [ - "{name} data shall be {value} and in the dataset {propertySet}", - "{name} data shall be provided in the dataset {propertySet}", + "{baseName} data shall be {value} and in the dataset {propertySet}", + "{baseName} data shall be provided in the dataset {propertySet}", ] self.prohibited_templates = [ - "{name} data shall not be {value} and in the dataset {propertySet}", - "{name} data shall not be provided in the dataset {propertySet}", + "{baseName} data shall not be {value} and in the dataset {propertySet}", + "{baseName} data shall not be provided in the dataset {propertySet}", ] - super().__init__(propertySet, name, value, datatype, uri, minOccurs, maxOccurs, instructions) + super().__init__(propertySet, baseName, value, dataType, uri, cardinality, instructions) def filter( self, ifc_file: ifcopenshell.file, elements: Union[ifcopenshell.entity_instance, None] @@ -651,7 +628,7 @@ class Property(Facet): ) def __call__(self, inst, logger=None): - if self.minOccurs == 0 and self.maxOccurs != 0: + if self.cardinality == "optional": return PropertyResult(True) if isinstance(self.propertySet, str): @@ -671,18 +648,18 @@ class Property(Facet): props = {} for pset_name, pset_props in psets.items(): props[pset_name] = {} - if isinstance(self.name, str): - prop = pset_props.get(self.name) + if isinstance(self.baseName, str): + prop = pset_props.get(self.baseName) if prop == "UNKNOWN" and [ p for p in self.get_properties(inst.wrapped_data.file.by_id(pset_props["id"])) - if p.Name == self.name + if p.Name == self.baseName ][0].NominalValue.is_a("IfcLogical"): pass elif prop is not None and prop != "": - props[pset_name][self.name] = prop + props[pset_name][self.baseName] = prop else: - props[pset_name] = {k: v for k, v in pset_props.items() if k == self.name} + props[pset_name] = {k: v for k, v in pset_props.items() if k == self.baseName} if not bool(props[pset_name]): is_pass = False @@ -701,9 +678,9 @@ class Property(Facet): elif prop_entity.is_a("IfcPropertySingleValue"): data_type = prop_entity.NominalValue.is_a() - if data_type.lower() != self.datatype.lower(): + if self.dataType and data_type.lower() != self.dataType.lower(): is_pass = False - reason = {"type": "DATATYPE", "actual": data_type, "datatype": self.datatype} + reason = {"type": "DATATYPE", "actual": data_type, "dataType": self.dataType} break unit = ifcopenshell.util.unit.get_property_unit(prop_entity, inst.wrapped_data.file) @@ -720,9 +697,9 @@ class Property(Facet): prop_schema = prop_entity.wrapped_data.declaration().as_entity() data_type = prop_schema.attribute_by_index(3).type_of_attribute().declared_type().name() - if data_type.lower() != self.datatype.lower(): + if self.dataType and data_type.lower() != self.dataType.lower(): is_pass = False - reason = {"type": "DATATYPE", "actual": data_type, "datatype": self.datatype} + reason = {"type": "DATATYPE", "actual": data_type, "dataType": self.dataType} break unit = ifcopenshell.util.unit.get_property_unit(prop_entity, inst.wrapped_data.file) @@ -740,9 +717,9 @@ class Property(Facet): reason = {"type": "NOVALUE"} break data_type = prop_entity.EnumerationValues[0].is_a() - if data_type.lower() != self.datatype.lower(): + if self.dataType and data_type.lower() != self.dataType.lower(): is_pass = False - reason = {"type": "DATATYPE", "actual": data_type, "datatype": self.datatype} + reason = {"type": "DATATYPE", "actual": data_type, "dataType": self.dataType} break elif prop_entity.is_a("IfcPropertyListValue"): if not prop_entity.ListValues: @@ -750,9 +727,9 @@ class Property(Facet): reason = {"type": "NOVALUE"} break data_type = prop_entity.ListValues[0].is_a() - if data_type.lower() != self.datatype.lower(): + if self.dataType and data_type.lower() != self.dataType.lower(): is_pass = False - reason = {"type": "DATATYPE", "actual": data_type, "datatype": self.datatype} + reason = {"type": "DATATYPE", "actual": data_type, "dataType": self.dataType} break unit = ifcopenshell.util.unit.get_property_unit(prop_entity, inst.wrapped_data.file) if unit: @@ -773,9 +750,9 @@ class Property(Facet): if value is not None: data_type = value.is_a() values.append(value.wrappedValue) - if data_type.lower() != self.datatype.lower(): + if self.dataType and data_type.lower() != self.dataType.lower(): is_pass = False - reason = {"type": "DATATYPE", "actual": data_type, "datatype": self.datatype} + reason = {"type": "DATATYPE", "actual": data_type, "dataType": self.dataType} break unit = ifcopenshell.util.unit.get_property_unit(prop_entity, inst.wrapped_data.file) if unit: @@ -798,7 +775,7 @@ class Property(Facet): if not column_values: continue data_type = column_values[0].is_a() - if data_type.lower() == self.datatype.lower(): + if self.dataType and data_type.lower() == self.dataType.lower(): column_values = [v.wrappedValue for v in column_values] unit = units[f"{attribute}Unit"] if unit: @@ -815,7 +792,7 @@ class Property(Facet): values.extend(column_values) if not values: is_pass = False - reason = {"type": "DATATYPE", "actual": data_type, "datatype": self.datatype} + reason = {"type": "DATATYPE", "actual": data_type, "dataType": self.dataType} break props[pset_name][prop_entity.Name] = values else: @@ -868,7 +845,7 @@ class Property(Facet): reason = {"type": "VALUE", "actual": value} break - if self.maxOccurs == 0: + if self.cardinality == "prohibited": return PropertyResult(not is_pass, {"type": "PROHIBITED"}) return PropertyResult(is_pass, reason) @@ -888,8 +865,8 @@ class Property(Facet): class Material(Facet): - def __init__(self, value=None, uri=None, minOccurs=None, maxOccurs="unbounded", instructions=None): - self.parameters = ["value", "@uri", "@minOccurs", "@maxOccurs", "@instructions"] + def __init__(self, value=None, uri=None, cardinality="required", instructions=None): + self.parameters = ["value", "@uri", "@cardinality", "@instructions"] self.applicability_templates = [ "All data with a {value} material", "All data with a material", @@ -902,7 +879,7 @@ class Material(Facet): "Shall not have a material of {value}", "Shall not have a material", ] - super().__init__(value, uri, minOccurs, maxOccurs, instructions) + super().__init__(value, uri, cardinality, instructions) def filter( self, ifc_file: ifcopenshell.file, elements: Union[ifcopenshell.entity_instance, None] @@ -912,7 +889,7 @@ class Material(Facet): return ifc_file.by_type("IfcObjectDefinition") def __call__(self, inst, logger=None): - if self.minOccurs == 0 and self.maxOccurs != 0: + if self.cardinality == "optional": return MaterialResult(True) material = ifcopenshell.util.element.get_material(inst, should_skip_usage=True) @@ -963,7 +940,7 @@ class Material(Facet): if not is_pass: reason = {"type": "VALUE", "actual": values} - if self.maxOccurs == 0: + if self.cardinality == "prohibited": return MaterialResult(not is_pass, {"type": "PROHIBITED"}) return MaterialResult(is_pass, reason) @@ -1108,7 +1085,7 @@ class PropertyResult(Result): elif self.reason["type"] == "NOVALUE": return "The property set does not contain the required property" elif self.reason["type"] == "DATATYPE": - return f"The property's data type \"{str(self.reason['actual'])}\" does not match the required data type of \"{str(self.reason['datatype'])}\"" + return f"The property's data type \"{str(self.reason['actual'])}\" does not match the required data type of \"{str(self.reason['dataType'])}\"" elif self.reason["type"] == "VALUE": if isinstance(self.reason["actual"], list): if len(self.reason["actual"]) == 1: diff --git a/src/ifctester/ifctester/ids.py b/src/ifctester/ifctester/ids.py index b18b162e94..aaa74120df 100644 --- a/src/ifctester/ifctester/ids.py +++ b/src/ifctester/ifctester/ids.py @@ -89,7 +89,7 @@ class Ids: "@xmlns": "http://standards.buildingsmart.org/IDS", "@xmlns:xs": "http://www.w3.org/2001/XMLSchema", "@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance", - "@xsi:schemaLocation": "http://standards.buildingsmart.org/IDS http://standards.buildingsmart.org/IDS/0.9.6/ids.xsd", + "@xsi:schemaLocation": "http://standards.buildingsmart.org/IDS http://standards.buildingsmart.org/IDS/0.9.7/ids.xsd", "info": info, "specifications": {"specification": []}, } @@ -164,7 +164,7 @@ class Specification: "applicability": {}, "requirements": {}, } - for attribute in ["identifier", "description", "instructions", "minOccurs", "maxOccurs"]: + for attribute in ["identifier", "description", "instructions"]: value = getattr(self, attribute) if value is not None: results[f"@{attribute}"] = value @@ -181,20 +181,25 @@ class Specification: for facet_type in ("entity", "partOf", "classification", "attribute", "property", "material"): if facet_type in facets: results[clause_type][facet_type] = facets[facet_type] + if clause_type == "applicability": + for attribute in ["minOccurs", "maxOccurs"]: + value = getattr(self, attribute) + if value is not None: + results[clause_type][f"@{attribute}"] = value return results def parse(self, ids_dict): self.name = ids_dict.get("@name", "") self.description = ids_dict.get("@description", "") self.instructions = ids_dict.get("@instructions", "") - self.minOccurs = ids_dict["@minOccurs"] - self.maxOccurs = ids_dict["@maxOccurs"] + self.minOccurs = ids_dict.get("applicability", {}).get("@minOccurs", 0) + self.maxOccurs = ids_dict.get("applicability", {}).get("@minOccurs", "unbounded") self.ifcVersion = ids_dict["@ifcVersion"] self.applicability = ( - self.parse_clause(ids_dict["applicability"]) if ids_dict.get("applicability") is not None else [] + self.parse_clause(ids_dict["applicability"]) if ids_dict.get("applicability", None) is not None else [] ) self.requirements = ( - self.parse_clause(ids_dict["requirements"]) if ids_dict.get("requirements") is not None else [] + self.parse_clause(ids_dict["requirements"]) if ids_dict.get("requirements", None) is not None else [] ) return self @@ -243,21 +248,17 @@ class Specification: self.applicable_entities.append(element) for facet in self.requirements: result = facet(element) - if self.maxOccurs == 0: - prohibited = bool(result) - else: - prohibited = not bool(result) - if prohibited: + if not bool(result): self.failed_entities.add(element) facet.failed_entities.append(element) facet.failed_reasons.append(str(result)) for facet in self.requirements: - if facet.minOccurs != 0: + if facet.cardinality == "required": facet.status = not bool(facet.failed_entities) - elif facet.minOccurs == 0 and facet.maxOccurs != 0: + elif facet.cardinality == "optional": facet.status = True - elif facet.maxOccurs == 0: + elif facet.cardinality == "prohibited": facet.status = bool(facet.failed_entities) self.status = True diff --git a/src/ifctester/ifctester/ids.xsd b/src/ifctester/ifctester/ids.xsd index 0d2a595e72..62bb820697 100644 --- a/src/ifctester/ifctester/ids.xsd +++ b/src/ifctester/ifctester/ids.xsd @@ -1,5 +1,5 @@ - - + + @@ -40,20 +40,20 @@ - + - + - + @@ -62,26 +62,53 @@ - + + + - - + + + + the moniker 'baseName' is chosen to clarify that the data needs to reference the property name as stored in the IFC file, + which might differ from the multiple language-dependent presentations (e.g. 'FireRating' vs. 'Fire rating'). + + + + + + + Depending on the dataType attribute, values are expressed in the default unit documented at + https://github.com/buildingSMART/IDS/blob/master/Documentation/units.md, and unit conversion might be required. + + + - + - This is the name of an IFC Defined Type. See the full list for IFC 4 on https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/link/alphabeticalorder-defined-types.htm Documentation and default units on https://github.com/buildingSMART/IDS/blob/master/Documentation/units.md + This is the name of an IFC Defined Type, all uppercase. - - + + + + Depending on the IFC type of the attribute, values are expressed in the default unit documented at + https://github.com/buildingSMART/IDS/blob/master/Documentation/units.md, and unit conversion might be required. + + + @@ -98,6 +125,13 @@ + Author of the IDS can leave instructions for the authors of the IFC. This text could/should be displayed in the BIM/IFC authoring tool. @@ -111,7 +145,7 @@ - + Author of the IDS can leave instructions for the authors of the IFC. This text could/should be displayed in the BIM/IFC authoring tool. @@ -126,7 +160,7 @@ - + Author of the IDS can leave instructions for the authors of the IFC. This text could/should be displayed in the BIM/IFC authoring tool. @@ -140,6 +174,7 @@ + Author of the IDS can leave instructions for the authors of the IFC. This text could/should be displayed in the BIM/IFC authoring tool. @@ -154,7 +189,7 @@ - + Author of the IDS can leave instructions for the authors of the IFC. This text could/should be displayed in the BIM/IFC authoring tool. @@ -164,12 +199,12 @@ - + - + Author of the IDS can leave instructions for the authors of the IFC. This text could/should be displayed in the BIM/IFC authoring tool. @@ -195,7 +230,6 @@ - @@ -210,7 +244,7 @@ - + Author of the IDS can provide an identifier to the specification. This is intended to be a machine readable identifier. Beware: because of the possibility to combine different 'specification' elements from several ids files this cannot be enforced/assumed as (global) unique. @@ -229,12 +263,30 @@ - - - - - - + + + + + - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + diff --git a/src/ifctester/test/test_facet.py b/src/ifctester/test/test_facet.py index 7d9307dba1..61e3a05817 100644 --- a/src/ifctester/test/test_facet.py +++ b/src/ifctester/test/test_facet.py @@ -37,9 +37,9 @@ def run(name, *, facet, inst, expected): class TestEntity: def test_creating_an_entity_facet(self): facet = Entity(name="IfcName") - assert facet.asdict() == {"name": {"simpleValue": "IfcName"}} + assert facet.asdict("applicability") == {"name": {"simpleValue": "IfcName"}} facet = Entity(name="IfcName", predefinedType="predefinedType", instructions="instructions") - assert facet.asdict() == { + assert facet.asdict("requirement") == { "name": {"simpleValue": "IfcName"}, "predefinedType": {"simpleValue": "predefinedType"}, "@instructions": "instructions", @@ -223,17 +223,16 @@ class TestEntity: class TestAttribute: def test_creating_an_attribute_facet(self): attribute = Attribute(name="name") - assert attribute.asdict() == {"name": {"simpleValue": "name"}} + assert attribute.asdict("applicability") == {"name": {"simpleValue": "name"}} attribute = Attribute(name="name", value="value") - assert attribute.asdict() == {"name": {"simpleValue": "name"}, "value": {"simpleValue": "value"}} + assert attribute.asdict("applicability") == {"name": {"simpleValue": "name"}, "value": {"simpleValue": "value"}} attribute = Attribute( - name="name", value="value", minOccurs="0", maxOccurs="unbounded", instructions="instructions" + name="name", value="value", cardinality="required", instructions="instructions" ) - assert attribute.asdict() == { + assert attribute.asdict("requirement") == { "name": {"simpleValue": "name"}, "value": {"simpleValue": "value"}, - "@minOccurs": "0", - "@maxOccurs": "unbounded", + "@cardinality": "required", "@instructions": "instructions", } @@ -258,12 +257,12 @@ class TestAttribute: element = ifc.createIfcWall(Name="Foobar") run("A required facet checks all parameters as normal", facet=facet, inst=element, expected=True) - # facet = Attribute(name="Name", minOccurs=0, maxOccurs=0) - # run("A prohibited facet returns the opposite of a required facet", facet=facet, inst=element, expected=False) - # facet = Attribute(name="Name", minOccurs=0) - # run("An optional facet always passes regardless of outcome 1/2", facet=facet, inst=element, expected=True) - # facet = Attribute(name="Rabbit", minOccurs=0) - # run("An optional facet always passes regardless of outcome 2/2", facet=facet, inst=element, expected=True) + facet = Attribute(name="Name", cardinality="prohibited") + run("A prohibited facet returns the opposite of a required facet", facet=facet, inst=element, expected=False) + facet = Attribute(name="Name", cardinality="optional") + run("An optional facet always passes regardless of outcome 1/2", facet=facet, inst=element, expected=True) + facet = Attribute(name="Rabbit", cardinality="optional") + run("An optional facet always passes regardless of outcome 2/2", facet=facet, inst=element, expected=True) ifc = ifcopenshell.file() facet = Attribute(name="Name") @@ -668,26 +667,22 @@ class TestAttribute: class TestClassification: def test_creating_a_classification_facet(self): - facet = Classification() - assert facet.asdict() == { - "@maxOccurs": "unbounded" - } + facet = Classification(system="system") + assert facet.asdict("requirement") == {"system": {"simpleValue": "system"}, "@cardinality": "required" } facet = Classification(value="value", system="system") - assert facet.asdict() == {"value": {"simpleValue": "value"}, "system": {"simpleValue": "system"}, "@maxOccurs": "unbounded" } + assert facet.asdict("requirement") == {"value": {"simpleValue": "value"}, "system": {"simpleValue": "system"}, "@cardinality": "required" } facet = Classification( value="value", system="system", uri="https://test.com", - minOccurs="0", - maxOccurs="unbounded", + cardinality="required", instructions="instructions", ) - assert facet.asdict() == { + assert facet.asdict("requirement") == { "value": {"simpleValue": "value"}, "system": {"simpleValue": "system"}, "@uri": "https://test.com", - "@minOccurs": "0", - "@maxOccurs": "unbounded", + "@cardinality": "required", "@instructions": "instructions", } @@ -729,29 +724,29 @@ class TestClassification: "classification.add_reference", ifc, product=material, reference=ref1, classification=system_a ) - facet = Classification() + facet = Classification(system="Foobar") run( - "A classification facet with no data matches any classification 1/2", + "A classification facet with no value matches any classification 1/2", facet=facet, inst=element0, expected=False, ) run( - "A classification facet with no data matches any classification 2/2", + "A classification facet with no value matches any classification 2/2", facet=facet, inst=element1, expected=True, ) run("A required facet checks all parameters as normal", facet=facet, inst=element1, expected=True) - facet = Classification(minOccurs=0, maxOccurs=0) + facet = Classification(system="Foobar", cardinality="prohibited") run("A prohibited facet returns the opposite of a required facet", facet=facet, inst=element1, expected=False) - facet = Classification(minOccurs=0) + facet = Classification(system="Foobar", cardinality="optional") run("An optional facet always passes regardless of outcome 1/2", facet=facet, inst=element0, expected=True) - facet = Classification(minOccurs=0) + facet = Classification(system="Foobar", cardinality="optional") run("An optional facet always passes regardless of outcome 2/2", facet=facet, inst=element1, expected=True) - facet = Classification(value="1") + facet = Classification(system="Foobar", value="1") run( "Values should match exactly if lightweight classifications are used", facet=facet, @@ -759,7 +754,7 @@ class TestClassification: expected=True, ) - facet = Classification(value="2") + facet = Classification(system="Foobar", value="2") run( "Values match subreferences if full classifications are used (e.g. EF_25_10 should match EF_25_10_25, EF_25_10_30, etc)", facet=facet, @@ -767,7 +762,7 @@ class TestClassification: expected=True, ) - facet = Classification(value="1") + facet = Classification(system="Foobar", value="1") run( "Non-rooted resources that have external classification references should also pass", facet=facet, @@ -783,7 +778,7 @@ class TestClassification: run("Systems should match exactly 5/5", facet=facet, inst=element22, expected=True) restriction = Restriction(options={"pattern": "1.*"}) - facet = Classification(value=restriction) + facet = Classification(system="Foobar", value=restriction) run("Restrictions can be used for values 1/3", facet=facet, inst=element1, expected=True) run("Restrictions can be used for values 2/3", facet=facet, inst=element11, expected=True) run("Restrictions can be used for values 3/3", facet=facet, inst=element22, expected=False) @@ -824,40 +819,38 @@ class TestClassification: "classification.add_reference", ifc, product=wall_type, reference=refx, classification=system_b ) - facet = Classification(value="11") + facet = Classification(system="Foobar", value="11") run("Occurrences override the type classification per system 1/3", facet=facet, inst=wall, expected=True) - facet = Classification(value="22") + facet = Classification(system="Foobar", value="22") run("Occurrences override the type classification per system 2/3", facet=facet, inst=wall, expected=False) - facet = Classification(value="X") + facet = Classification(system="Foobaz", value="X") run("Occurrences override the type classification per system 3/3", facet=facet, inst=wall, expected=True) class TestProperty: def test_creating_a_property_facet(self): facet = Property() - assert facet.asdict() == { + assert facet.asdict("requirement") == { "propertySet": {"simpleValue": "Property_Set"}, - "name": {"simpleValue": "PropertyName"}, - "@maxOccurs": "unbounded" + "baseName": {"simpleValue": "PropertyName"}, + "@cardinality": "required" } facet = Property( propertySet="propertySet", - name="name", + baseName="baseName", value="value", - datatype="datatype", + dataType="dataType", uri="https://test.com", - minOccurs="0", - maxOccurs="unbounded", + cardinality="required", instructions="instructions", ) - assert facet.asdict() == { + assert facet.asdict("requirement") == { "propertySet": {"simpleValue": "propertySet"}, - "name": {"simpleValue": "name"}, + "baseName": {"simpleValue": "baseName"}, "value": {"simpleValue": "value"}, - "@datatype": "datatype", + "@dataType": "DATATYPE", "@uri": "https://test.com", - "@minOccurs": "0", - "@maxOccurs": "unbounded", + "@cardinality": "required", "@instructions": "instructions", } @@ -866,7 +859,7 @@ class TestProperty: ifc = self.setup_ifc() - facet = Property(propertySet="Foo_Bar", name="Foo", datatype="IFCLABEL") + facet = Property(propertySet="Foo_Bar", baseName="Foo", dataType="IFCLABEL") element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall") run("Elements with no properties always fail", facet=facet, inst=element, expected=False) pset = ifcopenshell.api.run("pset.add_pset", ifc, product=element, name="Foo_Bar") @@ -878,27 +871,27 @@ class TestProperty: run("A name check will match any property with any string value", facet=facet, inst=element, expected=True) ifc = self.setup_ifc() - facet = Property(propertySet="Foo_Bar", name="Foo", datatype="IFCLABEL") + facet = Property(propertySet="Foo_Bar", baseName="Foo", dataType="IFCLABEL") element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall") pset = ifcopenshell.api.run("pset.add_pset", ifc, product=element, name="Foo_Bar") ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foo": "Bar"}) run("A required facet checks all parameters as normal", facet=facet, inst=element, expected=True) - facet = Property(propertySet="Foo_Bar", name="Foo", datatype="IFCLABEL", minOccurs=0, maxOccurs=0) + facet = Property(propertySet="Foo_Bar", baseName="Foo", dataType="IFCLABEL", cardinality="prohibited") run("A prohibited facet returns the opposite of a required facet", facet=facet, inst=element, expected=False) - facet = Property(propertySet="Foo_Bar", name="Foo", datatype="IFCLABEL", minOccurs=0) + facet = Property(propertySet="Foo_Bar", baseName="Foo", dataType="IFCLABEL", cardinality="optional") run("An optional facet always passes regardless of outcome 1/2", facet=facet, inst=element, expected=True) - facet = Property(propertySet="Foo_Bar", name="Bar", datatype="IFCLABEL", minOccurs=0) + facet = Property(propertySet="Foo_Bar", baseName="Bar", dataType="IFCLABEL", cardinality="optional") run("An optional facet always passes regardless of outcome 2/2", facet=facet, inst=element, expected=True) ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foo": ""}) - facet = Property(propertySet="Foo_Bar", name="Foo", datatype="IFCLOGICAL") + facet = Property(propertySet="Foo_Bar", baseName="Foo", dataType="IFCLOGICAL") run("An empty string is considered falsey and will not pass", facet=facet, inst=element, expected=False) ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foo": ifc.createIfcLogical("UNKNOWN")}) - facet = Property(propertySet="Foo_Bar", name="Foo", datatype="IFCDURATION") + facet = Property(propertySet="Foo_Bar", baseName="Foo", dataType="IFCDURATION") run("A logical unknown is considered falsey and will not pass", facet=facet, inst=element, expected=False) ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foo": ifc.createIfcDuration("P0D")}) run("A zero duration will pass", facet=facet, inst=element, expected=True) - facet = Property(propertySet="Foo_Bar", name="Foo", datatype="IFCBOOLEAN") + facet = Property(propertySet="Foo_Bar", baseName="Foo", dataType="IFCBOOLEAN") ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foo": ifc.createIfcBoolean(True)}) run("A property set to true will pass a name check", facet=facet, inst=element, expected=True) ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foo": False}) @@ -910,7 +903,7 @@ class TestProperty: ) ifc = self.setup_ifc() - facet = Property(propertySet="Foo_Bar", name="Foo", value="Bar", datatype="IFCLABEL") + facet = Property(propertySet="Foo_Bar", baseName="Foo", value="Bar", dataType="IFCLABEL") element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall") pset = ifcopenshell.api.run("pset.add_pset", ifc, product=element, name="Foo_Bar") ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foo": "Bar"}) @@ -920,55 +913,55 @@ class TestProperty: ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foo": "Baz"}) run("Specifying a value fails against different values", facet=facet, inst=element, expected=False) - facet = Property(propertySet="Foo_Bar", name="Foo", value="♫Don'tÄrgerhôtelЊет", datatype="IFCLABEL") + facet = Property(propertySet="Foo_Bar", baseName="Foo", value="♫Don'tÄrgerhôtelЊет", dataType="IFCLABEL") ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foo": "♫Don'tÄrgerhôtelЊет"}) run("Non-ascii characters are treated without encoding", facet=facet, inst=element, expected=True) identifier = "123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345" facet = Property( - propertySet="Foo_Bar", name="Foo", value=identifier + "_extra_characters", datatype="IFCIDENTIFIER" + propertySet="Foo_Bar", baseName="Foo", value=identifier + "_extra_characters", dataType="IFCIDENTIFIER" ) ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foo": ifc.createIfcIdentifier(identifier)}) run("IDS does not handle string truncation such as for identifiers", facet=facet, inst=element, expected=False) ifc = self.setup_ifc() - facet = Property(propertySet="Foo_Bar", name="Foo", value="1", datatype="IFCLABEL") + facet = Property(propertySet="Foo_Bar", baseName="Foo", value="1", dataType="IFCLABEL") element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall") pset = ifcopenshell.api.run("pset.add_pset", ifc, product=element, name="Foo_Bar") ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foo": "1"}) run("A number specified as a string is treated as a string", facet=facet, inst=element, expected=True) - facet = Property(propertySet="Foo_Bar", name="Foo", value="42", datatype="IFCINTEGER") + facet = Property(propertySet="Foo_Bar", baseName="Foo", value="42", dataType="IFCINTEGER") ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foo": ifc.createIfcInteger(42)}) run("Integer values are checked using type casting 1/4", facet=facet, inst=element, expected=True) - facet = Property(propertySet="Foo_Bar", name="Foo", value="42.", datatype="IFCINTEGER") + facet = Property(propertySet="Foo_Bar", baseName="Foo", value="42.", dataType="IFCINTEGER") run("Integer values are checked using type casting 2/4", facet=facet, inst=element, expected=True) - facet = Property(propertySet="Foo_Bar", name="Foo", value="42.0", datatype="IFCINTEGER") + facet = Property(propertySet="Foo_Bar", baseName="Foo", value="42.0", dataType="IFCINTEGER") run("Integer values are checked using type casting 3/4", facet=facet, inst=element, expected=True) - facet = Property(propertySet="Foo_Bar", name="Foo", value="42.3", datatype="IFCINTEGER") + facet = Property(propertySet="Foo_Bar", baseName="Foo", value="42.3", dataType="IFCINTEGER") run("Integer values are checked using type casting 4/4", facet=facet, inst=element, expected=False) - facet = Property(propertySet="Foo_Bar", name="Foo", value="42", datatype="IFCREAL") + facet = Property(propertySet="Foo_Bar", baseName="Foo", value="42", dataType="IFCREAL") ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foo": ifc.createIfcReal(42.0)}) run("Real values are checked using type casting 1/3", facet=facet, inst=element, expected=True) - facet = Property(propertySet="Foo_Bar", name="Foo", value="42.0", datatype="IFCREAL") + facet = Property(propertySet="Foo_Bar", baseName="Foo", value="42.0", dataType="IFCREAL") run("Real values are checked using type casting 2/3", facet=facet, inst=element, expected=True) - facet = Property(propertySet="Foo_Bar", name="Foo", value="42.3", datatype="IFCREAL") + facet = Property(propertySet="Foo_Bar", baseName="Foo", value="42.3", dataType="IFCREAL") ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foo": ifc.createIfcReal(42.3)}) run("Real values are checked using type casting 3/3", facet=facet, inst=element, expected=True) - facet = Property(propertySet="Foo_Bar", name="Foo", value="42,3", datatype="IFCREAL") + facet = Property(propertySet="Foo_Bar", baseName="Foo", value="42,3", dataType="IFCREAL") ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foo": ifc.createIfcReal(42.3)}) run("Only specifically formatted numbers are allowed 1/4", facet=facet, inst=element, expected=False) - facet = Property(propertySet="Foo_Bar", name="Foo", value="123,4.5", datatype="IFCREAL") + facet = Property(propertySet="Foo_Bar", baseName="Foo", value="123,4.5", dataType="IFCREAL") ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foo": ifc.createIfcReal(1234.5)}) run("Only specifically formatted numbers are allowed 2/4", facet=facet, inst=element, expected=False) - facet = Property(propertySet="Foo_Bar", name="Foo", value="1.2345e3", datatype="IFCREAL") + facet = Property(propertySet="Foo_Bar", baseName="Foo", value="1.2345e3", dataType="IFCREAL") run("Only specifically formatted numbers are allowed 3/4", facet=facet, inst=element, expected=True) - facet = Property(propertySet="Foo_Bar", name="Foo", value="1.2345E3", datatype="IFCREAL") + facet = Property(propertySet="Foo_Bar", baseName="Foo", value="1.2345E3", dataType="IFCREAL") run("Only specifically formatted numbers are allowed 4/4", facet=facet, inst=element, expected=True) - facet = Property(propertySet="Foo_Bar", name="Foo", value="42.", datatype="IFCREAL") + facet = Property(propertySet="Foo_Bar", baseName="Foo", value="42.", dataType="IFCREAL") ifcopenshell.api.run( "pset.edit_pset", ifc, pset=pset, properties={"Foo": ifc.createIfcReal(42.0 * (1.0 + 1e-6))} ) @@ -986,15 +979,15 @@ class TestProperty: ) run("Floating point numbers are compared with a 1e-6 tolerance 4/4", facet=facet, inst=element, expected=False) - facet = Property(propertySet="Foo_Bar", name="Foo", value="TRUE", datatype="IFCBOOLEAN") + facet = Property(propertySet="Foo_Bar", baseName="Foo", value="TRUE", dataType="IFCBOOLEAN") ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foo": ifc.createIfcBoolean(False)}) run("Booleans must be specified as uppercase strings 1/3", facet=facet, inst=element, expected=False) - facet = Property(propertySet="Foo_Bar", name="Foo", value="FALSE", datatype="IFCBOOLEAN") + facet = Property(propertySet="Foo_Bar", baseName="Foo", value="FALSE", dataType="IFCBOOLEAN") run("Booleans must be specified as uppercase strings 2/3", facet=facet, inst=element, expected=True) - facet = Property(propertySet="Foo_Bar", name="Foo", value="False", datatype="IFCBOOLEAN") + facet = Property(propertySet="Foo_Bar", baseName="Foo", value="False", dataType="IFCBOOLEAN") run("Booleans must be specified as uppercase strings 3/3", facet=facet, inst=element, expected=False) - facet = Property(propertySet="Foo_Bar", name="Foo", value="2022-01-01", datatype="IFCDATE") + facet = Property(propertySet="Foo_Bar", baseName="Foo", value="2022-01-01", dataType="IFCDATE") ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foo": ifc.createIfcDate("2022-01-01")}) run("Dates are treated as strings 1/2", facet=facet, inst=element, expected=True) ifcopenshell.api.run( @@ -1002,7 +995,7 @@ class TestProperty: ) run("Dates are treated as strings 2/2", facet=facet, inst=element, expected=False) - facet = Property(propertySet="Foo_Bar", name="Foo", value="PT16H", datatype="IFCDURATION") + facet = Property(propertySet="Foo_Bar", baseName="Foo", value="PT16H", dataType="IFCDURATION") ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foo": ifc.createIfcDuration("PT16H")}) run("Durations are treated as strings 1/2", facet=facet, inst=element, expected=True) ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foo": ifc.createIfcDuration("P2D")}) @@ -1019,11 +1012,11 @@ class TestProperty: properties={"Status": ["EXISTING", "DEMOLISH"]}, pset_template=pset_template, ) - facet = Property(propertySet="Pset_WallCommon", name="Status", value="EXISTING", datatype="IFCLABEL") + facet = Property(propertySet="Pset_WallCommon", baseName="Status", value="EXISTING", dataType="IFCLABEL") run("Any matching value in an enumerated property will pass 1/3", facet=facet, inst=element, expected=True) - facet = Property(propertySet="Pset_WallCommon", name="Status", value="DEMOLISH", datatype="IFCLABEL") + facet = Property(propertySet="Pset_WallCommon", baseName="Status", value="DEMOLISH", dataType="IFCLABEL") run("Any matching value in an enumerated property will pass 2/3", facet=facet, inst=element, expected=True) - facet = Property(propertySet="Pset_WallCommon", name="Status", value="NEW", datatype="IFCLABEL") + facet = Property(propertySet="Pset_WallCommon", baseName="Status", value="NEW", dataType="IFCLABEL") run("Any matching value in an enumerated property will pass 3/3", facet=facet, inst=element, expected=False) ifc = self.setup_ifc() @@ -1033,11 +1026,11 @@ class TestProperty: Name="Foo", ListValues=[ifc.createIfcLabel("X"), ifc.createIfcLabel("Y")] ) pset.HasProperties = [list_property] - facet = Property(propertySet="Foo_Bar", name="Foo", value="X", datatype="IFCLABEL") + facet = Property(propertySet="Foo_Bar", baseName="Foo", value="X", dataType="IFCLABEL") run("Any matching value in a list property will pass 1/3", facet=facet, inst=element, expected=True) - facet = Property(propertySet="Foo_Bar", name="Foo", value="Y", datatype="IFCLABEL") + facet = Property(propertySet="Foo_Bar", baseName="Foo", value="Y", dataType="IFCLABEL") run("Any matching value in a list property will pass 2/3", facet=facet, inst=element, expected=True) - facet = Property(propertySet="Foo_Bar", name="Foo", value="Z", datatype="IFCLABEL") + facet = Property(propertySet="Foo_Bar", baseName="Foo", value="Z", dataType="IFCLABEL") run("Any matching value in a list property will pass 3/3", facet=facet, inst=element, expected=False) ifc = self.setup_ifc() @@ -1050,13 +1043,13 @@ class TestProperty: SetPointValue=ifc.createIfcLengthMeasure(3000), ) pset.HasProperties = [bounded_property] - facet = Property(propertySet="Foo_Bar", name="Foo", value="1", datatype="IFCLENGTHMEASURE") + facet = Property(propertySet="Foo_Bar", baseName="Foo", value="1", dataType="IFCLENGTHMEASURE") run("Any matching value in a bounded property will pass 1/4", facet=facet, inst=element, expected=True) - facet = Property(propertySet="Foo_Bar", name="Foo", value="5", datatype="IFCLENGTHMEASURE") + facet = Property(propertySet="Foo_Bar", baseName="Foo", value="5", dataType="IFCLENGTHMEASURE") run("Any matching value in a bounded property will pass 2/4", facet=facet, inst=element, expected=True) - facet = Property(propertySet="Foo_Bar", name="Foo", value="3", datatype="IFCLENGTHMEASURE") + facet = Property(propertySet="Foo_Bar", baseName="Foo", value="3", dataType="IFCLENGTHMEASURE") run("Any matching value in a bounded property will pass 3/4", facet=facet, inst=element, expected=True) - facet = Property(propertySet="Foo_Bar", name="Foo", value="2", datatype="IFCLENGTHMEASURE") + facet = Property(propertySet="Foo_Bar", baseName="Foo", value="2", dataType="IFCLENGTHMEASURE") run("Any matching value in a bounded property will pass 4/4", facet=facet, inst=element, expected=False) ifc = self.setup_ifc() @@ -1066,18 +1059,18 @@ class TestProperty: Name="Foo", DefiningValues=[ifc.createIfcLabel("X")], DefinedValues=[ifc.createIfcLengthMeasure(1000)] ) pset.HasProperties = [table_property] - facet = Property(propertySet="Foo_Bar", name="Foo", value="X", datatype="IFCLABEL") + facet = Property(propertySet="Foo_Bar", baseName="Foo", value="X", dataType="IFCLABEL") run("Any matching value in a table property will pass 1/3", facet=facet, inst=element, expected=True) - facet = Property(propertySet="Foo_Bar", name="Foo", value="1", datatype="IFCLENGTHMEASURE") + facet = Property(propertySet="Foo_Bar", baseName="Foo", value="1", dataType="IFCLENGTHMEASURE") run("Any matching value in a table property will pass 2/3", facet=facet, inst=element, expected=True) - facet = Property(propertySet="Foo_Bar", name="Foo", value="Y", datatype="IFCLABEL") + facet = Property(propertySet="Foo_Bar", baseName="Foo", value="Y", dataType="IFCLABEL") run("Any matching value in a table property will pass 3/3", facet=facet, inst=element, expected=False) ifc = self.setup_ifc() element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall") pset = ifcopenshell.api.run("pset.add_pset", ifc, product=element, name="Foo_Bar") pset.HasProperties = [ifc.createIfcPropertyReferenceValue(Name="Foo")] - facet = Property(propertySet="Foo_Bar", name="Foo", datatype="IFCLABEL") + facet = Property(propertySet="Foo_Bar", baseName="Foo", dataType="IFCLABEL") run("Reference properties are treated as objects and not supported", facet=facet, inst=element, expected=False) ifc = self.setup_ifc() @@ -1096,21 +1089,21 @@ class TestProperty: RelatingPropertyDefinition=pset, ) facet = Property( - propertySet="Foo_Bar", name="PanelOperation", value="SWINGING", datatype="IFCDOORPANELOPERATIONENUM" + propertySet="Foo_Bar", baseName="PanelOperation", value="SWINGING", dataType="IFCDOORPANELOPERATIONENUM" ) run("Predefined properties are supported but discouraged 1/2", facet=facet, inst=element, expected=True) facet = Property( - propertySet="Foo_Bar", name="PanelOperation", value="SWONGING", datatype="IFCDOORPANELOPERATIONENUM" + propertySet="Foo_Bar", baseName="PanelOperation", value="SWONGING", dataType="IFCDOORPANELOPERATIONENUM" ) run("Predefined properties are supported but discouraged 2/2", facet=facet, inst=element, expected=False) ifc = self.setup_ifc() - facet = Property(propertySet="Foo_Bar", name="Foo", datatype="IFCLENGTHMEASURE") + facet = Property(propertySet="Foo_Bar", baseName="Foo", dataType="IFCLENGTHMEASURE") element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall") qto = ifcopenshell.api.run("pset.add_qto", ifc, product=element, name="Foo_Bar") ifcopenshell.api.run("pset.edit_qto", ifc, qto=qto, properties={"Foo": ifc.createIfcLengthMeasure(42)}) run("A name check will match any quantity with any value", facet=facet, inst=element, expected=True) - facet = Property(propertySet="Foo_Bar", name="Foo", datatype="IFCAREAMEASURE") + facet = Property(propertySet="Foo_Bar", baseName="Foo", dataType="IFCAREAMEASURE") run("Quantities must also match the appropriate measure", facet=facet, inst=element, expected=False) ifc = self.setup_ifc() @@ -1119,9 +1112,9 @@ class TestProperty: complex_property = ifc.createIfcComplexProperty(Name="Foo", UsageName="RabbitAgilityTraining") ifcopenshell.api.run("pset.edit_pset", ifc, pset=complex_property, properties={"Rabbits": "Awesome"}) pset.HasProperties = [complex_property] - facet = Property(propertySet="Foo_Bar", name="Foo", datatype="IFCLABEL") + facet = Property(propertySet="Foo_Bar", baseName="Foo", dataType="IFCLABEL") run("Complex properties are not supported 1/2", facet=facet, inst=element, expected=False) - facet = Property(propertySet="Foo", name="Rabbits", datatype="IFCLABEL") + facet = Property(propertySet="Foo", baseName="Rabbits", dataType="IFCLABEL") run("Complex properties are not supported 2/2", facet=facet, inst=element, expected=False) ifc = self.setup_ifc() @@ -1132,14 +1125,14 @@ class TestProperty: "pset.edit_qto", ifc, qto=complex_quantity, properties={"MyLength": ifc.createIfcLengthMeasure(42)} ) qto.Quantities = [complex_quantity] - facet = Property(propertySet="Foo_Bar", name="Foo", datatype="IFCLENGTHMEASURE") + facet = Property(propertySet="Foo_Bar", baseName="Foo", dataType="IFCLENGTHMEASURE") run("Complex properties are not supported 1/2", facet=facet, inst=element, expected=False) - facet = Property(propertySet="Foo", name="MyLength", datatype="IFCLENGTHMEASURE") + facet = Property(propertySet="Foo", baseName="MyLength", dataType="IFCLENGTHMEASURE") run("Complex properties are not supported 2/2", facet=facet, inst=element, expected=False) ifc = self.setup_ifc() restriction = Restriction(options={"pattern": "Foo_.*"}) - facet = Property(propertySet=restriction, name="Foo", datatype="IFCLABEL") + facet = Property(propertySet=restriction, baseName="Foo", dataType="IFCLABEL") element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall") pset = ifcopenshell.api.run("pset.add_pset", ifc, product=element, name="Foo_Bar") ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foo": "Bar"}) @@ -1152,7 +1145,7 @@ class TestProperty: ifc = self.setup_ifc() restriction = Restriction(options={"pattern": "Foo.*"}) - facet = Property(propertySet="Foo_Bar", name=restriction, value="x", datatype="IFCLABEL") + facet = Property(propertySet="Foo_Bar", baseName=restriction, value="x", dataType="IFCLABEL") element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall") pset = ifcopenshell.api.run("pset.add_pset", ifc, product=element, name="Foo_Bar") ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foobar": "x"}) @@ -1165,7 +1158,7 @@ class TestProperty: ifc = self.setup_ifc() restriction1 = Restriction(options={"pattern": "Foo.*"}) restriction2 = Restriction(options={"enumeration": ["x", "y"]}) - facet = Property(propertySet="Foo_Bar", name=restriction1, value=restriction2, datatype="IFCLABEL") + facet = Property(propertySet="Foo_Bar", baseName=restriction1, value=restriction2, dataType="IFCLABEL") element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall") pset = ifcopenshell.api.run("pset.add_pset", ifc, product=element, name="Foo_Bar") ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foobar": "x", "Foobaz": "y"}) @@ -1184,7 +1177,7 @@ class TestProperty: ) ifc = self.setup_ifc() - facet = Property(propertySet="Foo_Bar", name="Foo", value="2", datatype="IFCTIMEMEASURE") + facet = Property(propertySet="Foo_Bar", baseName="Foo", value="2", dataType="IFCTIMEMEASURE") element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall") pset = ifcopenshell.api.run("pset.add_pset", ifc, product=element, name="Foo_Bar") ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foo": ifc.createIfcMassMeasure(2)}) @@ -1193,7 +1186,7 @@ class TestProperty: run("Measures are used to specify an IFC data type 2/2", facet=facet, inst=element, expected=True) ifc = self.setup_ifc() - facet = Property(propertySet="Foo_Bar", name="Foo", value="2", datatype="IFCLENGTHMEASURE") + facet = Property(propertySet="Foo_Bar", baseName="Foo", value="2", dataType="IFCLENGTHMEASURE") element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall") pset = ifcopenshell.api.run("pset.add_pset", ifc, product=element, name="Foo_Bar") ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foo": ifc.createIfcLengthMeasure(2)}) @@ -1217,7 +1210,7 @@ class TestProperty: ifcopenshell.api.run("type.assign_type", ifc, related_object=wall, relating_type=wall_type) pset = ifcopenshell.api.run("pset.add_pset", ifc, product=wall_type, name="Foo_Bar") ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foo": "Bar"}) - facet = Property(propertySet="Foo_Bar", name="Foo", datatype="IFCLABEL") + facet = Property(propertySet="Foo_Bar", baseName="Foo", dataType="IFCLABEL") run("Properties can be inherited from the type 1/2", facet=facet, inst=wall, expected=True) run("Properties can be inherited from the type 2/2", facet=facet, inst=wall_type, expected=True) @@ -1229,7 +1222,7 @@ class TestProperty: ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foo": "Baz"}) pset = ifcopenshell.api.run("pset.add_pset", ifc, product=wall, name="Foo_Bar") ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foo": "Bar"}) - facet = Property(propertySet="Foo_Bar", name="Foo", value="Bar", datatype="IFCLABEL") + facet = Property(propertySet="Foo_Bar", baseName="Foo", value="Bar", dataType="IFCLABEL") run("Properties can be overriden by an occurrence 1/2", facet=facet, inst=wall, expected=True) run("Properties can be overriden by an occurrence 2/2", facet=facet, inst=wall_type, expected=False) @@ -1248,15 +1241,14 @@ class TestProperty: class TestMaterial: def test_creating_a_material_facet(self): facet = Material() - assert facet.asdict() == {"@maxOccurs": "unbounded"} + assert facet.asdict("requirement") == {"@cardinality": "required"} facet = Material( - value="value", uri="https://test.com", minOccurs="0", maxOccurs="unbounded", instructions="instructions" + value="value", uri="https://test.com", cardinality="required", instructions="instructions" ) - assert facet.asdict() == { + assert facet.asdict("requirement") == { "value": {"simpleValue": "value"}, "@uri": "https://test.com", - "@minOccurs": "0", - "@maxOccurs": "unbounded", + "@cardinality": "required", "@instructions": "instructions", } @@ -1272,11 +1264,11 @@ class TestMaterial: run("Elements with any material will pass an empty material facet", facet=facet, inst=element, expected=True) run("A required facet checks all parameters as normal", facet=facet, inst=element, expected=True) - facet = Material(minOccurs=0, maxOccurs=0) + facet = Material(cardinality="prohibited") run("A prohibited facet returns the opposite of a required facet", facet=facet, inst=element, expected=False) - facet = Material(minOccurs=0) + facet = Material(cardinality="optional") run("An optional facet always passes regardless of outcome 1/2", facet=facet, inst=element, expected=True) - facet = Material(value="Foo", minOccurs=0) + facet = Material(value="Foo", cardinality="optional") run("An optional facet always passes regardless of outcome 1/2", facet=facet, inst=element, expected=True) ifc = ifcopenshell.file() @@ -1406,23 +1398,21 @@ class TestMaterial: class TestPartOf: def test_creating_a_partof_facet(self): facet = PartOf() - assert facet.asdict() == {"entity": {"name": {"simpleValue": "IFCWALL"}}, "@maxOccurs": "unbounded" } + assert facet.asdict("requirement") == {"entity": {"name": {"simpleValue": "IFCWALL"}}, "@cardinality": "required" } facet = PartOf( name="IFCGROUP", predefinedType="predefinedType", relation="IFCRELASSIGNSTOGROUP", - minOccurs="0", - maxOccurs="unbounded", + cardinality="required", instructions="instructions", ) - assert facet.asdict() == { + assert facet.asdict("requirement") == { "entity": { "name": {"simpleValue": "IFCGROUP"}, "predefinedType": {"simpleValue": "predefinedType"}, }, "@relation": "IFCRELASSIGNSTOGROUP", - "@minOccurs": "0", - "@maxOccurs": "unbounded", + "@cardinality": "required", "@instructions": "instructions", } @@ -1440,11 +1430,8 @@ class TestPartOf: run("The aggregated part passes an aggregate relationship", facet=facet, inst=subelement, expected=True) run("A required facet checks all parameters as normal", facet=facet, inst=subelement, expected=True) - facet = PartOf(name="IFCELEMENTASSEMBLY", relation="IFCRELAGGREGATES", minOccurs=0, maxOccurs=0) + facet = PartOf(name="IFCELEMENTASSEMBLY", relation="IFCRELAGGREGATES", cardinality="prohibited") run("A prohibited facet returns the opposite of a required facet", facet=facet, inst=subelement, expected=False) - facet = PartOf(name="IFCELEMENTASSEMBLY", relation="IFCRELAGGREGATES", minOccurs=0) - run("An optional facet always passes regardless of outcome 1/2", facet=facet, inst=element, expected=True) - run("An optional facet always passes regardless of outcome 2/2", facet=facet, inst=subelement, expected=True) ifc = ifcopenshell.file() element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcSlab") diff --git a/src/ifctester/test/test_ids.py b/src/ifctester/test/test_ids.py index ea2b63dd55..aea997d014 100644 --- a/src/ifctester/test/test_ids.py +++ b/src/ifctester/test/test_ids.py @@ -48,12 +48,11 @@ class TestIds: def test_create_an_ids_with_minimal_information(self): specs = ids.Ids() - print('AAA', specs.asdict()) assert specs.asdict() == { "@xmlns": "http://standards.buildingsmart.org/IDS", "@xmlns:xs": "http://www.w3.org/2001/XMLSchema", "@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance", - "@xsi:schemaLocation": "http://standards.buildingsmart.org/IDS http://standards.buildingsmart.org/IDS/0.9.6/ids.xsd", + "@xsi:schemaLocation": "http://standards.buildingsmart.org/IDS http://standards.buildingsmart.org/IDS/0.9.7/ids.xsd", "info": {"title": "Untitled"}, "specifications": {"specification": []}, } @@ -73,7 +72,7 @@ class TestIds: "@xmlns": "http://standards.buildingsmart.org/IDS", "@xmlns:xs": "http://www.w3.org/2001/XMLSchema", "@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance", - "@xsi:schemaLocation": "http://standards.buildingsmart.org/IDS http://standards.buildingsmart.org/IDS/0.9.6/ids.xsd", + "@xsi:schemaLocation": "http://standards.buildingsmart.org/IDS http://standards.buildingsmart.org/IDS/0.9.7/ids.xsd", "info": { "title": "title", "copyright": "copyright", @@ -93,7 +92,7 @@ class TestIds: "@xmlns": "http://standards.buildingsmart.org/IDS", "@xmlns:xs": "http://www.w3.org/2001/XMLSchema", "@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance", - "@xsi:schemaLocation": "http://standards.buildingsmart.org/IDS http://standards.buildingsmart.org/IDS/0.9.6/ids.xsd", + "@xsi:schemaLocation": "http://standards.buildingsmart.org/IDS http://standards.buildingsmart.org/IDS/0.9.7/ids.xsd", "info": {"title": "Untitled"}, "specifications": {"specification": []}, } @@ -117,7 +116,7 @@ class TestIds: specs = ids.Ids(title="Title") spec = ids.Specification(name="Name") spec.applicability.append(ids.Entity(name="IFCWALL")) - spec.requirements.append(name_attr := ids.Attribute(name="Name", value="Waldo")) + spec.requirements.append(ids.Attribute(name="Name", value="Waldo")) specs.specifications.append(spec) assert "http://standards.buildingsmart.org/IDS" in specs.to_string() assert spec.status == None @@ -233,12 +232,9 @@ class TestIds: class TestSpecification: def test_create_specification_with_minimal_information(self): spec = ids.Specification() - print(spec.asdict()) assert spec.asdict() == { "@name": "Unnamed", "@ifcVersion": ["IFC2X3", "IFC4"], - "@minOccurs": 0, - "@maxOccurs": "unbounded", "applicability": {}, "requirements": {}, } @@ -255,8 +251,6 @@ class TestSpecification: ) assert spec.asdict() == { "@name": "name", - "@minOccurs": 1, - "@maxOccurs": 1, "@ifcVersion": "IFC4", "@identifier": "identifier", "@description": "description",