diff --git a/src/blenderbim/blenderbim/bim/helper.py b/src/blenderbim/blenderbim/bim/helper.py index 9a67c74812..0fb911234e 100644 --- a/src/blenderbim/blenderbim/bim/helper.py +++ b/src/blenderbim/blenderbim/bim/helper.py @@ -304,6 +304,9 @@ def draw_filter(layout, filter_groups, data, module): elif ifc_filter.type == "group": row = box.row(align=True) row.prop(ifc_filter, "value", text="", icon="OUTLINER_COLLECTION") + elif ifc_filter.type == "parent": + row = box.row(align=True) + row.prop(ifc_filter, "value", text="", icon="FILE_PARENT") elif ifc_filter.type == "query": row = box.row(align=True) row.prop(ifc_filter, "name", text="", icon="POINTCLOUD_DATA") diff --git a/src/blenderbim/blenderbim/bim/module/search/prop.py b/src/blenderbim/blenderbim/bim/module/search/prop.py index c8c5580e6e..a7f2cc7d25 100644 --- a/src/blenderbim/blenderbim/bim/module/search/prop.py +++ b/src/blenderbim/blenderbim/bim/module/search/prop.py @@ -118,8 +118,9 @@ class BIMSearchProperties(PropertyGroup): ("location", "Location", "", "PACKAGE", 5), ("type", "Type", "", "FILE_VOLUME", 6), ("group", "Group", "", "OUTLINER_COLLECTION", 7), - ("query", "Query", "", "POINTCLOUD_DATA", 8), - ("instance", "GlobalId", "", "GRIP", 9), + ("parent", "Parent", "", "FILE_PARENT", 8), + ("query", "Query", "", "POINTCLOUD_DATA", 9), + ("instance", "GlobalId", "", "GRIP", 10), ], ) saved_searches: EnumProperty(items=get_saved_searches, name="Saved Searches") diff --git a/src/blenderbim/blenderbim/tool/search.py b/src/blenderbim/blenderbim/tool/search.py index 984e359d93..dad303d15b 100644 --- a/src/blenderbim/blenderbim/tool/search.py +++ b/src/blenderbim/blenderbim/tool/search.py @@ -86,6 +86,9 @@ class Search(blenderbim.core.tool.Search): elif ifc_filter.type == "group": comparison, value = cls.get_comparison_and_value(ifc_filter) filter_group_query.append(f"group{comparison}{value}") + elif ifc_filter.type == "parent": + comparison, value = cls.get_comparison_and_value(ifc_filter) + filter_group_query.append(f"parent{comparison}{value}") elif ifc_filter.type == "query": keys = cls.wrap_value(ifc_filter, ifc_filter.name) comparison, value = cls.get_comparison_and_value(ifc_filter) @@ -201,6 +204,10 @@ class ImportFilterQueryTransformer(lark.Transformer): comparison, value = args return {"type": "group", "value": f"{comparison}{value}"} + def parent(self, args): + comparison, value = args + return {"type": "parent", "value": f"{comparison}{value}"} + def query(self, args): keys, comparison, value = args return {"type": "query", "name": keys, "value": f"{comparison}{value}"} diff --git a/src/ifcopenshell-python/ifcopenshell/util/selector.py b/src/ifcopenshell-python/ifcopenshell/util/selector.py index 52c7176650..cd07e6b074 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/selector.py +++ b/src/ifcopenshell-python/ifcopenshell/util/selector.py @@ -39,7 +39,7 @@ filter_elements_grammar = lark.Lark( filter_group: facet_list ("+" facet_list)* facet_list: facet ("," facet)* - facet: instance | entity | attribute | type | material | query | classification | location | property | group + facet: instance | entity | attribute | type | material | query | classification | location | property | group | parent instance: not? globalid globalid: /[0-3][a-zA-Z0-9_$]{21}/ @@ -51,6 +51,7 @@ filter_elements_grammar = lark.Lark( classification: "classification" comparison value location: "location" comparison value group: "group" comparison value + parent: "parent" comparison value query: "query:" keys comparison value pset: quoted_string | regex_string | unquoted_string @@ -681,6 +682,25 @@ class FacetTransformer(lark.Transformer): self.elements = set(filter(filter_function, self.elements)) + def parent(self, args): + comparison, value = args + + def filter_function(element): + parents = [] + result = False + if parent := ifcopenshell.util.element.get_parent(element): + parents.append(parent) + while parents: + parent = parents.pop() + if self.compare(parent.Name, comparison, value): + result = True + break + if grandparent := ifcopenshell.util.element.get_parent(parent): + parents.append(grandparent) + return result if comparison == "=" else not result + + self.elements = set(filter(filter_function, self.elements)) + def query(self, args): keys, comparison, value = args diff --git a/src/ifcopenshell-python/test/util/test_selector.py b/src/ifcopenshell-python/test/util/test_selector.py index e361833b34..469b8918ef 100644 --- a/src/ifcopenshell-python/test/util/test_selector.py +++ b/src/ifcopenshell-python/test/util/test_selector.py @@ -266,6 +266,25 @@ class TestFilterElements(test.bootstrap.IFC4): assert subject.filter_elements(self.file, "IfcWall, group=Foo") == {element} assert subject.filter_elements(self.file, "IfcWall, group!=Foo") == {element2} + def test_selecting_by_parent(self): + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall", name="Element1") + element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall", name="Element2") + element3 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall", name="Element3") + space = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcSpace", name="Space") + storey = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuildingStorey", name="G") + building = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding", name="Building") + project = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject", name="Project") + ifcopenshell.api.run("spatial.assign_container", self.file, products=[element], relating_structure=space) + ifcopenshell.api.run("spatial.assign_container", self.file, products=[element2], relating_structure=storey) + ifcopenshell.api.run("aggregate.assign_object", self.file, products=[element3], relating_object=element2) + ifcopenshell.api.run("aggregate.assign_object", self.file, products=[space], relating_object=storey) + ifcopenshell.api.run("aggregate.assign_object", self.file, products=[storey], relating_object=building) + ifcopenshell.api.run("aggregate.assign_object", self.file, products=[building], relating_object=project) + assert subject.filter_elements(self.file, "IfcWall, parent=Project") == {element, element2, element3} + assert subject.filter_elements(self.file, "IfcWall, parent=Space") == {element} + assert subject.filter_elements(self.file, "IfcWall, parent=G") == {element, element2, element3} + assert subject.filter_elements(self.file, "IfcWall, parent=Element2") == {element3} + def test_selecting_multiple_filter_groups(self): element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") element.Name = "Foo"