Fix #4465. Update IfcTester to IDS 0.9.7

This commit is contained in:
Dion Moult
2024-03-28 22:47:48 +11:00
parent 12a1f8b5f0
commit ed8eb7543b
6 changed files with 281 additions and 271 deletions
+3 -4
View File
@@ -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)
+69 -92
View File
@@ -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:
+15 -14
View File
@@ -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
+78 -26
View File
@@ -1,5 +1,5 @@
<!-- June 20, 2023 - DRAFT -->
<xs:schema xmlns:ids="http://standards.buildingsmart.org/IDS" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:altova="http://www.altova.com/xml-schema-extensions" targetNamespace="http://standards.buildingsmart.org/IDS" elementFormDefault="qualified" attributeFormDefault="unqualified" version="0.9.6">
<!-- Draft - Do not use in production -->
<xs:schema xmlns:ids="http://standards.buildingsmart.org/IDS" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:altova="http://www.altova.com/xml-schema-extensions" targetNamespace="http://standards.buildingsmart.org/IDS" elementFormDefault="qualified" attributeFormDefault="unqualified" version="0.9.7">
<xs:import namespace="http://www.w3.org/XML/1998/namespace" schemaLocation="http://www.w3.org/2001/xml.xsd"/>
<xs:import namespace="http://www.w3.org/2001/XMLSchema" schemaLocation="http://www.w3.org/2001/XMLSchema.xsd"/>
<xs:import namespace="http://www.w3.org/2001/XMLSchema-instance" schemaLocation="http://www.w3.org/2001/XMLSchema-instance"/>
@@ -40,20 +40,20 @@
<xs:choice minOccurs="1">
<!-- place for potential additional rules for idsValue -->
<xs:element name="simpleValue" type="xs:string" minOccurs="1" maxOccurs="1"/>
<xs:element ref="xs:restriction" minOccurs="1" maxOccurs="unbounded"/>
<xs:element ref="xs:restriction" minOccurs="1" maxOccurs="1"/>
</xs:choice>
</xs:complexType>
<xs:complexType name="classificationType">
<xs:sequence>
<xs:element name="value" type="ids:idsValue" minOccurs="0"/>
<xs:element name="system" type="ids:idsValue" minOccurs="0"/>
<xs:element name="system" type="ids:idsValue" minOccurs="1"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="partOfType">
<xs:sequence>
<xs:element name="entity" type="ids:entityType" minOccurs="1"/>
</xs:sequence>
<xs:attribute name="relation" type="ids:relations"/>
<xs:attribute name="relation" type="ids:relations" use="optional" />
</xs:complexType>
<xs:complexType name="applicabilityType">
<xs:sequence>
@@ -62,26 +62,53 @@
<xs:element name="classification" type="ids:classificationType" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="attribute" type="ids:attributeType" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="property" type="ids:propertyType" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="material" type="ids:materialType" minOccurs="0"/>
<xs:element name="material" type="ids:materialType" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
<!-- Please note there is an implementation agreement on the use of minOccurs and maxOccurs.
Valid values are:
a. 0 to unbounded, meaning Optional
b. 1 to unbounded, meaning Required
c. 0 to 0, meaning Prohibited
-->
<xs:attributeGroup ref="xs:occurs"/>
</xs:complexType>
<xs:complexType name="propertyType">
<xs:sequence>
<xs:element name="propertySet" type="ids:idsValue"/>
<xs:element name="name" type="ids:idsValue"/>
<xs:element name="value" type="ids:idsValue" minOccurs="0"/>
<xs:element name="baseName" type="ids:idsValue">
<xs:annotation>
<xs:documentation>
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').
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="value" type="ids:idsValue" minOccurs="0">
<xs:annotation>
<xs:documentation>
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.
</xs:documentation>
</xs:annotation>
</xs:element>
</xs:sequence>
<xs:attribute name="datatype" use="required">
<xs:attribute name="dataType" type="ids:upperCaseName" use="optional">
<xs:annotation>
<xs:documentation>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</xs:documentation>
<xs:documentation>This is the name of an IFC Defined Type, all uppercase.</xs:documentation>
</xs:annotation>
<!-- renamed 'measure' to data type to better represent reality -->
</xs:attribute>
</xs:complexType>
<xs:complexType name="attributeType">
<xs:sequence>
<xs:element name="name" type="ids:idsValue"/>
<xs:element name="value" type="ids:idsValue" minOccurs="0"/>
<xs:element name="value" type="ids:idsValue" minOccurs="0">
<xs:annotation>
<xs:documentation>
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.
</xs:documentation>
</xs:annotation>
</xs:element>
</xs:sequence>
</xs:complexType>
<xs:complexType name="materialType">
@@ -98,6 +125,13 @@
<xs:complexType>
<xs:complexContent>
<xs:extension base="ids:entityType">
<!--
Contrary to other requirements facet extensions, cardinality is not available in the entityType facet when used for requirements.
Its cardinality state is always considered to be "required".
Constraining the acceptable values is achieved by specifying criteria via with xs:Enumeration and xs:Pattern, rather than the negative form.
This is possible because the list of options is finite and mandated by the IFC schema, so prohibited constraints are superfluous.
This choice allows for improved user experience in the editors.
-->
<xs:attribute name="instructions" type="xs:string" use="optional">
<xs:annotation>
<xs:documentation>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.</xs:documentation>
@@ -111,7 +145,7 @@
<xs:complexType>
<xs:complexContent>
<xs:extension base="ids:partOfType">
<xs:attributeGroup ref="xs:occurs"/>
<xs:attribute name="cardinality" type="ids:simpleCardinality" use="optional" default="required"/>
<xs:attribute name="instructions" type="xs:string" use="optional">
<xs:annotation>
<xs:documentation>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.</xs:documentation>
@@ -126,7 +160,7 @@
<xs:complexContent>
<xs:extension base="ids:classificationType">
<xs:attribute name="uri" type="xs:anyURI" use="optional"/>
<xs:attributeGroup ref="xs:occurs"/>
<xs:attribute name="cardinality" type="ids:conditionalCardinality" use="optional" default="required"/>
<xs:attribute name="instructions" type="xs:string" use="optional">
<xs:annotation>
<xs:documentation>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.</xs:documentation>
@@ -140,6 +174,7 @@
<xs:complexType>
<xs:complexContent>
<xs:extension base="ids:attributeType">
<xs:attribute name="cardinality" type="ids:conditionalCardinality" use="optional" default="required"/>
<xs:attribute name="instructions" type="xs:string" use="optional">
<xs:annotation>
<xs:documentation>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.</xs:documentation>
@@ -154,7 +189,7 @@
<xs:complexContent>
<xs:extension base="ids:propertyType">
<xs:attribute name="uri" type="xs:anyURI" use="optional"/>
<xs:attributeGroup ref="xs:occurs"/>
<xs:attribute name="cardinality" type="ids:conditionalCardinality" use="optional" default="required"/>
<xs:attribute name="instructions" type="xs:string" use="optional">
<xs:annotation>
<xs:documentation>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.</xs:documentation>
@@ -164,12 +199,12 @@
</xs:complexContent>
</xs:complexType>
</xs:element>
<xs:element name="material" minOccurs="0">
<xs:element name="material" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:complexContent>
<xs:extension base="ids:materialType">
<xs:attribute name="uri" type="xs:anyURI" use="optional"/>
<xs:attributeGroup ref="xs:occurs"/>
<xs:attribute name="cardinality" type="ids:conditionalCardinality" use="optional" default="required"/>
<xs:attribute name="instructions" type="xs:string" use="optional">
<xs:annotation>
<xs:documentation>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.</xs:documentation>
@@ -195,7 +230,6 @@
</xs:element>
</xs:sequence>
<xs:attribute name="name" type="xs:string" use="required"/>
<xs:attributeGroup ref="xs:occurs"/>
<xs:attribute name="ifcVersion" use="required">
<xs:simpleType>
<xs:list>
@@ -210,7 +244,7 @@
</xs:list>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="identifier" type="xs:string">
<xs:attribute name="identifier" type="xs:string" use="optional">
<xs:annotation>
<xs:documentation>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.</xs:documentation>
</xs:annotation>
@@ -229,12 +263,30 @@
</xs:complexType>
<xs:simpleType name="relations">
<xs:restriction base="xs:string">
<xs:enumeration value="IFCRELAGGREGATES"/>
<xs:enumeration value="IFCRELASSIGNSTOGROUP"/>
<xs:enumeration value="IFCRELCONTAINEDINSPATIALSTRUCTURE"/>
<xs:enumeration value="IFCRELNESTS"/>
<xs:enumeration value="IFCRELVOIDSELEMENT"/>
<xs:enumeration value="IFCRELFILLSELEMENT"/>
<xs:enumeration value="IFCRELAGGREGATES"/>
<xs:enumeration value="IFCRELASSIGNSTOGROUP"/>
<xs:enumeration value="IFCRELCONTAINEDINSPATIALSTRUCTURE"/>
<xs:enumeration value="IFCRELNESTS"/>
<xs:enumeration value="IFCRELVOIDSELEMENT IFCRELFILLSELEMENT"/>
</xs:restriction>
</xs:simpleType>
</xs:schema>
<xs:simpleType name="upperCaseName">
<xs:restriction base="xs:normalizedString">
<xs:pattern value="[A-Z]+"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="simpleCardinality">
<xs:restriction base="xs:string">
<xs:enumeration value="required"/>
<xs:enumeration value="prohibited"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="conditionalCardinality">
<xs:restriction base="xs:string">
<xs:enumeration value="required"/>
<xs:enumeration value="prohibited"/>
<xs:enumeration value="optional"/>
</xs:restriction>
</xs:simpleType>
</xs:schema>
+112 -125
View File
@@ -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")
+4 -10
View File
@@ -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",