mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-25 17:57:02 +00:00
Fix #2881. Support added for referencing nested relationships and material set items.
This commit is contained in:
@@ -1042,9 +1042,10 @@ class Drawing(blenderbim.core.tool.Drawing):
|
|||||||
if not product:
|
if not product:
|
||||||
return text
|
return text
|
||||||
for variable in re.findall("{{.*?}}", text):
|
for variable in re.findall("{{.*?}}", text):
|
||||||
text = text.replace(
|
value = ifcopenshell.util.selector.get_element_value(product, variable[2:-2])
|
||||||
variable, str(ifcopenshell.util.selector.get_element_value(product, variable[2:-2]) or "")
|
if isinstance(value, (list, tuple)):
|
||||||
)
|
value = ", ".join(str(v) for v in value)
|
||||||
|
text = text.replace(variable, str(value))
|
||||||
return text
|
return text
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@@ -26,9 +26,9 @@ import ifcopenshell.util.element
|
|||||||
def get_element_value(element, query):
|
def get_element_value(element, query):
|
||||||
l = lark.Lark(
|
l = lark.Lark(
|
||||||
"""start: WORD | ESCAPED_STRING | keys_regex | keys_quoted | keys_simple
|
"""start: WORD | ESCAPED_STRING | keys_regex | keys_quoted | keys_simple
|
||||||
keys_regex: "r" ESCAPED_STRING "." ESCAPED_STRING
|
keys_regex: "r" ESCAPED_STRING ("." ESCAPED_STRING)*
|
||||||
keys_quoted: ESCAPED_STRING "." ESCAPED_STRING
|
keys_quoted: ESCAPED_STRING ("." ESCAPED_STRING)*
|
||||||
keys_simple: /[^\\W][^.=<>]*[^\\W]/ "." /[^\\W][^.=<>]*[^\\W]/
|
keys_simple: /[^\\W][^.=<>!%*\\]]*/ ("." /[^\\W][^.=<>!%*\\]]*/)*
|
||||||
|
|
||||||
// Embed common.lark for packaging
|
// Embed common.lark for packaging
|
||||||
_STRING_INNER: /.*?/
|
_STRING_INNER: /.*?/
|
||||||
@@ -63,9 +63,9 @@ class Selector:
|
|||||||
filter: "[" filter_key (comparison filter_value)? "]"
|
filter: "[" filter_key (comparison filter_value)? "]"
|
||||||
filter_key: WORD | ESCAPED_STRING | keys_regex | keys_quoted | keys_simple
|
filter_key: WORD | ESCAPED_STRING | keys_regex | keys_quoted | keys_simple
|
||||||
filter_value: ESCAPED_STRING | SIGNED_FLOAT | SIGNED_INT | BOOLEAN | NULL
|
filter_value: ESCAPED_STRING | SIGNED_FLOAT | SIGNED_INT | BOOLEAN | NULL
|
||||||
keys_regex: "r" ESCAPED_STRING "." ESCAPED_STRING
|
keys_regex: "r" ESCAPED_STRING ("." ESCAPED_STRING)*
|
||||||
keys_quoted: ESCAPED_STRING "." ESCAPED_STRING
|
keys_quoted: ESCAPED_STRING ("." ESCAPED_STRING)*
|
||||||
keys_simple: /[^\\W][^.=<>]*[^\\W]/ "." /[^\\W][^.=<>]*[^\\W]/
|
keys_simple: /[^\\W][^.=<>!%*\\]]*/ ("." /[^\\W][^.=<>!%*\\]]*/)*
|
||||||
lfunction: and | or
|
lfunction: and | or
|
||||||
inverse_relationship: types | decomposed_by | bounded_by | grouped_by
|
inverse_relationship: types | decomposed_by | bounded_by | grouped_by
|
||||||
types: "*"
|
types: "*"
|
||||||
@@ -243,21 +243,29 @@ class Selector:
|
|||||||
keys = [keys]
|
keys = [keys]
|
||||||
elif keys.data == "keys_regex":
|
elif keys.data == "keys_regex":
|
||||||
is_regex = True
|
is_regex = True
|
||||||
keys = [keys.children[0][1:-1].replace("\\\"", '"'), keys.children[1][1:-1].replace("\\\"", '"')]
|
keys = [k[1:-1].replace("\\\"", '"') for k in keys.children]
|
||||||
elif keys.data == "keys_quoted":
|
elif keys.data == "keys_quoted":
|
||||||
keys = [keys.children[0][1:-1].replace("\\\"", '"'), keys.children[1][1:-1].replace("\\\"", '"')]
|
keys = [k[1:-1].replace("\\\"", '"') for k in keys.children]
|
||||||
elif keys.data == "keys_simple":
|
elif keys.data == "keys_simple":
|
||||||
keys = [keys.children[0], keys.children[1]]
|
keys = keys.children
|
||||||
return {"keys": keys, "is_regex": is_regex}
|
return {"keys": keys, "is_regex": is_regex}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_element_value(cls, element, keys, is_regex=False):
|
def get_element_value(cls, element, keys, is_regex=False):
|
||||||
value = element
|
value = element
|
||||||
for key in keys:
|
for key in keys:
|
||||||
|
key = key.strip()
|
||||||
if key == "type":
|
if key == "type":
|
||||||
value = ifcopenshell.util.element.get_type(element)
|
value = ifcopenshell.util.element.get_type(value)
|
||||||
elif key == "material":
|
elif key in ("material", "mat"):
|
||||||
value = ifcopenshell.util.element.get_material(element, should_skip_usage=True)
|
value = ifcopenshell.util.element.get_material(value, should_skip_usage=True)
|
||||||
|
elif key in ("item", "i"):
|
||||||
|
if value.is_a("IfcMaterialLayerSet"):
|
||||||
|
value = value.MaterialLayers
|
||||||
|
elif value.is_a("IfcMaterialProfileSet"):
|
||||||
|
value = value.MaterialProfiles
|
||||||
|
elif value.is_a("IfcMaterialConstituentSet"):
|
||||||
|
value = value.MaterialConstituents
|
||||||
elif key == "container":
|
elif key == "container":
|
||||||
value = ifcopenshell.util.element.get_container(element)
|
value = ifcopenshell.util.element.get_container(element)
|
||||||
elif isinstance(value, ifcopenshell.entity_instance):
|
elif isinstance(value, ifcopenshell.entity_instance):
|
||||||
@@ -287,7 +295,7 @@ class Selector:
|
|||||||
value = results
|
value = results
|
||||||
else:
|
else:
|
||||||
value = value.get(key, None)
|
value = value.get(key, None)
|
||||||
elif isinstance(value, list): # If we use regex
|
elif isinstance(value, (list, tuple)): # If we use regex
|
||||||
results = []
|
results = []
|
||||||
for v in value:
|
for v in value:
|
||||||
subvalue = cls.get_element_value(v, [key], is_regex=is_regex)
|
subvalue = cls.get_element_value(v, [key], is_regex=is_regex)
|
||||||
|
|||||||
@@ -28,6 +28,27 @@ class TestGetElementValue(test.bootstrap.IFC4):
|
|||||||
element.Name = "Foobar"
|
element.Name = "Foobar"
|
||||||
assert subject.get_element_value(element, "Name") == "Foobar"
|
assert subject.get_element_value(element, "Name") == "Foobar"
|
||||||
|
|
||||||
|
def test_selecting_using_a_multiple_key_query(self):
|
||||||
|
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
|
||||||
|
material = ifcopenshell.api.run("material.add_material", self.file, name="CON01")
|
||||||
|
material2 = ifcopenshell.api.run("material.add_material", self.file, name="CON02")
|
||||||
|
material_set = ifcopenshell.api.run(
|
||||||
|
"material.add_material_set", self.file, name="FOO", set_type="IfcMaterialLayerSet"
|
||||||
|
)
|
||||||
|
layer = ifcopenshell.api.run("material.add_layer", self.file, layer_set=material_set, material=material)
|
||||||
|
layer.Name = "L1"
|
||||||
|
layer = ifcopenshell.api.run("material.add_layer", self.file, layer_set=material_set, material=material)
|
||||||
|
layer.Name = "L2"
|
||||||
|
ifcopenshell.api.run("material.edit_layer", self.file, layer=layer, attributes={"LayerThickness": 13})
|
||||||
|
ifcopenshell.api.run("material.assign_material", self.file, product=element, material=material_set)
|
||||||
|
assert subject.get_element_value(element, "material.MaterialLayers.Name") == ["L1", "L2"]
|
||||||
|
# Allow to use "item" to generically select an item in a material set
|
||||||
|
assert subject.get_element_value(element, "material.item.Name") == ["L1", "L2"]
|
||||||
|
assert subject.get_element_value(element, '"material"."item"."Name"') == ["L1", "L2"]
|
||||||
|
assert subject.get_element_value(element, 'r"material"."item"."Name"') == ["L1", "L2"]
|
||||||
|
# Provide shortform for convenience
|
||||||
|
assert subject.get_element_value(element, "mat.i.Name") == ["L1", "L2"]
|
||||||
|
|
||||||
|
|
||||||
class TestSelector(test.bootstrap.IFC4):
|
class TestSelector(test.bootstrap.IFC4):
|
||||||
def test_selecting_by_class(self):
|
def test_selecting_by_class(self):
|
||||||
@@ -149,7 +170,7 @@ class TestSelector(test.bootstrap.IFC4):
|
|||||||
pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="a !%$§&/()?|*-+,€~#@µ^°a")
|
pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="a !%$§&/()?|*-+,€~#@µ^°a")
|
||||||
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"a !%$§&/()?|*-+,€~#@µ^°a": "Bar"})
|
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"a !%$§&/()?|*-+,€~#@µ^°a": "Bar"})
|
||||||
assert subject.Selector.parse(
|
assert subject.Selector.parse(
|
||||||
self.file, '.IfcElement[a !%$§&/()?|*-+,€~#@µ^°a.a !%$§&/()?|*-+,€~#@µ^°a="Bar"]'
|
self.file, '.IfcElement["a !%$§&/()?|*-+,€~#@µ^°a"."a !%$§&/()?|*-+,€~#@µ^°a"="Bar"]'
|
||||||
) == [element]
|
) == [element]
|
||||||
|
|
||||||
def test_selecting_a_property_which_includes_a_dot(self):
|
def test_selecting_a_property_which_includes_a_dot(self):
|
||||||
@@ -212,9 +233,40 @@ class TestSelector(test.bootstrap.IFC4):
|
|||||||
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Foo": "Baz"})
|
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Foo": "Baz"})
|
||||||
assert subject.Selector.parse(self.file, '.IfcElement[r"Foo.*"."F.*"="Bar"]') == [element]
|
assert subject.Selector.parse(self.file, '.IfcElement[r"Foo.*"."F.*"="Bar"]') == [element]
|
||||||
|
|
||||||
def test_selecting_a_property_via_a_type_attribute(self):
|
def test_selecting_an_attribute_via_a_type(self):
|
||||||
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
|
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
|
||||||
element_type = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType")
|
element_type = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType")
|
||||||
element_type.Name = "Foo"
|
element_type.Name = "Foo"
|
||||||
ifcopenshell.api.run("type.assign_type", self.file, related_object=element, relating_type=element_type)
|
ifcopenshell.api.run("type.assign_type", self.file, related_object=element, relating_type=element_type)
|
||||||
assert set(subject.Selector.parse(self.file, '.IfcWall[type.Name="Foo"]')) == {element}
|
assert set(subject.Selector.parse(self.file, '.IfcWall[type.Name="Foo"]')) == {element}
|
||||||
|
|
||||||
|
def test_selecting_via_a_material(self):
|
||||||
|
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
|
||||||
|
material = ifcopenshell.api.run("material.add_material", self.file, name="CON01")
|
||||||
|
ifcopenshell.api.run("material.assign_material", self.file, product=element, material=material)
|
||||||
|
assert set(subject.Selector.parse(self.file, '.IfcWall[material.Name="CON01"]')) == {element}
|
||||||
|
|
||||||
|
def test_selecting_via_a_material_set(self):
|
||||||
|
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
|
||||||
|
material = ifcopenshell.api.run("material.add_material", self.file, name="CON01")
|
||||||
|
material_set = ifcopenshell.api.run(
|
||||||
|
"material.add_material_set", self.file, name="FOO", set_type="IfcMaterialLayerSet"
|
||||||
|
)
|
||||||
|
layer = ifcopenshell.api.run("material.add_layer", self.file, layer_set=material_set, material=material)
|
||||||
|
ifcopenshell.api.run("material.edit_layer", self.file, layer=layer, attributes={"LayerThickness": 13})
|
||||||
|
ifcopenshell.api.run("material.assign_material", self.file, product=element, material=material_set)
|
||||||
|
assert set(subject.Selector.parse(self.file, '.IfcWall[material.LayerSetName="FOO"]')) == {element}
|
||||||
|
|
||||||
|
def test_selecting_via_a_material_set_item(self):
|
||||||
|
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
|
||||||
|
material = ifcopenshell.api.run("material.add_material", self.file, name="CON01")
|
||||||
|
material2 = ifcopenshell.api.run("material.add_material", self.file, name="CON02")
|
||||||
|
material_set = ifcopenshell.api.run(
|
||||||
|
"material.add_material_set", self.file, name="FOO", set_type="IfcMaterialLayerSet"
|
||||||
|
)
|
||||||
|
layer = ifcopenshell.api.run("material.add_layer", self.file, layer_set=material_set, material=material)
|
||||||
|
layer = ifcopenshell.api.run("material.add_layer", self.file, layer_set=material_set, material=material2)
|
||||||
|
ifcopenshell.api.run("material.assign_material", self.file, product=element, material=material_set)
|
||||||
|
assert set(subject.Selector.parse(self.file, '.IfcWall[material.item.Material.Name="CON01"]')) == {element}
|
||||||
|
assert set(subject.Selector.parse(self.file, '.IfcWall[material.item.Material.Name="CON02"]')) == {element}
|
||||||
|
assert set(subject.Selector.parse(self.file, '.IfcWall[material.item.Material.Name="CON03"]')) == set()
|
||||||
|
|||||||
Reference in New Issue
Block a user