mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-26 10:11:46 +00:00
Fix #2858. Fix #2856. BlenderBIM Add-on drawings and IfcCSV now support the new quoted and escaped selector capabilities.
This commit is contained in:
@@ -612,7 +612,7 @@ class CreateDrawing(bpy.types.Operator):
|
|||||||
classes.append("material-{}".format(re.sub("[^0-9a-zA-Z]+", "", self.get_material_name(material))))
|
classes.append("material-{}".format(re.sub("[^0-9a-zA-Z]+", "", self.get_material_name(material))))
|
||||||
classes.append("globalid-{}".format(element.GlobalId))
|
classes.append("globalid-{}".format(element.GlobalId))
|
||||||
for attribute in svg_writer.annotations["attributes"]:
|
for attribute in svg_writer.annotations["attributes"]:
|
||||||
result = self.selector.get_element_value(element, attribute)
|
result = ifcopenshell.util.selector.get_element_value(element, attribute)
|
||||||
if result:
|
if result:
|
||||||
classes.append(
|
classes.append(
|
||||||
"{}-{}".format(re.sub("[^0-9a-zA-Z]+", "", attribute), re.sub("[^0-9a-zA-Z]+", "", result))
|
"{}-{}".format(re.sub("[^0-9a-zA-Z]+", "", attribute), re.sub("[^0-9a-zA-Z]+", "", result))
|
||||||
@@ -960,7 +960,6 @@ class ActivateDrawingStyle(bpy.types.Operator):
|
|||||||
exec(f"{path} = {value}")
|
exec(f"{path} = {value}")
|
||||||
|
|
||||||
def set_query(self, context):
|
def set_query(self, context):
|
||||||
self.selector = ifcopenshell.util.selector.Selector()
|
|
||||||
self.include_global_ids = []
|
self.include_global_ids = []
|
||||||
self.exclude_global_ids = []
|
self.exclude_global_ids = []
|
||||||
for ifc_file in context.scene.DocProperties.ifc_files:
|
for ifc_file in context.scene.DocProperties.ifc_files:
|
||||||
@@ -969,10 +968,10 @@ class ActivateDrawingStyle(bpy.types.Operator):
|
|||||||
except:
|
except:
|
||||||
continue
|
continue
|
||||||
if self.drawing_style.include_query:
|
if self.drawing_style.include_query:
|
||||||
results = self.selector.parse(ifc, self.drawing_style.include_query)
|
results = ifcopenshell.util.selector.Selector.parse(ifc, self.drawing_style.include_query)
|
||||||
self.include_global_ids.extend([e.GlobalId for e in results])
|
self.include_global_ids.extend([e.GlobalId for e in results])
|
||||||
if self.drawing_style.exclude_query:
|
if self.drawing_style.exclude_query:
|
||||||
results = self.selector.parse(ifc, self.drawing_style.exclude_query)
|
results = ifcopenshell.util.selector.Selector.parse(ifc, self.drawing_style.exclude_query)
|
||||||
self.exclude_global_ids.extend([e.GlobalId for e in results])
|
self.exclude_global_ids.extend([e.GlobalId for e in results])
|
||||||
if self.drawing_style.include_query:
|
if self.drawing_style.include_query:
|
||||||
self.parse_filter_query("INCLUDE", context)
|
self.parse_filter_query("INCLUDE", context)
|
||||||
|
|||||||
@@ -337,7 +337,7 @@ class SvgWriter:
|
|||||||
if custom_classes:
|
if custom_classes:
|
||||||
classes.extend(custom_classes.split())
|
classes.extend(custom_classes.split())
|
||||||
for key in self.metadata:
|
for key in self.metadata:
|
||||||
value = ifcopenshell.util.selector.Selector.get_element_value(element, key)
|
value = ifcopenshell.util.selector.get_element_value(element, key)
|
||||||
if value:
|
if value:
|
||||||
classes.append(self.canonicalise_class_name(key) + "-" + self.canonicalise_class_name(str(value)))
|
classes.append(self.canonicalise_class_name(key) + "-" + self.canonicalise_class_name(str(value)))
|
||||||
return classes
|
return classes
|
||||||
@@ -671,11 +671,10 @@ class SvgWriter:
|
|||||||
literal = text_literal.Literal
|
literal = text_literal.Literal
|
||||||
|
|
||||||
product = tool.Drawing.get_assigned_product(element)
|
product = tool.Drawing.get_assigned_product(element)
|
||||||
selector = ifcopenshell.util.selector.Selector
|
|
||||||
variables = {}
|
variables = {}
|
||||||
if product != None:
|
if product != None:
|
||||||
for variable in re.findall("{{.*?}}", literal):
|
for variable in re.findall("{{.*?}}", literal):
|
||||||
literal = literal.replace(variable, str(selector.get_element_value(product, variable[2:-2]) or ""))
|
literal = literal.replace(variable, str(ifcopenshell.util.selector.get_element_value(product, variable[2:-2]) or ""))
|
||||||
|
|
||||||
for line_number, text_line in enumerate(literal.replace("\\n", "\n").split("\n")):
|
for line_number, text_line in enumerate(literal.replace("\\n", "\n").split("\n")):
|
||||||
self.svg.add(
|
self.svg.add(
|
||||||
|
|||||||
@@ -526,10 +526,9 @@ class Drawing(blenderbim.core.tool.Drawing):
|
|||||||
value = element.Literal
|
value = element.Literal
|
||||||
product = cls.get_assigned_product(tool.Ifc.get_entity(obj))
|
product = cls.get_assigned_product(tool.Ifc.get_entity(obj))
|
||||||
if product:
|
if product:
|
||||||
selector = ifcopenshell.util.selector.Selector()
|
|
||||||
variables = {}
|
variables = {}
|
||||||
for variable in re.findall("{{.*?}}", value):
|
for variable in re.findall("{{.*?}}", value):
|
||||||
value = value.replace(variable, str(selector.get_element_value(product, variable[2:-2]) or ""))
|
value = value.replace(variable, str(ifcopenshell.util.selector.get_element_value(product, variable[2:-2]) or ""))
|
||||||
obj.BIMTextProperties.value = value
|
obj.BIMTextProperties.value = value
|
||||||
|
|
||||||
# TODO below this point is highly experimental prototype code with no tests
|
# TODO below this point is highly experimental prototype code with no tests
|
||||||
|
|||||||
@@ -144,7 +144,6 @@ class IfcCsv:
|
|||||||
self.attributes = []
|
self.attributes = []
|
||||||
self.output = ""
|
self.output = ""
|
||||||
self.format = "csv"
|
self.format = "csv"
|
||||||
self.selector = ifcopenshell.util.selector.Selector()
|
|
||||||
self.delimiter = ","
|
self.delimiter = ","
|
||||||
|
|
||||||
def export(self, ifc_file, elements):
|
def export(self, ifc_file, elements):
|
||||||
@@ -162,7 +161,7 @@ class IfcCsv:
|
|||||||
del self.attributes[index]
|
del self.attributes[index]
|
||||||
|
|
||||||
for attribute in self.attributes:
|
for attribute in self.attributes:
|
||||||
result.append(self.selector.get_element_value(element, attribute))
|
result.append(ifcopenshell.util.selector.get_element_value(element, attribute))
|
||||||
self.results.append(result)
|
self.results.append(result)
|
||||||
|
|
||||||
self.headers = ["GlobalId"]
|
self.headers = ["GlobalId"]
|
||||||
|
|||||||
@@ -23,6 +23,31 @@ import ifcopenshell.util.fm
|
|||||||
import ifcopenshell.util.element
|
import ifcopenshell.util.element
|
||||||
|
|
||||||
|
|
||||||
|
def get_element_value(element, query):
|
||||||
|
l = lark.Lark(
|
||||||
|
"""start: WORD | ESCAPED_STRING | keys_regex | keys_quoted | keys_simple
|
||||||
|
keys_regex: "r" ESCAPED_STRING "." ESCAPED_STRING
|
||||||
|
keys_quoted: ESCAPED_STRING "." ESCAPED_STRING
|
||||||
|
keys_simple: /[^\\W][^.=<>]*[^\\W]/ "." /[^\\W][^.=<>]*[^\\W]/
|
||||||
|
|
||||||
|
// Embed common.lark for packaging
|
||||||
|
_STRING_INNER: /.*?/
|
||||||
|
_STRING_ESC_INNER: _STRING_INNER /(?<!\\\\)(\\\\\\\\)*?/
|
||||||
|
ESCAPED_STRING : "\\"" _STRING_ESC_INNER "\\""
|
||||||
|
LCASE_LETTER: "a".."z"
|
||||||
|
UCASE_LETTER: "A".."Z"
|
||||||
|
LETTER: UCASE_LETTER | LCASE_LETTER
|
||||||
|
WORD: LETTER+
|
||||||
|
WS: /[ \\t\\f\\r\\n]/+
|
||||||
|
|
||||||
|
%ignore WS // Disregard spaces in text
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
start = l.parse(query)
|
||||||
|
filter_query = Selector.parse_filter_query(start.children[0])
|
||||||
|
return Selector.get_element_value(element, filter_query["keys"], filter_query["is_regex"])
|
||||||
|
|
||||||
|
|
||||||
class Selector:
|
class Selector:
|
||||||
@classmethod
|
@classmethod
|
||||||
def parse(cls, ifc_file, query, elements=None):
|
def parse(cls, ifc_file, query, elements=None):
|
||||||
@@ -181,17 +206,7 @@ class Selector:
|
|||||||
@classmethod
|
@classmethod
|
||||||
def filter_elements(cls, elements, filter_rule):
|
def filter_elements(cls, elements, filter_rule):
|
||||||
results = []
|
results = []
|
||||||
keys = filter_rule.children[0].children[0]
|
filter_query = cls.parse_filter_query(filter_rule.children[0].children[0])
|
||||||
is_regex = False
|
|
||||||
if isinstance(keys, str):
|
|
||||||
keys = [keys]
|
|
||||||
elif keys.data == "keys_regex":
|
|
||||||
is_regex = True
|
|
||||||
keys = [keys.children[0][1:-1].replace("\\\"", '"'), keys.children[1][1:-1].replace("\\\"", '"')]
|
|
||||||
elif keys.data == "keys_quoted":
|
|
||||||
keys = [keys.children[0][1:-1].replace("\\\"", '"'), keys.children[1][1:-1].replace("\\\"", '"')]
|
|
||||||
elif keys.data == "keys_simple":
|
|
||||||
keys = [keys.children[0], keys.children[1]]
|
|
||||||
comparison = value = None
|
comparison = value = None
|
||||||
if len(filter_rule.children) > 1:
|
if len(filter_rule.children) > 1:
|
||||||
comparison = filter_rule.children[1].children[0].data
|
comparison = filter_rule.children[1].children[0].data
|
||||||
@@ -209,7 +224,7 @@ class Selector:
|
|||||||
elif token_type == "NULL":
|
elif token_type == "NULL":
|
||||||
value = None
|
value = None
|
||||||
for element in elements:
|
for element in elements:
|
||||||
element_value = cls.get_element_value(element, keys, is_regex=is_regex)
|
element_value = cls.get_element_value(element, filter_query["keys"], is_regex=filter_query["is_regex"])
|
||||||
if element_value is None and value is not None and "not" not in comparison:
|
if element_value is None and value is not None and "not" not in comparison:
|
||||||
continue
|
continue
|
||||||
if comparison and cls.filter_element(
|
if comparison and cls.filter_element(
|
||||||
@@ -220,6 +235,21 @@ class Selector:
|
|||||||
results.append(element)
|
results.append(element)
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def parse_filter_query(cls, filter_query):
|
||||||
|
keys = filter_query
|
||||||
|
is_regex = False
|
||||||
|
if isinstance(keys, str):
|
||||||
|
keys = [keys]
|
||||||
|
elif keys.data == "keys_regex":
|
||||||
|
is_regex = True
|
||||||
|
keys = [keys.children[0][1:-1].replace("\\\"", '"'), keys.children[1][1:-1].replace("\\\"", '"')]
|
||||||
|
elif keys.data == "keys_quoted":
|
||||||
|
keys = [keys.children[0][1:-1].replace("\\\"", '"'), keys.children[1][1:-1].replace("\\\"", '"')]
|
||||||
|
elif keys.data == "keys_simple":
|
||||||
|
keys = [keys.children[0], keys.children[1]]
|
||||||
|
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
|
||||||
|
|||||||
@@ -22,6 +22,13 @@ import ifcopenshell.api
|
|||||||
import ifcopenshell.util.selector as subject
|
import ifcopenshell.util.selector as subject
|
||||||
|
|
||||||
|
|
||||||
|
class TestGetElementValue(test.bootstrap.IFC4):
|
||||||
|
def test_selecting_an_elements_value_using_a_query(self):
|
||||||
|
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
|
||||||
|
element.Name = "Foobar"
|
||||||
|
assert subject.get_element_value(element, "Name") == "Foobar"
|
||||||
|
|
||||||
|
|
||||||
class TestSelector(test.bootstrap.IFC4):
|
class TestSelector(test.bootstrap.IFC4):
|
||||||
def test_selecting_by_class(self):
|
def test_selecting_by_class(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")
|
||||||
|
|||||||
Reference in New Issue
Block a user