Merge pull request #7505 from falken10vdl/text-annotations-squash-merge

PR7500 squashed commit
This commit is contained in:
falken10vdl
2025-12-28 12:47:30 +01:00
committed by GitHub
10 changed files with 3030 additions and 143 deletions
@@ -136,21 +136,32 @@ get_element_grammar = lark.Lark(
)
format_grammar = lark.Lark(
"""start: function
"""start: expression
function: round | number | int | format_length | lower | upper | title | concat | substr | ESCAPED_STRING | NUMBER
?expression: add_sub
?add_sub: mul_div
| add_sub "+" mul_div -> add
| add_sub "-" mul_div -> subtract
?mul_div: function
| mul_div "*" function -> multiply
| mul_div "/" function -> divide
function: round | number | int | format_length | lower | upper | title | concat | substr | variable | ESCAPED_STRING | NUMBER | "(" expression ")"
round: "round(" function "," NUMBER ")"
number: "number(" function ["," ESCAPED_STRING ["," ESCAPED_STRING]] ")"
int: "int(" function ")"
variable: "{{" query_path "}}"
query_path: /[^}]+/
round: "round(" expression "," NUMBER ")"
number: "number(" expression ["," ESCAPED_STRING ["," ESCAPED_STRING]] ")"
int: "int(" expression ")"
format_length: metric_length | imperial_length
metric_length: "metric_length(" function "," NUMBER "," NUMBER ")"
imperial_length: "imperial_length(" function "," NUMBER ["," ESCAPED_STRING "," ESCAPED_STRING ["," boolean]] ")"
lower: "lower(" function ")"
upper: "upper(" function ")"
title: "title(" function ")"
concat: "concat(" function ("," function)* ")"
substr: "substr(" function "," SIGNED_INT ["," SIGNED_INT] ")"
metric_length: "metric_length(" expression "," NUMBER "," NUMBER ")"
imperial_length: "imperial_length(" expression "," NUMBER ["," ESCAPED_STRING "," ESCAPED_STRING ["," boolean]] ")"
lower: "lower(" expression ")"
upper: "upper(" expression ")"
title: "title(" expression ")"
concat: "concat(" expression ("," expression)* ")"
substr: "substr(" expression "," SIGNED_INT ["," SIGNED_INT] ")"
boolean: TRUE | FALSE
TRUE: "true" | "True" | "TRUE"
@@ -186,9 +197,83 @@ format_grammar = lark.Lark(
class FormatTransformer(lark.Transformer):
def __init__(self, element=None):
"""Initialize transformer with optional element for variable substitution"""
super().__init__()
self.element = element
def start(self, args):
return args[0]
def expression(self, args):
return args[0]
def variable(self, args):
"""Handle variable substitution like {{z}} or {{Pset_Wall.FireRating}}"""
if self.element is None:
return "0" # Default value if no element context
query_path = args[0]
try:
value = get_element_value(self.element, query_path)
if value is None:
return "0"
# Convert to string for further processing
return str(value)
except:
return "0" # Return default on error
def query_path(self, args):
"""Extract the query path from variable"""
return str(args[0]).strip()
def add(self, args):
"""Handle addition operation"""
left, right = args
try:
left_val = float(left) if left != "None" and left is not None else 0.0
right_val = float(right) if right != "None" and right is not None else 0.0
result = left_val + right_val
# Return integer if result has no decimal part
if result % 1 == 0:
return str(int(result))
return str(result)
except (ValueError, TypeError):
# If can't convert to numbers, concatenate as strings
return str(left) + str(right)
def subtract(self, args):
"""Handle subtraction operation"""
left, right = args
left_val = float(left) if left != "None" and left is not None else 0.0
right_val = float(right) if right != "None" and right is not None else 0.0
result = left_val - right_val
if result % 1 == 0:
return str(int(result))
return str(result)
def multiply(self, args):
"""Handle multiplication operation"""
left, right = args
left_val = float(left) if left != "None" and left is not None else 0.0
right_val = float(right) if right != "None" and right is not None else 0.0
result = left_val * right_val
if result % 1 == 0:
return str(int(result))
return str(result)
def divide(self, args):
"""Handle division operation"""
left, right = args
left_val = float(left) if left != "None" and left is not None else 0.0
right_val = float(right) if right != "None" and right is not None else 1.0
if right_val == 0:
return "inf" # or raise an error, or return "0"
result = left_val / right_val
if result % 1 == 0:
return str(int(result))
return str(result)
def function(self, args):
return args[0]
@@ -208,7 +293,7 @@ class FormatTransformer(lark.Transformer):
return str(args[0]).title()
def concat(self, args):
return "".join(args)
return "".join(str(arg) for arg in args)
def substr(self, args):
if len(args) == 3:
@@ -238,13 +323,14 @@ class FormatTransformer(lark.Transformer):
return str(result)
def number(self, args):
if isinstance(args[0], str):
args[0] = float(args[0]) if "." in args[0] else int(args[0])
arg_val = args[0]
if isinstance(arg_val, str):
arg_val = float(arg_val) if "." in arg_val else int(arg_val)
if len(args) >= 3 and args[2]:
return "{:,}".format(args[0]).replace(".", "*").replace(",", args[2]).replace("*", args[1])
return "{:,}".format(arg_val).replace(".", "*").replace(",", args[2]).replace("*", args[1])
elif len(args) >= 2 and args[1]:
return "{}".format(args[0]).replace(".", args[1])
return "{:,}".format(args[0])
return "{}".format(arg_val).replace(".", args[1])
return "{:,}".format(arg_val)
def format_length(self, args):
return args[0]
@@ -284,7 +370,8 @@ class FormatTransformer(lark.Transformer):
)
def int(self, args: list[str]) -> str:
return str(int(float(args[0])))
value = 0.0 if args[0] == "None" else args[0] or 0.0
return str(int(float(value)))
class GetElementTransformer(lark.Transformer):
@@ -310,8 +397,18 @@ class GetElementTransformer(lark.Transformer):
return args[1:-1].replace("\\", "")
def format(query: str) -> str:
return FormatTransformer().transform(format_grammar.parse(query))
def format(query: str, element: Optional[ifcopenshell.entity_instance] = None) -> str:
"""Format a query string with optional element context for variable substitution.
:param query: Format query string (can include {{variable}} placeholders)
:param element: Optional IFC element for variable substitution
:return: Formatted string
Example:
format("{{z}} / 2", element) # Substitutes element's z value
format("imperial_length({{z}} / 2, 4)", element) # Uses z in calculation
"""
return FormatTransformer(element).transform(format_grammar.parse(query))
def get_element_value(element: ifcopenshell.entity_instance, query: str) -> Any:
@@ -1160,4 +1257,4 @@ class FacetTransformer(lark.Transformer):
if comparison.startswith("!"):
return not result
return result
return result