mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-08 00:41:36 +00:00
Fix #4465. Update IfcTester to IDS 0.9.7
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
+112
-125
@@ -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")
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user