Adjust selector's behaviour to use the elements argument as a filtering subset

This was always the original intention, but never worked as designed. This is so that you don't need to filter the entire file (which can be huge).
This commit is contained in:
Dion Moult
2024-05-28 17:29:34 +10:00
parent 316934de97
commit 8403c32e76
2 changed files with 80 additions and 55 deletions
@@ -291,9 +291,10 @@ def filter_elements(
:type ifc_file: ifcopenshell.file
:param query: Query to execute
:type query: str
:param elements: Base set of IFC elements for the query.
If provided, new elements found for the current query will be added to `elements`.
Elements explicitly excluded in the `query` will also be excluded from `elements`
:param elements: Base set of IFC elements for the query. If not provided,
all elements in the IFC are queried. If provided, the query will be
applied to this set of elements, so the result will be a subset of
elements.
:type elements: set[ifcopenshell.entity_instance], optional
:param edit_in_place: If `True`, mutate the provided `elements` in place. Defaults to `False`
:type edit_in_place: bool
@@ -497,11 +498,17 @@ def set_element_value(
f"Failed to set value for element '{original_element}' with query '{query}' (invalid or unsupported query)."
)
class FacetTransformer(lark.Transformer):
def __init__(self, ifc_file: ifcopenshell.file, elements: Optional[set[ifcopenshell.entity_instance]] = None):
self.file = ifc_file
self.results = []
self.elements = set() if elements is None else elements
if elements is None:
self.base_elements = None
self.elements = set()
else:
self.base_elements = elements.copy()
self.elements = set()
self.container_parents = {}
self.container_trees = {}
@@ -517,28 +524,44 @@ class FacetTransformer(lark.Transformer):
self.elements = set()
def instance(self, args):
if args[0].data == "globalid":
try:
self.elements.add(self.file.by_guid(args[0].children[0].value))
except:
pass
if self.base_elements is None:
if args[0].data == "globalid":
try:
self.elements.add(self.file.by_guid(args[0].children[0].value))
except:
pass
else:
try:
self.elements.remove(self.file.by_guid(args[1].children[0].value))
except:
pass
else:
try:
self.elements.remove(self.file.by_guid(args[1].children[0].value))
except:
pass
if args[0].data == "globalid":
self.elements |= {
e for e in self.base_elements if getattr(e, "GlobalId", None) == args[0].children[0].value
}
else:
self.elements -= {
e for e in self.base_elements if getattr(e, "GlobalId", None) == args[1].children[0].value
}
def entity(self, args):
if args[0].data == "ifc_class":
try:
self.elements |= set(self.file.by_type(args[0].children[0].value))
except:
pass
if self.base_elements is None:
if args[0].data == "ifc_class":
try:
self.elements |= set(self.file.by_type(args[0].children[0].value))
except:
pass
else:
try:
self.elements -= set(self.file.by_type(args[1].children[0].value))
except:
pass
else:
try:
self.elements -= set(self.file.by_type(args[1].children[0].value))
except:
pass
if args[0].data == "ifc_class":
self.elements |= {e for e in self.base_elements if e.is_a(args[0].children[0].value)}
else:
self.elements -= {e for e in self.base_elements if e.is_a(args[1].children[0].value)}
def attribute(self, args):
name, comparison, value = args
@@ -25,41 +25,41 @@ import ifcopenshell.util.pset
import numpy as np
class TestFormat():
class TestFormat:
def test_no_formatting(self):
assert subject.format("123") == "123"
assert subject.format('\"123\"') == "123"
assert subject.format('\"foo\"') == "foo"
assert subject.format('"123"') == "123"
assert subject.format('"foo"') == "foo"
def test_string_formatting(self):
assert subject.format('upper(\"fOo\")') == "FOO"
assert subject.format('lower(\"fOo\")') == "foo"
assert subject.format('title(\"fOo\")') == "Foo"
assert subject.format('concat(\"fOo\", \"bar\")') == "fOobar"
assert subject.format('upper(concat(\"fOo\", \"bar\"))') == "FOOBAR"
assert subject.format('substr(\"foobar\", 3)') == "bar"
assert subject.format('substr(\"foobar\", 1, 2)') == "o"
assert subject.format('substr(\"foobar\", 1, -1)') == "ooba"
assert subject.format('upper("fOo")') == "FOO"
assert subject.format('lower("fOo")') == "foo"
assert subject.format('title("fOo")') == "Foo"
assert subject.format('concat("fOo", "bar")') == "fOobar"
assert subject.format('upper(concat("fOo", "bar"))') == "FOOBAR"
assert subject.format('substr("foobar", 3)') == "bar"
assert subject.format('substr("foobar", 1, 2)') == "o"
assert subject.format('substr("foobar", 1, -1)') == "ooba"
def test_number_formatting(self):
assert subject.format("round(123, 5)") == "125"
assert subject.format('round(\"123\", 5)') == "125"
assert subject.format('number(123)') == "123"
assert subject.format('number(1234.56)') == "1,234.56"
assert subject.format('round("123", 5)') == "125"
assert subject.format("number(123)") == "123"
assert subject.format("number(1234.56)") == "1,234.56"
assert subject.format('number(123, ".")') == "123"
assert subject.format('number(\"123\", ".")') == "123"
assert subject.format('number("123", ".")') == "123"
assert subject.format('number(123.12, ".")') == "123.12"
assert subject.format('number(123.12, ",")') == "123,12"
assert subject.format('number(1234.12, ",", ".")') == "1.234,12"
assert subject.format('metric_length(123, 5, 2)') == "125.00"
assert subject.format('metric_length(123.123, 0.1, 2)') == "123.10"
assert subject.format('metric_length(\"123\", 5, 2)') == "125.00"
assert subject.format('imperial_length(1, 1)') == "1'"
assert subject.format('imperial_length(3.123, 1)') == "3' - 1\""
assert subject.format('imperial_length(3.123, 2)') == "3' - 1 1/2\""
assert subject.format('imperial_length(\"3.123\", 2)') == "3' - 1 1/2\""
assert subject.format('imperial_length(\"123.123\", 2, \"inch\", \"foot\")') == "10' - 3\""
assert subject.format('imperial_length(\"123.123\", 2, \"inch\", \"inch\")') == "123\""
assert subject.format("metric_length(123, 5, 2)") == "125.00"
assert subject.format("metric_length(123.123, 0.1, 2)") == "123.10"
assert subject.format('metric_length("123", 5, 2)') == "125.00"
assert subject.format("imperial_length(1, 1)") == "1'"
assert subject.format("imperial_length(3.123, 1)") == "3' - 1\""
assert subject.format("imperial_length(3.123, 2)") == "3' - 1 1/2\""
assert subject.format('imperial_length("3.123", 2)') == "3' - 1 1/2\""
assert subject.format('imperial_length("123.123", 2, "inch", "foot")') == "10' - 3\""
assert subject.format('imperial_length("123.123", 2, "inch", "inch")') == '123"'
class TestGetElementValue(test.bootstrap.IFC4):
@@ -269,17 +269,19 @@ class TestFilterElements(test.bootstrap.IFC4):
def test_using_elements_argument(self):
wall = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
slab = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcSlab")
door = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcDoor")
elements = {wall, slab}
results = subject.filter_elements(self.file, "IfcWall", {wall, slab})
assert results != elements
assert results == {wall}
elements = {wall, slab, door}
assert subject.filter_elements(self.file, "IfcWall, IfcSlab", elements) == {wall, slab}
# keep elements unaffected by expression
assert subject.filter_elements(self.file, "IfcWall", {slab}) == {wall, slab}
# filter out excluded elements
assert subject.filter_elements(self.file, "IfcWall, ! IfcSlab", {slab}) == {wall}
# edit_in_place to update original set
original_set = set()
def test_editing_in_place(self):
wall = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
original_set = {wall}
new_set = subject.filter_elements(self.file, "IfcWall", original_set, edit_in_place=True)
assert new_set == original_set
assert new_set == original_set == {wall}
class TestSetElementValue(test.bootstrap.IFC4):