mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-18 06:21:40 +00:00
Fix #4465. Update IfcTester to IDS 0.9.7
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user