mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-10 17:58:20 +00:00
Implement newly proposed IDS PartOf facet
This commit is contained in:
@@ -232,7 +232,7 @@ class Attribute(Facet):
|
||||
elif isinstance(self.value, str):
|
||||
cast_value = cast_to_value(self.value, value)
|
||||
if isinstance(value, float) and isinstance(cast_value, float):
|
||||
if value < cast_value * (1. - 1e-6) or value > cast_value * (1. + 1e-6):
|
||||
if value < cast_value * (1.0 - 1e-6) or value > cast_value * (1.0 + 1e-6):
|
||||
is_pass = False
|
||||
reason = {"type": "VALUE", "actual": value}
|
||||
break
|
||||
@@ -295,28 +295,79 @@ class Classification(Facet):
|
||||
|
||||
|
||||
class PartOf(Facet):
|
||||
def __init__(self, entity="IfcSystem"):
|
||||
self.parameters = ["@entity"]
|
||||
self.applicability_templates = ["An element part of a {entity}"]
|
||||
self.requirement_templates = ["Must be part of a {entity}"]
|
||||
super().__init__(entity)
|
||||
def __init__(self, entity=None, relation="IfcRelAggregates", instructions=None):
|
||||
self.parameters = ["entity", "@relation", "@instructions"]
|
||||
self.applicability_templates = [
|
||||
"An element with an {relation} relationship with an {entity}",
|
||||
"An element with an {relation} relationship",
|
||||
]
|
||||
self.requirement_templates = [
|
||||
"An element must have an {relation} relationship with an {entity}",
|
||||
"An element must have an {relation} relationship",
|
||||
]
|
||||
super().__init__(entity, relation, instructions)
|
||||
|
||||
def __call__(self, inst, logger=None):
|
||||
if self.entity == "IfcElementAssembly":
|
||||
is_pass = False
|
||||
reason = None
|
||||
if self.relation == "IfcRelAggregates":
|
||||
aggregate = ifcopenshell.util.element.get_aggregate(inst)
|
||||
while aggregate is not None:
|
||||
if aggregate.is_a() == "IfcElementAssembly":
|
||||
is_pass = True
|
||||
break
|
||||
aggregate = ifcopenshell.util.element.get_aggregate(aggregate)
|
||||
else:
|
||||
is_pass = False
|
||||
is_pass = aggregate is not None
|
||||
if not is_pass:
|
||||
reason = {"type": "RELATION"}
|
||||
if is_pass and self.entity:
|
||||
is_pass = False
|
||||
ancestors = []
|
||||
while aggregate is not None:
|
||||
ancestors.append(aggregate.is_a())
|
||||
if aggregate.is_a().upper() == self.entity:
|
||||
is_pass = True
|
||||
break
|
||||
aggregate = ifcopenshell.util.element.get_aggregate(aggregate)
|
||||
if not is_pass:
|
||||
reason = {"type": "ENTITY", "actual": ancestors}
|
||||
elif self.relation == "IfcRelAssignsToGroup":
|
||||
group = None
|
||||
for rel in getattr(inst, "HasAssignments", []) or []:
|
||||
if rel.is_a("IfcRelAssignsToGroup") and rel.RelatingGroup.is_a(self.entity):
|
||||
is_pass = True
|
||||
if rel.is_a("IfcRelAssignsToGroup"):
|
||||
group = rel.RelatingGroup
|
||||
break
|
||||
is_pass = group is not None
|
||||
if not is_pass:
|
||||
reason = {"type": "NOVALUE"}
|
||||
if is_pass and self.entity:
|
||||
if group.is_a().upper() != self.entity:
|
||||
is_pass = False
|
||||
reason = {"type": "ENTITY", "actual": group.is_a().upper()}
|
||||
elif self.relation == "IfcRelContainedInSpatialStructure":
|
||||
container = ifcopenshell.util.element.get_container(inst)
|
||||
is_pass = container is not None
|
||||
if not is_pass:
|
||||
reason = {"type": "RELATION"}
|
||||
if is_pass and self.entity:
|
||||
if container.is_a().upper() != self.entity:
|
||||
is_pass = False
|
||||
reason = {"type": "ENTITY", "actual": container.is_a().upper()}
|
||||
elif self.relation == "IfcRelNests":
|
||||
nest = self.get_nested_whole(inst)
|
||||
is_pass = nest is not None
|
||||
if not is_pass:
|
||||
reason = {"type": "NOVALUE"}
|
||||
if is_pass and self.entity:
|
||||
is_pass = False
|
||||
ancestors = []
|
||||
while nest is not None:
|
||||
ancestors.append(nest.is_a())
|
||||
if nest.is_a().upper() == self.entity:
|
||||
is_pass = True
|
||||
break
|
||||
nest = self.get_nested_whole(nest)
|
||||
if not is_pass:
|
||||
reason = {"type": "ENTITY", "actual": ancestors}
|
||||
return PartOfResult(is_pass, reason)
|
||||
|
||||
return PartOfResult(is_pass, "TODO")
|
||||
def get_nested_whole(self, element):
|
||||
for rel in getattr(element, "Nests", []) or []:
|
||||
return rel.RelatingObject
|
||||
|
||||
|
||||
class Property(Facet):
|
||||
@@ -426,7 +477,7 @@ class Property(Facet):
|
||||
elif isinstance(self.value, str):
|
||||
cast_value = cast_to_value(self.value, value)
|
||||
if isinstance(value, float) and isinstance(cast_value, float):
|
||||
if value < cast_value * (1. - 1e-6) or value > cast_value * (1. + 1e-6):
|
||||
if value < cast_value * (1.0 - 1e-6) or value > cast_value * (1.0 + 1e-6):
|
||||
is_pass = False
|
||||
reason = {"type": "VALUE", "actual": value}
|
||||
break
|
||||
|
||||
@@ -56,9 +56,16 @@
|
||||
<xs:element name="system" type="ids:idsValue" minOccurs="0"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="partOfType">
|
||||
<xs:sequence>
|
||||
<xs:element name="entity" type="ids:idsValue" minOccurs="0"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="relation" type="ids:relations" use="required"/>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="applicabilityType">
|
||||
<xs:sequence>
|
||||
<xs:element name="entity" type="ids:entityType" minOccurs="0"/>
|
||||
<xs:element name="partOf" type="ids:partOfType" minOccurs="0"/>
|
||||
<xs:element name="classification" type="ids:classificationType" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:element name="attribute" type="ids:attributeType" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:element name="property" minOccurs="0" maxOccurs="unbounded">
|
||||
@@ -115,17 +122,16 @@
|
||||
</xs:element>
|
||||
<xs:element name="partOf" minOccurs="0">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="entity" maxOccurs="unbounded">
|
||||
<xs:complexType>
|
||||
<xs:complexContent>
|
||||
<xs:extension base="ids:idsValue">
|
||||
<xs:attribute name="relation" type="ids:relations" use="required"/>
|
||||
</xs:extension>
|
||||
</xs:complexContent>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:sequence>
|
||||
<xs:complexContent>
|
||||
<xs:extension base="ids:partOfType">
|
||||
<xs:attributeGroup ref="xs:occurs"/>
|
||||
<xs:attribute name="instructions" type="xs:string" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Author of the IDS can leave instructions for the authors of the IFC. This text could/should be displayed in the BIM/IFC authoring tool.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:extension>
|
||||
</xs:complexContent>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="classification" minOccurs="0" maxOccurs="unbounded">
|
||||
@@ -242,4 +248,4 @@
|
||||
<xs:enumeration value="IfcRelNests"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
</xs:schema>
|
||||
</xs:schema>
|
||||
|
||||
@@ -492,25 +492,25 @@ class TestAttribute:
|
||||
run(
|
||||
"Floating point numbers are compared with a 1e-6 tolerance 1/4",
|
||||
facet=facet,
|
||||
inst=ifc.createIfcSurfaceStyleRefraction(RefractionIndex=42. * (1. + 1e-6)),
|
||||
inst=ifc.createIfcSurfaceStyleRefraction(RefractionIndex=42.0 * (1.0 + 1e-6)),
|
||||
expected=True,
|
||||
)
|
||||
run(
|
||||
"Floating point numbers are compared with a 1e-6 tolerance 2/4",
|
||||
facet=facet,
|
||||
inst=ifc.createIfcSurfaceStyleRefraction(RefractionIndex=42. * (1. - 1e-6)),
|
||||
inst=ifc.createIfcSurfaceStyleRefraction(RefractionIndex=42.0 * (1.0 - 1e-6)),
|
||||
expected=True,
|
||||
)
|
||||
run(
|
||||
"Floating point numbers are compared with a 1e-6 tolerance 3/4",
|
||||
facet=facet,
|
||||
inst=ifc.createIfcSurfaceStyleRefraction(RefractionIndex=42. * (1. + 2e-6)),
|
||||
inst=ifc.createIfcSurfaceStyleRefraction(RefractionIndex=42.0 * (1.0 + 2e-6)),
|
||||
expected=False,
|
||||
)
|
||||
run(
|
||||
"Floating point numbers are compared with a 1e-6 tolerance 4/4",
|
||||
facet=facet,
|
||||
inst=ifc.createIfcSurfaceStyleRefraction(RefractionIndex=42. * (1. - 2e-6)),
|
||||
inst=ifc.createIfcSurfaceStyleRefraction(RefractionIndex=42.0 * (1.0 - 2e-6)),
|
||||
expected=False,
|
||||
)
|
||||
|
||||
@@ -931,13 +931,21 @@ class TestProperty:
|
||||
run("Only specifically formatted numbers are allowed 4/4", facet=facet, inst=element, expected=True)
|
||||
|
||||
facet = Property(propertySet="Foo_Bar", name="Foo", value="42.")
|
||||
ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foo": ifc.createIfcReal(42. * (1. + 1e-6))})
|
||||
ifcopenshell.api.run(
|
||||
"pset.edit_pset", ifc, pset=pset, properties={"Foo": ifc.createIfcReal(42.0 * (1.0 + 1e-6))}
|
||||
)
|
||||
run("Floating point numbers are compared with a 1e-6 tolerance 1/4", facet=facet, inst=element, expected=True)
|
||||
ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foo": ifc.createIfcReal(42. * (1. - 1e-6))})
|
||||
ifcopenshell.api.run(
|
||||
"pset.edit_pset", ifc, pset=pset, properties={"Foo": ifc.createIfcReal(42.0 * (1.0 - 1e-6))}
|
||||
)
|
||||
run("Floating point numbers are compared with a 1e-6 tolerance 2/4", facet=facet, inst=element, expected=True)
|
||||
ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foo": ifc.createIfcReal(42. * (1. + 2e-6))})
|
||||
ifcopenshell.api.run(
|
||||
"pset.edit_pset", ifc, pset=pset, properties={"Foo": ifc.createIfcReal(42.0 * (1.0 + 2e-6))}
|
||||
)
|
||||
run("Floating point numbers are compared with a 1e-6 tolerance 3/4", facet=facet, inst=element, expected=False)
|
||||
ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foo": ifc.createIfcReal(42. * (1. - 2e-6))})
|
||||
ifcopenshell.api.run(
|
||||
"pset.edit_pset", ifc, pset=pset, properties={"Foo": ifc.createIfcReal(42.0 * (1.0 - 2e-6))}
|
||||
)
|
||||
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")
|
||||
@@ -1017,9 +1025,19 @@ class TestProperty:
|
||||
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)})
|
||||
run("Unit conversions shall take place to IDS-nominated standard units 1/2", facet=facet, inst=element, expected=False)
|
||||
run(
|
||||
"Unit conversions shall take place to IDS-nominated standard units 1/2",
|
||||
facet=facet,
|
||||
inst=element,
|
||||
expected=False,
|
||||
)
|
||||
ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foo": ifc.createIfcLengthMeasure(2000)})
|
||||
run("Unit conversions shall take place to IDS-nominated standard units 2/2", facet=facet, inst=element, expected=True)
|
||||
run(
|
||||
"Unit conversions shall take place to IDS-nominated standard units 2/2",
|
||||
facet=facet,
|
||||
inst=element,
|
||||
expected=True,
|
||||
)
|
||||
|
||||
wall = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall")
|
||||
wall_type = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWallType")
|
||||
@@ -1197,69 +1215,102 @@ class TestMaterial:
|
||||
class TestPartOf:
|
||||
def test_creating_a_partof_facet(self):
|
||||
facet = PartOf()
|
||||
assert facet.asdict() == {"@entity": "IfcSystem"}
|
||||
facet = PartOf(entity="IfcGroup")
|
||||
assert facet.asdict() == {"@entity": "IfcGroup"}
|
||||
assert facet.asdict() == {"@relation": "IfcRelAggregates"}
|
||||
facet = PartOf(entity="IfcGroup", relation="IfcRelAssignsToGroup", instructions="instructions")
|
||||
assert facet.asdict() == {
|
||||
"entity": {"simpleValue": "IfcGroup"},
|
||||
"@relation": "IfcRelAssignsToGroup",
|
||||
"@instructions": "instructions",
|
||||
}
|
||||
|
||||
def test_filtering_using_a_partof_facet(self):
|
||||
ifc = ifcopenshell.file()
|
||||
|
||||
# An IfcElementAssembly entity only passes those who are part of an assembly
|
||||
element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcElementAssembly")
|
||||
subelement = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall")
|
||||
facet = PartOf(relation="IfcRelAggregates")
|
||||
run("A non aggregated element fails an aggregate relationship", facet=facet, inst=subelement, expected=False)
|
||||
ifcopenshell.api.run("aggregate.assign_object", ifc, product=subelement, relating_object=element)
|
||||
facet = PartOf(entity="IfcElementAssembly")
|
||||
run("", facet=facet, inst=element, expected=False)
|
||||
run("", facet=facet, inst=subelement, expected=True)
|
||||
run("The aggregated whole fails an aggregate relationship", facet=facet, inst=element, expected=False)
|
||||
run("The aggregated part passes an aggregate relationship", facet=facet, inst=subelement, expected=True)
|
||||
|
||||
# An IfcElementAssembly strictly checks that the whole is an IfcElementAssembly class
|
||||
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)
|
||||
facet = PartOf(entity="IfcElementAssembly")
|
||||
run("", facet=facet, inst=subelement, expected=False)
|
||||
facet = PartOf(entity="IFCSLAB", relation="IfcRelAggregates")
|
||||
run("An aggregate may specify the entity of the whole 1/2", facet=facet, inst=subelement, expected=True)
|
||||
facet = PartOf(entity="IFCWALL", relation="IfcRelAggregates")
|
||||
run("An aggregate may specify the entity of the whole 2/2", facet=facet, inst=subelement, expected=False)
|
||||
|
||||
# A nested subelement still passes so long as one of its parents is an IfcElementAssembly
|
||||
# TODO nononono
|
||||
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")
|
||||
ifcopenshell.api.run("aggregate.assign_object", ifc, product=subelement, relating_object=element)
|
||||
ifcopenshell.api.run("aggregate.assign_object", ifc, product=subsubelement, relating_object=subelement)
|
||||
facet = PartOf(entity="IfcElementAssembly")
|
||||
run("", facet=facet, inst=subsubelement, expected=True)
|
||||
facet = PartOf(entity="IFCELEMENTASSEMBLY", relation="IfcRelAggregates")
|
||||
run("An aggregate entity may pass any ancestral whole passes", facet=facet, inst=subsubelement, expected=True)
|
||||
|
||||
# An IfcGroup only checks that a group is assigned without any other logic
|
||||
element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcElementAssembly")
|
||||
group = ifcopenshell.api.run("group.add_group", ifc)
|
||||
facet = PartOf(entity="IfcGroup")
|
||||
run("", facet=facet, inst=element, expected=False)
|
||||
facet = PartOf(relation="IfcRelAssignsToGroup")
|
||||
run("A non grouped element fails a group relationship", facet=facet, inst=element, expected=False)
|
||||
ifcopenshell.api.run("group.assign_group", ifc, products=[element], group=group)
|
||||
run("", facet=facet, inst=element, expected=True)
|
||||
run("A grouped element passes a group relationship", facet=facet, inst=element, expected=True)
|
||||
|
||||
# An IfcGroup can be passed by subtypes
|
||||
# TODO: wrong, subtypes should not be matched
|
||||
element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcElementAssembly")
|
||||
group = ifc.createIfcInventory()
|
||||
facet = PartOf(entity="IfcGroup")
|
||||
facet = PartOf(entity="IFCGROUP", relation="IfcRelAssignsToGroup")
|
||||
ifcopenshell.api.run("group.assign_group", ifc, products=[element], group=group)
|
||||
run("", facet=facet, inst=element, expected=True)
|
||||
run("A group entity must match exactly 1/2", facet=facet, inst=element, expected=False)
|
||||
facet = PartOf(entity="IFCINVENTORY", relation="IfcRelAssignsToGroup")
|
||||
run("A group entity must match exactly 2/2", facet=facet, inst=element, expected=True)
|
||||
|
||||
# An IfcSystem only checks that a system is assigned without any other logic
|
||||
element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcElementAssembly")
|
||||
system = ifcopenshell.api.run("system.add_system", ifc)
|
||||
facet = PartOf(entity="IfcSystem")
|
||||
run("", facet=facet, inst=element, expected=False)
|
||||
ifcopenshell.api.run("system.assign_system", ifc, product=element, system=system)
|
||||
run("", facet=facet, inst=element, expected=True)
|
||||
container = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcSpace")
|
||||
facet = PartOf(relation="IfcRelContainedInSpatialStructure")
|
||||
run("Any contained element passes a containment relationship 1/2", facet=facet, inst=element, expected=False)
|
||||
ifcopenshell.api.run("spatial.assign_container", ifc, product=element, relating_structure=container)
|
||||
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)
|
||||
|
||||
# An IfcSystem allows subtypes
|
||||
# TODO: wrong, subtypes should not be matched
|
||||
element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcElementAssembly")
|
||||
system = ifcopenshell.api.run("system.add_system", ifc, ifc_class="IfcDistributionSystem")
|
||||
ifcopenshell.api.run("system.assign_system", ifc, product=element, system=system)
|
||||
facet = PartOf(entity="IfcSystem")
|
||||
run("", facet=facet, inst=element, expected=True)
|
||||
container = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcSpace")
|
||||
ifcopenshell.api.run("spatial.assign_container", ifc, product=element, relating_structure=container)
|
||||
facet = PartOf(relation="IfcRelContainedInSpatialStructure", entity="IFCSITE")
|
||||
run("The container entity must match exactly 1/2", facet=facet, inst=element, expected=False)
|
||||
facet = PartOf(relation="IfcRelContainedInSpatialStructure", entity="IFCSPACE")
|
||||
run("The container entity must match exactly 2/2", facet=facet, inst=element, expected=True)
|
||||
|
||||
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)
|
||||
container = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcSpace")
|
||||
ifcopenshell.api.run("spatial.assign_container", ifc, product=element, relating_structure=container)
|
||||
facet = PartOf(relation="IfcRelContainedInSpatialStructure", entity="IFCSPACE")
|
||||
run("The container may be indirect", facet=facet, inst=subelement, expected=True)
|
||||
|
||||
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)
|
||||
facet = PartOf(relation="IfcRelNests")
|
||||
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)
|
||||
|
||||
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)
|
||||
facet = PartOf(relation="IfcRelNests", entity="IFCBEAM")
|
||||
run("The nest entity must match exactly 1/2", facet=facet, inst=subelement, expected=False)
|
||||
facet = PartOf(relation="IfcRelNests", entity="IFCFURNITURE")
|
||||
run("The nest entity must match exactly 2/2", facet=facet, inst=subelement, expected=True)
|
||||
|
||||
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")
|
||||
ifcopenshell.api.run("nest.assign_object", ifc, related_object=subelement, relating_object=element)
|
||||
ifcopenshell.api.run("nest.assign_object", ifc, related_object=subsubelement, relating_object=subelement)
|
||||
facet = PartOf(relation="IfcRelNests", entity="IFCFURNITURE")
|
||||
run("Nesting may be indirect", facet=facet, inst=subsubelement, expected=True)
|
||||
|
||||
|
||||
class TestRestriction:
|
||||
|
||||
Reference in New Issue
Block a user