Update IfcTester to comply with latest modifications to IDS XSD.

This commit is contained in:
Dion Moult
2023-07-09 17:02:12 +10:00
parent 2dbb566072
commit ea44fa8b89
2 changed files with 120 additions and 60 deletions
+93 -22
View File
@@ -343,9 +343,9 @@ class Classification(Facet):
class PartOf(Facet):
def __init__(
self,
entity=None,
entity="IFCWALL",
predefinedType=None,
relation="IfcRelAggregates",
relation=None,
minOccurs=None,
maxOccurs=None,
instructions=None,
@@ -390,7 +390,23 @@ class PartOf(Facet):
return PartOfResult(True)
reason = None
if self.relation == "IfcRelAggregates":
if not self.relation:
is_pass = False
ancestors = []
parent = self.get_parent(inst)
while parent:
ancestors.append(parent.is_a())
if parent.is_a().upper() == self.entity:
if self.predefinedType:
if ifcopenshell.util.element.get_predefined_type(parent) == self.predefinedType:
is_pass = True
else:
is_pass = True
break
parent = self.get_parent(parent)
if not is_pass:
reason = {"type": "ENTITY", "actual": ancestors}
elif self.relation == "IFCRELAGGREGATES":
aggregate = ifcopenshell.util.element.get_aggregate(inst)
is_pass = aggregate is not None
if not is_pass:
@@ -410,7 +426,7 @@ class PartOf(Facet):
aggregate = ifcopenshell.util.element.get_aggregate(aggregate)
if not is_pass:
reason = {"type": "ENTITY", "actual": ancestors}
elif self.relation == "IfcRelAssignsToGroup":
elif self.relation == "IFCRELASSIGNSTOGROUP":
group = None
for rel in getattr(inst, "HasAssignments", []) or []:
if rel.is_a("IfcRelAssignsToGroup"):
@@ -428,7 +444,7 @@ class PartOf(Facet):
if predefined_type != self.predefinedType:
is_pass = False
reason = {"type": "PREDEFINEDTYPE", "actual": predefined_type}
elif self.relation == "IfcRelContainedInSpatialStructure":
elif self.relation == "IFCRELCONTAINEDINSPATIALSTRUCTURE":
container = ifcopenshell.util.element.get_container(inst)
is_pass = container is not None
if not is_pass:
@@ -442,7 +458,7 @@ class PartOf(Facet):
if predefined_type != self.predefinedType:
is_pass = False
reason = {"type": "PREDEFINEDTYPE", "actual": predefined_type}
elif self.relation == "IfcRelNests":
elif self.relation == "IFCRELNESTS":
nest = self.get_nested_whole(inst)
is_pass = nest is not None
if not is_pass:
@@ -462,6 +478,36 @@ 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)
is_pass = building_element is not None
if not is_pass:
reason = {"type": "NOVALUE"}
if is_pass and self.entity:
is_pass = False
if building_element.is_a().upper() == self.entity:
if self.predefinedType:
if ifcopenshell.util.element.get_predefined_type(building_element) == self.predefinedType:
is_pass = True
else:
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.entity:
is_pass = False
if opening.is_a().upper() == self.entity:
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:
return PartOfResult(not is_pass, {"type": "PROHIBITED"})
@@ -471,6 +517,31 @@ class PartOf(Facet):
for rel in getattr(element, "Nests", []) or []:
return rel.RelatingObject
def get_voided_element(self, element):
for rel in getattr(element, "VoidsElements", []) or []:
return rel.RelatingBuildingElement
def get_filled_opening(self, element):
for rel in getattr(element, "FillsVoids", []) or []:
return rel.RelatingOpeningElement
def get_parent(self, element):
parent = ifcopenshell.util.element.get_aggregate(element)
if not parent:
parent = ifcopenshell.util.element.get_container(element, should_get_direct=True)
if not parent:
for rel in getattr(element, "HasAssignments", []) or []:
if rel.is_a("IfcRelAssignsToGroup"):
parent = rel.RelatingGroup
break
if not parent:
self.get_nested_whole(element)
if not parent:
self.get_voided_element(element)
if not parent:
self.get_filled_opening(element)
return parent
class Property(Facet):
def __init__(
@@ -478,7 +549,7 @@ class Property(Facet):
propertySet="Property_Set",
name="PropertyName",
value=None,
measure=None,
datatype=None,
uri=None,
minOccurs=None,
maxOccurs=None,
@@ -488,7 +559,7 @@ class Property(Facet):
"propertySet",
"name",
"value",
"@measure",
"@datatype",
"@uri",
"@minOccurs",
"@maxOccurs",
@@ -502,7 +573,7 @@ class Property(Facet):
"{name} data shall be {value} and in the dataset {propertySet}",
"{name} data shall be provided in the dataset {propertySet}",
]
super().__init__(propertySet, name, value, measure, uri, minOccurs, maxOccurs, instructions)
super().__init__(propertySet, name, value, datatype, uri, minOccurs, maxOccurs, instructions)
def filter(self, ifc_file, elements):
if isinstance(elements, list):
@@ -566,9 +637,9 @@ class Property(Facet):
elif prop_entity.is_a("IfcPropertySingleValue"):
data_type = prop_entity.NominalValue.is_a()
if data_type != self.measure:
if data_type != self.datatype:
is_pass = False
reason = {"type": "MEASURE", "actual": data_type}
reason = {"type": "DATATYPE", "actual": data_type}
break
unit = ifcopenshell.util.unit.get_property_unit(prop_entity, inst.wrapped_data.file)
@@ -585,9 +656,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 != self.measure:
if data_type != self.datatype:
is_pass = False
reason = {"type": "MEASURE", "actual": data_type}
reason = {"type": "DATATYPE", "actual": data_type}
break
unit = ifcopenshell.util.unit.get_property_unit(prop_entity, inst.wrapped_data.file)
@@ -605,9 +676,9 @@ class Property(Facet):
reason = {"type": "NOVALUE"}
break
data_type = prop_entity.EnumerationValues[0].is_a()
if data_type != self.measure:
if data_type != self.datatype:
is_pass = False
reason = {"type": "MEASURE", "actual": data_type}
reason = {"type": "DATATYPE", "actual": data_type}
break
elif prop_entity.is_a("IfcPropertyListValue"):
if not prop_entity.ListValues:
@@ -615,9 +686,9 @@ class Property(Facet):
reason = {"type": "NOVALUE"}
break
data_type = prop_entity.ListValues[0].is_a()
if data_type != self.measure:
if data_type != self.datatype:
is_pass = False
reason = {"type": "MEASURE", "actual": data_type}
reason = {"type": "DATATYPE", "actual": data_type}
break
unit = ifcopenshell.util.unit.get_property_unit(prop_entity, inst.wrapped_data.file)
if unit:
@@ -638,9 +709,9 @@ class Property(Facet):
if value is not None:
data_type = value.is_a()
values.append(value.wrappedValue)
if data_type != self.measure:
if data_type != self.datatype:
is_pass = False
reason = {"type": "MEASURE", "actual": data_type}
reason = {"type": "DATATYPE", "actual": data_type}
break
unit = ifcopenshell.util.unit.get_property_unit(prop_entity, inst.wrapped_data.file)
if unit:
@@ -663,7 +734,7 @@ class Property(Facet):
if not column_values:
continue
data_type = column_values[0].is_a()
if data_type == self.measure:
if data_type == self.datatype:
column_values = [v.wrappedValue for v in column_values]
unit = units[f"{attribute}Unit"]
if unit:
@@ -680,7 +751,7 @@ class Property(Facet):
values.extend(column_values)
if not values:
is_pass = False
reason = {"type": "MEASURE", "actual": data_type}
reason = {"type": "DATATYPE", "actual": data_type}
break
props[pset_name][prop_entity.Name] = values
else:
@@ -963,7 +1034,7 @@ class PropertyResult(Result):
return "The required property set does not exist"
elif self.reason["type"] == "NOVALUE":
return "The property set does not contain the required property"
elif self.reason["type"] == "MEASURE":
elif self.reason["type"] == "DATATYPE":
return f"The data type \"{str(self.reason['actual'])}\" does not match the requirements"
elif self.reason["type"] == "VALUE":
if isinstance(self.reason["actual"], list):
+27 -38
View File
@@ -1,8 +1,7 @@
<!-- edited with XMLSpy v2022 (x64) (http://www.altova.com) -->
<!-- September 26, 2022 - 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" targetNamespace="http://standards.buildingsmart.org/IDS" elementFormDefault="qualified" attributeFormDefault="unqualified" version="0.9.3">
<!-- 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">
<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="https://www.w3.org/2001/XMLSchema.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"/>
<xs:element name="ids">
<xs:complexType>
@@ -27,13 +26,7 @@
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="specifications">
<xs:complexType>
<xs:complexContent>
<xs:extension base="ids:specificationsType"/>
</xs:complexContent>
</xs:complexType>
</xs:element>
<xs:element name="specifications" type="ids:specificationsType"/>
</xs:sequence>
</xs:complexType>
</xs:element>
@@ -58,9 +51,9 @@
</xs:complexType>
<xs:complexType name="partOfType">
<xs:sequence>
<xs:element name="entity" type="ids:entityType" minOccurs="0"/>
<xs:element name="entity" type="ids:entityType" minOccurs="1" />
</xs:sequence>
<xs:attribute name="relation" type="ids:relations" use="required"/>
<xs:attribute name="relation" type="ids:relations" />
</xs:complexType>
<xs:complexType name="applicabilityType">
<xs:sequence>
@@ -68,20 +61,8 @@
<xs:element name="partOf" type="ids:partOfType" minOccurs="0" maxOccurs="unbounded"/>
<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" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:complexContent>
<xs:extension base="ids:propertyType"/>
</xs:complexContent>
</xs:complexType>
</xs:element>
<xs:element name="material" minOccurs="0">
<xs:complexType>
<xs:complexContent>
<xs:extension base="ids:materialType"/>
</xs:complexContent>
</xs:complexType>
</xs:element>
<xs:element name="property" type="ids:propertyType" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="material" type="ids:materialType" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="propertyType">
@@ -90,11 +71,11 @@
<xs:element name="name" type="ids:idsValue"/>
<xs:element name="value" type="ids:idsValue" minOccurs="0"/>
</xs:sequence>
<xs:attribute name="measure" type="xs:string">
<xs:attribute name="datatype" use="required">
<xs:annotation>
<xs:documentation>This is the name of an IFC Defined Types. See the 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. 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:annotation>
<!-- there is a request to make this an ids:value -->
<!-- renamed 'measure' to data type to better represent reality -->
</xs:attribute>
</xs:complexType>
<xs:complexType name="attributeType">
@@ -116,7 +97,13 @@
</xs:annotation>
<xs:complexType>
<xs:complexContent>
<xs:extension base="ids:entityType"/>
<xs:extension base="ids:entityType">
<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>
</xs:annotation>
</xs:attribute>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:element>
@@ -153,7 +140,6 @@
<xs:complexType>
<xs:complexContent>
<xs:extension base="ids:attributeType">
<xs:attributeGroup ref="xs:occurs"/>
<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>
@@ -215,6 +201,7 @@
<xs:list>
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:enumeration value="IFC2X3"/>
<xs:enumeration value="IFC4"/>
<xs:enumeration value="IFC4X3"/>
@@ -225,7 +212,7 @@
</xs:attribute>
<xs:attribute name="identifier" type="xs:string">
<xs:annotation>
<xs:documentation>Author of the IDS can provide an identifier to the IDS. Beware: this cannot be enforced/assumed as (global) unique.</xs:documentation>
<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 'requirement' elements from several ids files this cannot be enforced/assumed as (global) unique.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="description" type="xs:string" use="optional"/>
@@ -242,10 +229,12 @@
</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="IFCRELAGGREGATES"/>
<xs:enumeration value="IFCRELASSIGNSTOGROUP"/>
<xs:enumeration value="IFCRELCONTAINEDINSPATIALSTRUCTURE"/>
<xs:enumeration value="IFCRELNESTS"/>
<xs:enumeration value="IFCRELVOIDSELEMENT"/>
<xs:enumeration value="IFCRELFILLSELEMENT"/>
</xs:restriction>
</xs:simpleType>
</xs:schema>
</xs:schema>