mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-11 02:02:22 +00:00
Implement optionality in IfcTester, and generate integration testcases
This commit is contained in:
@@ -106,6 +106,14 @@ class Facet:
|
||||
raise Exception(str(parameter) + " was not able to be converted into 'Parameter_dict'")
|
||||
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"
|
||||
|
||||
|
||||
class Entity(Facet):
|
||||
def __init__(self, name="IFCWALL", predefinedType=None, instructions=None):
|
||||
@@ -165,24 +173,11 @@ class Attribute(Facet):
|
||||
if self.minOccurs == 0 and self.maxOccurs != 0:
|
||||
return AttributeResult(True)
|
||||
|
||||
def get_values(element, name):
|
||||
if isinstance(name, str):
|
||||
return [getattr(element, name, None)]
|
||||
return [v for k, v in element.get_info().items() if k == name]
|
||||
|
||||
element_type = ifcopenshell.util.element.get_type(inst)
|
||||
|
||||
if isinstance(self.name, str):
|
||||
type_value = getattr(element_type, self.name, None) if element_type else None
|
||||
occurrence_value = getattr(inst, self.name, None)
|
||||
names = [self.name]
|
||||
values = [occurrence_value if occurrence_value is not None else type_value]
|
||||
values = [getattr(inst, self.name, None)]
|
||||
else:
|
||||
if element_type:
|
||||
info = element_type.get_info()
|
||||
info.update({k: v for k, v in inst.get_info().items() if v is not None})
|
||||
else:
|
||||
info = inst.get_info()
|
||||
info = inst.get_info()
|
||||
names = []
|
||||
values = []
|
||||
for k, v in info.items():
|
||||
@@ -197,29 +192,31 @@ class Attribute(Facet):
|
||||
reason = {"type": "NOVALUE"}
|
||||
|
||||
if is_pass:
|
||||
non_empty_values = []
|
||||
for i, value in enumerate(values):
|
||||
is_empty = False
|
||||
if value is None:
|
||||
is_pass = False
|
||||
reason = {"type": "FALSEY", "actual": value}
|
||||
is_empty = True
|
||||
elif value == "":
|
||||
is_pass = False
|
||||
reason = {"type": "FALSEY", "actual": value}
|
||||
is_empty = True
|
||||
elif value == tuple():
|
||||
is_pass = False
|
||||
reason = {"type": "FALSEY", "actual": value}
|
||||
is_empty = True
|
||||
else:
|
||||
argument_index = inst.wrapped_data.get_argument_index(names[i])
|
||||
try:
|
||||
attribute_type = inst.attribute_type(argument_index)
|
||||
if attribute_type == "LOGICAL" and value == "UNKNOWN":
|
||||
is_pass = False
|
||||
reason = {"type": "FALSEY", "actual": value}
|
||||
is_empty = True
|
||||
except:
|
||||
if names[i] in inst.wrapped_data.get_inverse_attribute_names():
|
||||
is_pass = False
|
||||
reason = {"type": "INVALID"}
|
||||
if not is_pass:
|
||||
break
|
||||
is_empty = True
|
||||
if not is_empty:
|
||||
non_empty_values.append(value)
|
||||
if non_empty_values:
|
||||
values = non_empty_values
|
||||
else:
|
||||
is_pass = False
|
||||
reason = {"type": "FALSEY", "actual": values if len(values) > 1 else values[0]}
|
||||
|
||||
if is_pass and self.value:
|
||||
for value in values:
|
||||
@@ -326,7 +323,7 @@ class PartOf(Facet):
|
||||
aggregate = ifcopenshell.util.element.get_aggregate(inst)
|
||||
is_pass = aggregate is not None
|
||||
if not is_pass:
|
||||
reason = {"type": "RELATION"}
|
||||
reason = {"type": "NOVALUE"}
|
||||
if is_pass and self.entity:
|
||||
is_pass = False
|
||||
ancestors = []
|
||||
@@ -355,7 +352,7 @@ class PartOf(Facet):
|
||||
container = ifcopenshell.util.element.get_container(inst)
|
||||
is_pass = container is not None
|
||||
if not is_pass:
|
||||
reason = {"type": "RELATION"}
|
||||
reason = {"type": "NOVALUE"}
|
||||
if is_pass and self.entity:
|
||||
if container.is_a().upper() != self.entity:
|
||||
is_pass = False
|
||||
@@ -871,6 +868,8 @@ class AttributeResult(Result):
|
||||
return f"An invalid attribute name was specified in the IDS"
|
||||
elif self.reason["type"] == "VALUE":
|
||||
return f"The attribute value \"{str(self.reason['actual'])}\" does not match the requirement"
|
||||
elif self.reason["type"] == "PROHIBITED":
|
||||
return f"The attribute value should not have met the requirement"
|
||||
|
||||
|
||||
class ClassificationResult(Result):
|
||||
@@ -881,11 +880,18 @@ class ClassificationResult(Result):
|
||||
return f"The references \"{str(self.reason['actual'])}\" do not match the requirements"
|
||||
elif self.reason["type"] == "system":
|
||||
return f"The systems \"{str(self.reason['actual'])}\" do not match the requirements"
|
||||
elif self.reason["type"] == "PROHIBITED":
|
||||
return f"The classification should not have met the requirement"
|
||||
|
||||
|
||||
class PartOfResult(Result):
|
||||
def to_string(self):
|
||||
return "TODO"
|
||||
if self.reason["type"] == "NOVALUE":
|
||||
return "The entity has no relationship"
|
||||
elif self.reason["type"] == "ENTITY":
|
||||
return f"The entity has a relationship with incorrect entities: \"{str(self.reason['actual'])}\""
|
||||
elif self.reason["type"] == "PROHIBITED":
|
||||
return f"The relationship should not have met the requirement"
|
||||
|
||||
|
||||
class PropertyResult(Result):
|
||||
@@ -900,6 +906,8 @@ class PropertyResult(Result):
|
||||
return f"The property value \"{str(self.reason['actual'][0])}\" does not match the requirements"
|
||||
elif self.reason["type"] == "VALUE":
|
||||
return f"The property values \"{str(self.reason['actual'])}\" do not match the requirements"
|
||||
elif self.reason["type"] == "PROHIBITED":
|
||||
return f"The property should not have met the requirement"
|
||||
|
||||
|
||||
class MaterialResult(Result):
|
||||
@@ -910,3 +918,5 @@ class MaterialResult(Result):
|
||||
return (
|
||||
f"The material names and categories of \"{str(self.reason['actual'])}\" does not match the requirement"
|
||||
)
|
||||
elif self.reason["type"] == "PROHIBITED":
|
||||
return f"The material should not have met the requirement"
|
||||
|
||||
@@ -32,9 +32,7 @@ def open(filepath, validate=False):
|
||||
if validate:
|
||||
get_schema().validate(filepath)
|
||||
return Ids().parse(
|
||||
get_schema().decode(
|
||||
filepath, strip_namespaces=True, namespaces={"": "http://standards.buildingsmart.org/IDS"}
|
||||
)
|
||||
get_schema().decode(filepath, strip_namespaces=True, namespaces={"": "http://standards.buildingsmart.org/IDS"})
|
||||
)
|
||||
|
||||
|
||||
@@ -114,18 +112,18 @@ class Ids:
|
||||
ET.ElementTree(get_schema().encode(self.asdict())).write(filepath, encoding="utf-8", xml_declaration=True)
|
||||
return get_schema().is_valid(filepath)
|
||||
|
||||
def validate(self, ifc_file):
|
||||
def validate(self, ifc_file, filter_version=False):
|
||||
for specification in self.specifications:
|
||||
specification.reset_status()
|
||||
specification.validate(ifc_file)
|
||||
specification.validate(ifc_file, filter_version=filter_version)
|
||||
|
||||
|
||||
class Specification:
|
||||
def __init__(
|
||||
self,
|
||||
name="Unnamed",
|
||||
minOccurs=None,
|
||||
maxOccurs=None,
|
||||
minOccurs=0,
|
||||
maxOccurs="unbounded",
|
||||
ifcVersion=["IFC2X3", "IFC4"],
|
||||
identifier=None,
|
||||
description=None,
|
||||
@@ -153,7 +151,7 @@ class Specification:
|
||||
}
|
||||
for attribute in ["identifier", "description", "instructions", "minOccurs", "maxOccurs"]:
|
||||
value = getattr(self, attribute)
|
||||
if value:
|
||||
if value is not None:
|
||||
results[f"@{attribute}"] = value
|
||||
for clause_type in ["applicability", "requirements"]:
|
||||
clause = getattr(self, clause_type)
|
||||
@@ -194,11 +192,12 @@ class Specification:
|
||||
self.applicable_entities.clear()
|
||||
self.failed_entities = set()
|
||||
for facet in self.requirements:
|
||||
facet.status = None
|
||||
facet.failed_entities.clear()
|
||||
self.status = None
|
||||
|
||||
def validate(self, ifc_file):
|
||||
if ifc_file.schema not in self.ifcVersion:
|
||||
def validate(self, ifc_file, filter_version=False):
|
||||
if filter_version and ifc_file.schema not in self.ifcVersion:
|
||||
return
|
||||
|
||||
elements = []
|
||||
@@ -218,18 +217,38 @@ class Specification:
|
||||
self.applicable_entities.append(element)
|
||||
for facet in self.requirements:
|
||||
result = facet(element)
|
||||
facet.status = bool(result)
|
||||
if not facet.status:
|
||||
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:
|
||||
facet.status = not bool(facet.failed_entities)
|
||||
elif facet.minOccurs == 0 and facet.maxOccurs != 0:
|
||||
facet.status = True
|
||||
elif facet.maxOccurs == 0:
|
||||
facet.status = bool(facet.failed_entities)
|
||||
|
||||
self.status = True
|
||||
if self.failed_entities:
|
||||
self.status = False
|
||||
elif self.minOccurs != 0 and not self.applicable_entities:
|
||||
self.status = False
|
||||
for facet in self.requirements:
|
||||
facet.status = False
|
||||
elif len(self.applicable_entities) > (self.maxOccurs or 1):
|
||||
self.status = False
|
||||
if self.minOccurs != 0:
|
||||
if not self.applicable_entities:
|
||||
self.status = False
|
||||
for facet in self.requirements:
|
||||
facet.status = False
|
||||
elif self.failed_entities:
|
||||
self.status = False
|
||||
elif self.minOccurs == 0 and self.maxOccurs != 0:
|
||||
if self.failed_entities:
|
||||
self.status = False
|
||||
elif self.maxOccurs == 0:
|
||||
if (len(self.applicable_entities) - len(self.failed_entities)) > 0:
|
||||
self.status = False
|
||||
|
||||
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"
|
||||
|
||||
@@ -24,27 +24,21 @@ import functools
|
||||
import ifcopenshell
|
||||
import ifctester
|
||||
import test_facet
|
||||
import test_ids
|
||||
from xml.dom.minidom import parseString
|
||||
from ifctester import ids
|
||||
from ifcopenshell import validate
|
||||
|
||||
outdir = "build"
|
||||
|
||||
class StableUUIDGenerator:
|
||||
file = None
|
||||
|
||||
@classmethod
|
||||
def generate(cls):
|
||||
ns = uuid.UUID('b59aa156-82a4-4b4c-a6e5-3d04a0236af9')
|
||||
if cls.file:
|
||||
return ifcopenshell.guid.compress(uuid.uuid5(ns, str(cls.file.wrapped_data.getMaxId())).hex)
|
||||
return ifcopenshell.guid.compress(uuid.uuid4().hex)
|
||||
|
||||
# Just for aesthetics so we don't keep on getting brand new GlobalIds on each generation
|
||||
ifcopenshell.guid.new = StableUUIDGenerator.generate
|
||||
def regenerate_guids(ifc):
|
||||
ns = uuid.UUID("b59aa156-82a4-4b4c-a6e5-3d04a0236af9")
|
||||
for element in ifc.by_type("IfcRoot"):
|
||||
element.GlobalId = ifcopenshell.guid.compress(uuid.uuid5(ns, str(element.id())).hex)
|
||||
|
||||
|
||||
class DocGenerator:
|
||||
class FacetDocGenerator:
|
||||
def __init__(self):
|
||||
self.facet = None
|
||||
self.testcases = {}
|
||||
@@ -55,12 +49,13 @@ class DocGenerator:
|
||||
|
||||
result = "pass" if expected is True else "fail"
|
||||
|
||||
f = inst.wrapped_data.file
|
||||
StableUUIDGenerator.file = f
|
||||
ifc = inst.wrapped_data.file
|
||||
if "GlobalId" not in name:
|
||||
regenerate_guids(ifc)
|
||||
|
||||
# Validate the file created and loop over the issues, fixing them one by one.
|
||||
l = validate.json_logger()
|
||||
validate.validate(f, l)
|
||||
validate.validate(ifc, l)
|
||||
for issue in l.statements:
|
||||
if "GlobalId" in issue["message"]:
|
||||
issue["instance"].GlobalId = ifcopenshell.guid.new()
|
||||
@@ -68,17 +63,17 @@ class DocGenerator:
|
||||
ty = re.findall("\\(.+?\\)", issue["message"])[0][1:-1].split(", ")[0]
|
||||
issue["instance"].PredefinedType = ty
|
||||
elif "IfcMaterialList" in issue["message"]:
|
||||
issue["instance"].Materials = [f.createIfcMaterial("Concrete", None, "CONCRETE")]
|
||||
issue["instance"].Materials = [ifc.createIfcMaterial("Concrete", None, "CONCRETE")]
|
||||
else:
|
||||
raise Exception("About to emit invalid example data:", issue)
|
||||
|
||||
# ifc_text = "\n".join([f"{e} /* Testcase */" if e == inst else str(e) for e in f])
|
||||
lines = f.wrapped_data.to_string().split("\n")[7:-3]
|
||||
lines = ifc.wrapped_data.to_string().split("\n")[7:-3]
|
||||
ifc_text = "\n".join([f"{l} /* Testcase */" if f"#{inst.id()}=" in l else l for l in lines])
|
||||
basename = f"{result}-" + re.sub("[^0-9a-zA-Z]", "_", name.lower())
|
||||
|
||||
# Write IFC to disk
|
||||
f.write(os.path.join(outdir, "testcases", self.facet, f"{basename}.ifc"))
|
||||
ifc.write(os.path.join(outdir, "testcases", self.facet, f"{basename}.ifc"))
|
||||
|
||||
# Create an IDS with the applicability selecting exactly
|
||||
# the entity type passed to us in `inst`.
|
||||
@@ -112,8 +107,73 @@ class DocGenerator:
|
||||
self.facet = facet
|
||||
|
||||
|
||||
test_facet.run = DocGenerator()
|
||||
class IdsDocGenerator:
|
||||
def __init__(self):
|
||||
self.testcases = []
|
||||
|
||||
def __call__(self, name, ids, ifc, expected, applicable_entities=[], failed_entities=[]):
|
||||
ids.validate(ifc)
|
||||
all_applicable = set()
|
||||
all_failures = set()
|
||||
for spec in ids.specifications:
|
||||
assert spec.status is expected
|
||||
all_applicable.update(spec.applicable_entities)
|
||||
for requirement in spec.requirements:
|
||||
if requirement.status is False:
|
||||
all_failures.update(requirement.failed_entities)
|
||||
assert set(all_applicable) == set(applicable_entities)
|
||||
assert set(all_failures) == set(failed_entities)
|
||||
|
||||
result = "pass" if expected is True else "fail"
|
||||
|
||||
regenerate_guids(ifc)
|
||||
|
||||
l = validate.json_logger()
|
||||
validate.validate(ifc, l)
|
||||
for issue in l.statements:
|
||||
raise Exception("About to emit invalid example data:", issue)
|
||||
|
||||
lines = ifc.wrapped_data.to_string().split("\n")[7:-3]
|
||||
ifc_text = ""
|
||||
for i, line in enumerate(lines):
|
||||
step_id = int(line[1 : line.index("=")])
|
||||
element = ifc.by_id(step_id)
|
||||
newline = "" if i == 0 else "\n"
|
||||
if element in applicable_entities:
|
||||
pass_or_fail = "FAIL" if element in failed_entities else "PASS"
|
||||
ifc_text += f"{newline}[{pass_or_fail}] {line}"
|
||||
else:
|
||||
ifc_text += f"{newline} {line}"
|
||||
basename = f"{result}-" + re.sub("[^0-9a-zA-Z]", "_", name.lower())
|
||||
|
||||
# Write IFC to disk
|
||||
ifc.write(os.path.join(outdir, "testcases", "ids", f"{basename}.ifc"))
|
||||
|
||||
# Write IDS to disk
|
||||
with open(os.path.join(outdir, "testcases", "ids", f"{basename}.ids"), "w", encoding="utf-8") as ids_file:
|
||||
ids_file.write(ids.to_string())
|
||||
|
||||
reports = []
|
||||
for spec in ids.specifications:
|
||||
report = {"applicability": [], "requirements": [], "usage": spec.get_usage(), "status": spec.status}
|
||||
for facet in spec.applicability:
|
||||
report["applicability"].append(facet.to_string("applicability"))
|
||||
for facet in spec.requirements:
|
||||
report["requirements"].append(
|
||||
{"status": facet.status, "text": facet.to_string("requirement"), "usage": facet.get_usage()}
|
||||
)
|
||||
reports.append(report)
|
||||
|
||||
xml_text = "\n".join([l[4:] for l in ids.to_string().split("\n")[4:-1]]).replace("\t", " ")
|
||||
|
||||
self.testcases.append(
|
||||
{"name": name, "ids": xml_text, "ifc": ifc_text, "basename": basename, "result": result, "reports": reports}
|
||||
)
|
||||
|
||||
|
||||
test_facet.run = FacetDocGenerator()
|
||||
test_facet.set_facet = test_facet.run.set_facet
|
||||
test_ids.run = IdsDocGenerator()
|
||||
|
||||
pytest.main(["-p", "no:pytest-blender"])
|
||||
|
||||
@@ -142,6 +202,42 @@ for facet, testcases in test_facet.run.testcases.items():
|
||||
)
|
||||
write()
|
||||
|
||||
with open(os.path.join(outdir, f"testcases-ids.md"), "w") as f:
|
||||
write = functools.partial(print, file=f)
|
||||
write(f"# IDS integration testcases")
|
||||
write()
|
||||
write(
|
||||
"These testcases are designed to help describe behaviour in edge cases and ambiguities. All valid IDS implementations must demonstrate identical behaviour to these test cases."
|
||||
)
|
||||
write()
|
||||
for testcase in test_ids.run.testcases:
|
||||
write(f"## [{testcase['result'].upper()}] {testcase['name']}")
|
||||
write()
|
||||
write("~~~xml")
|
||||
write(testcase["ids"])
|
||||
write("~~~")
|
||||
write()
|
||||
write("~~~lua")
|
||||
write(testcase["ifc"])
|
||||
write("~~~")
|
||||
write()
|
||||
for report in testcase["reports"]:
|
||||
write("```")
|
||||
icon = "✔️" if report["status"] else "❌"
|
||||
write(f"# {icon} Specification ({report['usage']})")
|
||||
write("Applies to:")
|
||||
for facet in report["applicability"]:
|
||||
write(f" - {facet}")
|
||||
write("Requirements:")
|
||||
for facet in report["requirements"]:
|
||||
icon = "✔️" if facet["status"] else "❌"
|
||||
write(f" - {icon} {facet['text']} ({facet['usage']})")
|
||||
write("```")
|
||||
write()
|
||||
write(
|
||||
f"[Sample IDS](testcases/ids/{testcase['basename']}.ids) - [Sample IFC](testcases/ids/{testcase['basename']}.ifc)"
|
||||
)
|
||||
write()
|
||||
|
||||
specs = ifctester.ids.Ids(
|
||||
title="buildingSMART Sample IDS",
|
||||
@@ -153,15 +249,38 @@ specs = ifctester.ids.Ids(
|
||||
purpose="Contractual requirements",
|
||||
)
|
||||
|
||||
spec = ifctester.ids.Specification(name="Project naming", ifcVersion=["IFC4"], description="Projects shall be named correctly for the purposes of identification, project archival, and model federation.", instructions="Each discipline is responsible for naming their own project.")
|
||||
spec = ifctester.ids.Specification(
|
||||
name="Project naming",
|
||||
ifcVersion=["IFC4"],
|
||||
description="Projects shall be named correctly for the purposes of identification, project archival, and model federation.",
|
||||
instructions="Each discipline is responsible for naming their own project.",
|
||||
)
|
||||
specs.specifications.append(spec)
|
||||
spec.applicability.append(ifctester.ids.Entity(name="IFCPROJECT"))
|
||||
spec.requirements.append(ifctester.ids.Attribute(name="Name", value="TEST", instructions="The project manager shall confirm the short project code with the client based on their real estate portfolio naming scheme."))
|
||||
spec.requirements.append(
|
||||
ifctester.ids.Attribute(
|
||||
name="Name",
|
||||
value="TEST",
|
||||
instructions="The project manager shall confirm the short project code with the client based on their real estate portfolio naming scheme.",
|
||||
)
|
||||
)
|
||||
|
||||
spec = ifctester.ids.Specification(name="Fire rating", ifcVersion=["IFC4"], description="All objects must have a fire rating for building compliance checks and to know the protection strategies needed for any penetrations.", instructions="The architect is responsible for including this data.")
|
||||
spec = ifctester.ids.Specification(
|
||||
name="Fire rating",
|
||||
ifcVersion=["IFC4"],
|
||||
description="All objects must have a fire rating for building compliance checks and to know the protection strategies needed for any penetrations.",
|
||||
instructions="The architect is responsible for including this data.",
|
||||
)
|
||||
specs.specifications.append(spec)
|
||||
spec.applicability.append(ifctester.ids.Entity(name="IFCWALLTYPE"))
|
||||
restriction = ifctester.ids.Restriction(options="(-|[0-9]{2,3})\/(-|[0-9]{2,3})\/(-|[0-9]{2,3})", type="pattern")
|
||||
spec.requirements.append(ifctester.ids.Property(propertySet="Pset_WallCommon", name="FireRating", value=restriction, instructions="Fire rating is specified using the Fire Resistance Level as defined in the Australian National Construction Code (NCC) 2019. Valid examples include -/-/-, -/120/120, and 60/60/60"))
|
||||
spec.requirements.append(
|
||||
ifctester.ids.Property(
|
||||
propertySet="Pset_WallCommon",
|
||||
name="FireRating",
|
||||
value=restriction,
|
||||
instructions="Fire rating is specified using the Fire Resistance Level as defined in the Australian National Construction Code (NCC) 2019. Valid examples include -/-/-, -/120/120, and 60/60/60",
|
||||
)
|
||||
)
|
||||
|
||||
specs.to_xml(os.path.join(outdir, "library", "sample.ids"))
|
||||
|
||||
@@ -583,14 +583,14 @@ class TestAttribute:
|
||||
run(
|
||||
"Durations are treated as strings 1/2",
|
||||
facet=facet,
|
||||
inst=ifc.createIfcClassification(Name="Name", EditionDate="PT16H"),
|
||||
expected=False,
|
||||
inst=ifc.createIfcTaskTime(Name="Name", ScheduleDuration="PT16H"),
|
||||
expected=True,
|
||||
)
|
||||
ifc = ifcopenshell.file()
|
||||
run(
|
||||
"Durations are treated as strings 2/2",
|
||||
facet=facet,
|
||||
inst=ifc.createIfcClassification(Name="Name", EditionDate="P2D"),
|
||||
inst=ifc.createIfcTaskTime(Name="Name", ScheduleDuration="P2D"),
|
||||
expected=False,
|
||||
)
|
||||
|
||||
@@ -598,30 +598,27 @@ class TestAttribute:
|
||||
facet = Attribute(name=restriction)
|
||||
ifc = ifcopenshell.file()
|
||||
run(
|
||||
"Name restrictions may be used 1/4",
|
||||
"Name restrictions will match any result 1/3",
|
||||
facet=facet,
|
||||
inst=ifc.createIfcMaterialLayerSet(
|
||||
MaterialLayers=[ifc.createIfcMaterialLayer(LayerThickness=1)], LayerSetName="Foo"
|
||||
),
|
||||
expected=True,
|
||||
)
|
||||
ifc = ifcopenshell.file()
|
||||
run(
|
||||
"Name restrictions may be used 2/4",
|
||||
facet=facet,
|
||||
inst=ifc.createIfcMaterialConstituentSet(Name="Foo"),
|
||||
expected=True,
|
||||
)
|
||||
|
||||
restriction = Restriction(options=["Name", "Description"], type="enumeration")
|
||||
facet = Attribute(name=restriction)
|
||||
ifc = ifcopenshell.file()
|
||||
run("Name restrictions may be used 3/4", facet=facet, inst=ifc.createIfcWall(Name="Foo"), expected=False)
|
||||
run(
|
||||
"Name restrictions will match any result 2/3",
|
||||
facet=facet,
|
||||
inst=ifc.createIfcWall(Name="Foo"),
|
||||
expected=True,
|
||||
)
|
||||
ifc = ifcopenshell.file()
|
||||
run(
|
||||
"Name restrictions may be used 4/4",
|
||||
"Name restrictions will match any result 3/3",
|
||||
facet=facet,
|
||||
inst=ifc.createIfcWall(Name="Foo", Description="Bar"),
|
||||
inst=ifc.createIfcWall(Description="Bar"),
|
||||
expected=True,
|
||||
)
|
||||
|
||||
@@ -639,6 +636,7 @@ class TestAttribute:
|
||||
wall_type = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWallType")
|
||||
ifcopenshell.api.run("type.assign_type", ifc, related_object=wall, relating_type=wall_type)
|
||||
wall_type.Description = "Foobar"
|
||||
facet = Attribute(name="Description", value="Foobar")
|
||||
run("Attributes are not inherited by the occurrence", facet=facet, inst=wall, expected=False)
|
||||
|
||||
restriction = Restriction(options=["42"], type="enumeration", base="string")
|
||||
@@ -857,18 +855,7 @@ class TestProperty:
|
||||
def test_filtering_using_a_property_facet(self):
|
||||
set_facet("property")
|
||||
|
||||
ifc = ifcopenshell.file()
|
||||
ifc.createIfcProject()
|
||||
# Milli prefix used to check measurement conversions
|
||||
lengthunit = ifcopenshell.api.run("unit.add_si_unit", ifc, unit_type="LENGTHUNIT", name="METRE", prefix="MILLI")
|
||||
areaunit = ifcopenshell.api.run(
|
||||
"unit.add_si_unit", ifc, unit_type="AREAUNIT", name="SQUARE_METRE", prefix="MILLI"
|
||||
)
|
||||
volumeunit = ifcopenshell.api.run(
|
||||
"unit.add_si_unit", ifc, unit_type="VOLUMEUNIT", name="CUBIC_METRE", prefix="MILLI"
|
||||
)
|
||||
timeunit = ifcopenshell.api.run("unit.add_si_unit", ifc, unit_type="TIMEUNIT", name="SECOND")
|
||||
ifcopenshell.api.run("unit.assign_unit", ifc, units=[lengthunit, areaunit, volumeunit, timeunit])
|
||||
ifc = self.setup_ifc()
|
||||
|
||||
facet = Property(propertySet="Foo_Bar", name="Foo", measure="IfcLabel")
|
||||
element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall")
|
||||
@@ -876,11 +863,12 @@ class TestProperty:
|
||||
pset = ifcopenshell.api.run("pset.add_pset", ifc, product=element, name="Foo_Bar")
|
||||
ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"AnotherProperty": "AnotherValue"})
|
||||
run("Elements with a matching pset but no property also fail", facet=facet, inst=element, expected=False)
|
||||
ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foo": None})
|
||||
ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"AnotherProperty": None})
|
||||
run("Properties with a null value fail", facet=facet, inst=element, expected=False)
|
||||
ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foo": "Bar"})
|
||||
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", measure="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")
|
||||
@@ -912,6 +900,7 @@ class TestProperty:
|
||||
expected=True,
|
||||
)
|
||||
|
||||
ifc = self.setup_ifc()
|
||||
facet = Property(propertySet="Foo_Bar", name="Foo", value="Bar", measure="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")
|
||||
@@ -933,6 +922,7 @@ class TestProperty:
|
||||
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", measure="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")
|
||||
@@ -1009,6 +999,7 @@ class TestProperty:
|
||||
ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foo": ifc.createIfcDuration("P2D")})
|
||||
run("Durations are treated as strings 1/2", 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="Pset_WallCommon")
|
||||
pset_template = ifcopenshell.util.pset.get_template("IFC4").get_by_name("Pset_WallCommon")
|
||||
@@ -1026,6 +1017,7 @@ class TestProperty:
|
||||
facet = Property(propertySet="Pset_WallCommon", name="Status", value="NEW", measure="IfcLabel")
|
||||
run("Any matching value in an enumerated 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")
|
||||
list_property = ifc.createIfcPropertyListValue(
|
||||
@@ -1039,6 +1031,7 @@ class TestProperty:
|
||||
facet = Property(propertySet="Foo_Bar", name="Foo", value="Z", measure="IfcLabel")
|
||||
run("Any matching value in a list 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")
|
||||
bounded_property = ifc.createIfcPropertyBoundedValue(
|
||||
@@ -1057,6 +1050,7 @@ class TestProperty:
|
||||
facet = Property(propertySet="Foo_Bar", name="Foo", value="2", measure="IfcLengthMeasure")
|
||||
run("Any matching value in a bounded property will pass 4/4", 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")
|
||||
table_property = ifc.createIfcPropertyTableValue(
|
||||
@@ -1070,12 +1064,14 @@ class TestProperty:
|
||||
facet = Property(propertySet="Foo_Bar", name="Foo", value="Y", measure="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", measure="IfcLabel")
|
||||
run("Reference properties are treated as objects and not supported", facet=facet, inst=element, expected=False)
|
||||
|
||||
ifc = self.setup_ifc()
|
||||
element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcDoor")
|
||||
pset = ifc.create_entity(
|
||||
"IfcDoorPanelProperties",
|
||||
@@ -1099,6 +1095,7 @@ class TestProperty:
|
||||
)
|
||||
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", measure="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")
|
||||
@@ -1107,6 +1104,7 @@ class TestProperty:
|
||||
facet = Property(propertySet="Foo_Bar", name="Foo", measure="IfcAreaMeasure")
|
||||
run("Quantities must also match the appropriate measure", 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")
|
||||
complex_property = ifc.createIfcComplexProperty(Name="Foo", UsageName="RabbitAgilityTraining")
|
||||
@@ -1117,6 +1115,7 @@ class TestProperty:
|
||||
facet = Property(propertySet="Foo", name="Rabbits", measure="IfcLabel")
|
||||
run("Complex properties are not supported 2/2", facet=facet, inst=element, expected=False)
|
||||
|
||||
ifc = self.setup_ifc()
|
||||
element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall")
|
||||
qto = ifcopenshell.api.run("pset.add_qto", ifc, product=element, name="Foo_Bar")
|
||||
complex_quantity = ifc.createIfcPhysicalComplexQuantity(Name="Foo", Discrimination="FurThickness")
|
||||
@@ -1129,6 +1128,7 @@ class TestProperty:
|
||||
facet = Property(propertySet="Foo", name="MyLength", measure="IfcLengthMeasure")
|
||||
run("Complex properties are not supported 2/2", facet=facet, inst=element, expected=False)
|
||||
|
||||
ifc = self.setup_ifc()
|
||||
restriction = Restriction(options="Foo_.*", type="pattern")
|
||||
facet = Property(propertySet=restriction, name="Foo", measure="IfcLabel")
|
||||
element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall")
|
||||
@@ -1141,6 +1141,7 @@ class TestProperty:
|
||||
ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foo": "Bar"})
|
||||
run("All matching property sets must satisfy requirements 3/3", facet=facet, inst=element, expected=True)
|
||||
|
||||
ifc = self.setup_ifc()
|
||||
restriction = Restriction(options="Foo.*", type="pattern")
|
||||
facet = Property(propertySet="Foo_Bar", name=restriction, value="x", measure="IfcLabel")
|
||||
element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall")
|
||||
@@ -1152,6 +1153,7 @@ class TestProperty:
|
||||
ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foobar": "x", "Foobaz": "y"})
|
||||
run("All matching properties must satisfy requirements 3/3", facet=facet, inst=element, expected=False)
|
||||
|
||||
ifc = self.setup_ifc()
|
||||
restriction1 = Restriction(options="Foo.*", type="pattern")
|
||||
restriction2 = Restriction(options=["x", "y"], type="enumeration")
|
||||
facet = Property(propertySet="Foo_Bar", name=restriction1, value=restriction2, measure="IfcLabel")
|
||||
@@ -1172,6 +1174,7 @@ class TestProperty:
|
||||
expected=False,
|
||||
)
|
||||
|
||||
ifc = self.setup_ifc()
|
||||
facet = Property(propertySet="Foo_Bar", name="Foo", value="2", measure="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")
|
||||
@@ -1180,6 +1183,7 @@ class TestProperty:
|
||||
ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foo": ifc.createIfcTimeMeasure(2)})
|
||||
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", measure="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")
|
||||
@@ -1198,6 +1202,7 @@ class TestProperty:
|
||||
expected=True,
|
||||
)
|
||||
|
||||
ifc = self.setup_ifc()
|
||||
wall = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall")
|
||||
wall_type = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWallType")
|
||||
ifcopenshell.api.run("type.assign_type", ifc, related_object=wall, relating_type=wall_type)
|
||||
@@ -1207,6 +1212,7 @@ class TestProperty:
|
||||
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)
|
||||
|
||||
ifc = self.setup_ifc()
|
||||
wall = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall")
|
||||
wall_type = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWallType")
|
||||
ifcopenshell.api.run("type.assign_type", ifc, related_object=wall, relating_type=wall_type)
|
||||
@@ -1218,6 +1224,21 @@ class TestProperty:
|
||||
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)
|
||||
|
||||
def setup_ifc(self):
|
||||
ifc = ifcopenshell.file()
|
||||
ifc.createIfcProject()
|
||||
# Milli prefix used to check measurement conversions
|
||||
lengthunit = ifcopenshell.api.run("unit.add_si_unit", ifc, unit_type="LENGTHUNIT", name="METRE", prefix="MILLI")
|
||||
areaunit = ifcopenshell.api.run(
|
||||
"unit.add_si_unit", ifc, unit_type="AREAUNIT", name="SQUARE_METRE", prefix="MILLI"
|
||||
)
|
||||
volumeunit = ifcopenshell.api.run(
|
||||
"unit.add_si_unit", ifc, unit_type="VOLUMEUNIT", name="CUBIC_METRE", prefix="MILLI"
|
||||
)
|
||||
timeunit = ifcopenshell.api.run("unit.add_si_unit", ifc, unit_type="TIMEUNIT", name="SECOND")
|
||||
ifcopenshell.api.run("unit.assign_unit", ifc, units=[lengthunit, areaunit, volumeunit, timeunit])
|
||||
return ifc
|
||||
|
||||
|
||||
class TestMaterial:
|
||||
def test_creating_a_material_facet(self):
|
||||
@@ -1258,7 +1279,6 @@ class TestMaterial:
|
||||
element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall")
|
||||
material = ifcopenshell.api.run("material.add_material", ifc)
|
||||
ifcopenshell.api.run("material.assign_material", ifc, product=element, material=material)
|
||||
run("Material with no data will fail a value check", facet=facet, inst=element, expected=False)
|
||||
material.Name = "Foo"
|
||||
run("A material name may pass the value check", facet=facet, inst=element, expected=True)
|
||||
material.Name = "Bar"
|
||||
@@ -1270,7 +1290,6 @@ class TestMaterial:
|
||||
element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall")
|
||||
material_set = ifcopenshell.api.run("material.add_material_set", ifc, set_type="IfcMaterialList")
|
||||
ifcopenshell.api.run("material.assign_material", ifc, product=element, material=material_set)
|
||||
run("A material list with no data will fail a value check", facet=facet, inst=element, expected=False)
|
||||
material = ifcopenshell.api.run("material.add_material", ifc)
|
||||
ifcopenshell.api.run("material.add_list_item", ifc, material_list=material_set, material=material)
|
||||
material.Name = "Foo"
|
||||
@@ -1418,6 +1437,7 @@ class TestPartOf:
|
||||
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")
|
||||
subelement = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcBeam")
|
||||
ifcopenshell.api.run("aggregate.assign_object", ifc, product=subelement, relating_object=element)
|
||||
@@ -1426,6 +1446,7 @@ class TestPartOf:
|
||||
facet = PartOf(entity="IFCWALL", relation="IfcRelAggregates")
|
||||
run("An aggregate may specify the entity of the whole 2/2", facet=facet, inst=subelement, expected=False)
|
||||
|
||||
ifc = ifcopenshell.file()
|
||||
element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcElementAssembly")
|
||||
subelement = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcSlab")
|
||||
subsubelement = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcBeam")
|
||||
@@ -1434,6 +1455,7 @@ class TestPartOf:
|
||||
facet = PartOf(entity="IFCELEMENTASSEMBLY", relation="IfcRelAggregates")
|
||||
run("An aggregate entity may pass any ancestral whole passes", facet=facet, inst=subsubelement, expected=True)
|
||||
|
||||
ifc = ifcopenshell.file()
|
||||
element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcElementAssembly")
|
||||
group = ifcopenshell.api.run("group.add_group", ifc)
|
||||
facet = PartOf(relation="IfcRelAssignsToGroup")
|
||||
@@ -1441,6 +1463,7 @@ class TestPartOf:
|
||||
ifcopenshell.api.run("group.assign_group", ifc, products=[element], group=group)
|
||||
run("A grouped element passes a group relationship", facet=facet, inst=element, expected=True)
|
||||
|
||||
ifc = ifcopenshell.file()
|
||||
element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcElementAssembly")
|
||||
group = ifc.createIfcInventory()
|
||||
facet = PartOf(entity="IFCGROUP", relation="IfcRelAssignsToGroup")
|
||||
@@ -1449,6 +1472,7 @@ class TestPartOf:
|
||||
facet = PartOf(entity="IFCINVENTORY", relation="IfcRelAssignsToGroup")
|
||||
run("A group entity must match exactly 2/2", facet=facet, inst=element, expected=True)
|
||||
|
||||
ifc = ifcopenshell.file()
|
||||
element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcElementAssembly")
|
||||
container = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcSpace")
|
||||
facet = PartOf(relation="IfcRelContainedInSpatialStructure")
|
||||
@@ -1457,6 +1481,7 @@ class TestPartOf:
|
||||
run("Any contained element passes a containment relationship 2/2", facet=facet, inst=element, expected=True)
|
||||
run("The container itself always fails", facet=facet, inst=container, expected=False)
|
||||
|
||||
ifc = ifcopenshell.file()
|
||||
element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcElementAssembly")
|
||||
container = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcSpace")
|
||||
ifcopenshell.api.run("spatial.assign_container", ifc, product=element, relating_structure=container)
|
||||
@@ -1465,6 +1490,7 @@ class TestPartOf:
|
||||
facet = PartOf(relation="IfcRelContainedInSpatialStructure", entity="IFCSPACE")
|
||||
run("The container entity must match exactly 2/2", facet=facet, inst=element, expected=True)
|
||||
|
||||
ifc = ifcopenshell.file()
|
||||
element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcSlab")
|
||||
subelement = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcBeam")
|
||||
ifcopenshell.api.run("aggregate.assign_object", ifc, product=subelement, relating_object=element)
|
||||
@@ -1473,6 +1499,7 @@ class TestPartOf:
|
||||
facet = PartOf(relation="IfcRelContainedInSpatialStructure", entity="IFCSPACE")
|
||||
run("The container may be indirect", facet=facet, inst=subelement, expected=True)
|
||||
|
||||
ifc = ifcopenshell.file()
|
||||
element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcFurniture")
|
||||
subelement = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcDiscreteAccessory")
|
||||
ifcopenshell.api.run("nest.assign_object", ifc, related_object=subelement, relating_object=element)
|
||||
@@ -1480,6 +1507,7 @@ class TestPartOf:
|
||||
run("Any nested part passes a nest relationship", facet=facet, inst=subelement, expected=True)
|
||||
run("Any nested whole fails a nest relationship", facet=facet, inst=element, expected=False)
|
||||
|
||||
ifc = ifcopenshell.file()
|
||||
element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcFurniture")
|
||||
subelement = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcDiscreteAccessory")
|
||||
ifcopenshell.api.run("nest.assign_object", ifc, related_object=subelement, relating_object=element)
|
||||
@@ -1488,6 +1516,7 @@ class TestPartOf:
|
||||
facet = PartOf(relation="IfcRelNests", entity="IFCFURNITURE")
|
||||
run("The nest entity must match exactly 2/2", facet=facet, inst=subelement, expected=True)
|
||||
|
||||
ifc = ifcopenshell.file()
|
||||
element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcFurniture")
|
||||
subelement = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcDiscreteAccessory")
|
||||
subsubelement = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcMechanicalFastener")
|
||||
@@ -1516,3 +1545,47 @@ class TestRestriction:
|
||||
assert restriction == "AB01"
|
||||
assert restriction != "AB"
|
||||
assert restriction != "01"
|
||||
|
||||
def test_filtering_using_an_enumeration(self):
|
||||
set_facet("restriction")
|
||||
|
||||
ifc = ifcopenshell.file()
|
||||
restriction = Restriction(options=["Foo", "Bar"], type="enumeration")
|
||||
facet = Attribute(name="Name", value=restriction)
|
||||
element = ifc.createIfcWall(Name="Foo")
|
||||
run("An enumeration matches case sensitively 1/3", facet=facet, inst=element, expected=True)
|
||||
element.Name = "Bar"
|
||||
run("An enumeration matches case sensitively 1/3", facet=facet, inst=element, expected=True)
|
||||
element.Name = "Baz"
|
||||
run("An enumeration matches case sensitively 1/3", facet=facet, inst=element, expected=False)
|
||||
|
||||
ifc = ifcopenshell.file()
|
||||
restriction = Restriction(options={"minInclusive": 0, "maxInclusive": 10}, type="bounds", base="integer")
|
||||
element = ifc.createIfcSurfaceStyleRefraction(RefractionIndex=0)
|
||||
facet = Attribute(name="RefractionIndex", value=restriction)
|
||||
run("A bound can be inclusive 1/4", facet=facet, inst=element, expected=True)
|
||||
element.RefractionIndex = 5
|
||||
run("A bound can be inclusive 2/4", facet=facet, inst=element, expected=True)
|
||||
element.RefractionIndex = 10
|
||||
run("A bound can be inclusive 3/4", facet=facet, inst=element, expected=True)
|
||||
element.RefractionIndex = 100
|
||||
run("A bound can be inclusive 4/4", facet=facet, inst=element, expected=False)
|
||||
|
||||
restriction = Restriction(options={"minExclusive": 0, "maxExclusive": 10}, type="bounds", base="integer")
|
||||
facet = Attribute(name="RefractionIndex", value=restriction)
|
||||
element.RefractionIndex = 0
|
||||
run("A bound can be inclusive 1/3", facet=facet, inst=element, expected=False)
|
||||
element.RefractionIndex = 5
|
||||
run("A bound can be inclusive 2/3", facet=facet, inst=element, expected=True)
|
||||
element.RefractionIndex = 10
|
||||
run("A bound can be inclusive 3/3", facet=facet, inst=element, expected=False)
|
||||
|
||||
ifc = ifcopenshell.file()
|
||||
restriction = Restriction(options="[A-Z]{2}[0-9]{2}", type="pattern")
|
||||
facet = Attribute(name="Name", value=restriction)
|
||||
element = ifc.createIfcWall(Name="WT01")
|
||||
run("Regex patterns can be used 1/3", facet=facet, inst=element, expected=True)
|
||||
element.Name = "XY99"
|
||||
run("Regex patterns can be used 2/3", facet=facet, inst=element, expected=True)
|
||||
element.Name = "A5"
|
||||
run("Regex patterns can be used 3/3", facet=facet, inst=element, expected=False)
|
||||
|
||||
@@ -23,6 +23,14 @@ import ifcopenshell
|
||||
from ifctester import ids
|
||||
|
||||
|
||||
def run(name, specs, model, expected, applicable_entities, failed_entities):
|
||||
specs.validate(model)
|
||||
spec = specs.specifications[0]
|
||||
assert spec.status is expected
|
||||
assert set(spec.applicable_entities) == set(applicable_entities)
|
||||
assert set(spec.requirements[0].failed_entities) == set(failed_entities)
|
||||
|
||||
|
||||
class TestIds:
|
||||
def test_failing_on_opening_invalid_ids_data(self):
|
||||
with pytest.raises(xmlschema.validators.exceptions.XMLSchemaValidationError):
|
||||
@@ -106,11 +114,73 @@ class TestIds:
|
||||
model = ifcopenshell.file()
|
||||
wall = model.createIfcWall()
|
||||
waldo = model.createIfcWall(Name="Waldo")
|
||||
specs.validate(model)
|
||||
run("A minimal IDS can check a minimal IFC 1/2", specs, model, False, [wall, waldo], [wall])
|
||||
wall.Name = "Waldo"
|
||||
run("A minimal IDS can check a minimal IFC 2/2", specs, model, True, [wall, waldo], [])
|
||||
|
||||
assert spec.status == False
|
||||
assert set(spec.applicable_entities) == {wall, waldo}
|
||||
assert spec.requirements[0].failed_entities == [wall]
|
||||
spec.ifcVersion = []
|
||||
run(
|
||||
"Specification version is purely metadata and does not impact pass or fail result",
|
||||
specs,
|
||||
model,
|
||||
True,
|
||||
[wall, waldo],
|
||||
)
|
||||
|
||||
spec.minOccurs = 1
|
||||
model = ifcopenshell.file()
|
||||
waldo = model.createIfcWall(Name="Waldo")
|
||||
run("Required specifications need at least one applicable entity 1/2", specs, model, True, [waldo])
|
||||
model = ifcopenshell.file()
|
||||
waldo = model.createIfcSlab(Name="Waldo")
|
||||
run("Required specifications need at least one applicable entity 2/2", specs, model, False)
|
||||
|
||||
spec.minOccurs = 0
|
||||
model = ifcopenshell.file()
|
||||
waldo = model.createIfcSlab(Name="Waldo")
|
||||
run("Optional specifications may still pass if nothing is applicable", specs, model, True)
|
||||
|
||||
spec.minOccurs = 0
|
||||
spec.maxOccurs = 0
|
||||
model = ifcopenshell.file()
|
||||
wall = model.createIfcSlab(Name="Waldo")
|
||||
run("Prohibited specifications fail if at least one entity passes all requirements 1/3", specs, model, True)
|
||||
model = ifcopenshell.file()
|
||||
wall = model.createIfcWall(Name="Wally")
|
||||
run("Prohibited specifications fail if at least one entity passes all requirements 2/3", specs, model, True, [wall], [wall])
|
||||
model = ifcopenshell.file()
|
||||
wall = model.createIfcWall(Name="Waldo")
|
||||
run("Prohibited specifications fail if at least one entity passes all requirements 3/3", specs, model, False, [wall])
|
||||
|
||||
spec.minOccurs = 0
|
||||
spec.maxOccurs = "unbounded"
|
||||
model = ifcopenshell.file()
|
||||
wall = model.createIfcWall(Name="Waldo")
|
||||
spec.requirements.append(ids.Attribute(name="Description", value="Foobar"))
|
||||
run("A specification passes only if all requirements pass 1/2", specs, model, False, [wall], [wall])
|
||||
wall.Description = "Foobar"
|
||||
run("A specification passes only if all requirements pass 2/2", specs, model, True, [wall])
|
||||
|
||||
spec.requirements[1].minOccurs = 0
|
||||
wall.Description = None
|
||||
run("Specification optionality and facet optionality can be combined", specs, model, True, [wall])
|
||||
|
||||
spec.minOccurs = 0
|
||||
spec.maxOccurs = 0
|
||||
spec.requirements[0].minOccurs = 0
|
||||
spec.requirements[0].maxOccurs = 0
|
||||
spec.requirements[1].minOccurs = 0
|
||||
spec.requirements[1].maxOccurs = 0
|
||||
wall.Name = "Waldo"
|
||||
wall.Description = "Foobar"
|
||||
run("A prohibited specification and a prohibited facet results in a double negative", specs, model, True, [wall])
|
||||
|
||||
spec = ids.Specification(name="Name")
|
||||
spec.applicability.append(ids.Entity(name="IFCWALL"))
|
||||
spec.requirements.append(ids.Attribute(name="Name", value="Waldo"))
|
||||
specs.specifications.append(spec)
|
||||
wall2 = model.createIfcWall(Name="Waldo")
|
||||
run("Multiple specifications are independent of one another", specs, model, True, [wall, wall2])
|
||||
|
||||
def test_creating_multiple_specifications(self):
|
||||
specs = ids.Ids(title="Title")
|
||||
@@ -138,9 +208,12 @@ 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": {},
|
||||
}
|
||||
@@ -148,8 +221,8 @@ class TestSpecification:
|
||||
def test_create_specification_with_all_possible_information(self):
|
||||
spec = ids.Specification(
|
||||
name="name",
|
||||
minOccurs="0",
|
||||
maxOccurs="unbounded",
|
||||
minOccurs=1,
|
||||
maxOccurs=1,
|
||||
ifcVersion="IFC4",
|
||||
identifier="identifier",
|
||||
description="description",
|
||||
@@ -157,8 +230,8 @@ class TestSpecification:
|
||||
)
|
||||
assert spec.asdict() == {
|
||||
"@name": "name",
|
||||
"@minOccurs": "0",
|
||||
"@maxOccurs": "unbounded",
|
||||
"@minOccurs": 1,
|
||||
"@maxOccurs": 1,
|
||||
"@ifcVersion": "IFC4",
|
||||
"@identifier": "identifier",
|
||||
"@description": "description",
|
||||
|
||||
Reference in New Issue
Block a user