Upstream Snapshot bb87928bce Import upstream snapshot 80e710700aac9573a2230f74f7ce9e094833a0bc
Upstream: https://github.com/Rhoban/onshape-to-robot
Upstream-Commit: 80e710700aac9573a2230f74f7ce9e094833a0bc
Upstream-Branch: master
2026-08-28 15:42:31 +08:00

120 lines
3.4 KiB
Python

import numpy as np
import ast
import operator as op
class ExpressionParser:
"""
Evaluate Onshape expression
See https://cad.onshape.com/help/Content/numeric-fields.htm
This parser is based on ast module.
"""
def __init__(self):
self.variables_lazy_loading = None
self.variables = {
"pi": np.pi, "Pi": np.pi, "PI": np.pi
}
# Supported operators
operators = {
ast.Add: op.add,
ast.Sub: op.sub,
ast.Mult: op.mul,
ast.Div: op.truediv,
ast.Pow: op.pow,
ast.BitXor: op.xor,
ast.USub: op.neg,
ast.Mod: op.mod,
}
# Supporter functions
functions = {
"cos": np.cos,
"sin": np.sin,
"tan": np.tan,
"acos": np.arccos,
"asin": np.arcsin,
"atan": np.arctan,
"atan2": np.arctan2,
"cosh": np.cosh,
"sinh": np.sinh,
"tanh": np.tanh,
"asinh": np.arcsinh,
"acosh": np.arccosh,
"atanh": np.arctanh,
"ceil": np.ceil,
"floor": np.floor,
"round": np.round,
"exp": np.exp,
"sqrt": np.sqrt,
"abs": np.abs,
"max": np.max,
"min": np.min,
"log": np.log,
"log10": np.log10,
}
def eval_expr(self, expr):
# Length units. Converting everything to meter / radian
units = {
"millimeter": 1e-3,
"mm": 1e-3,
"centimeter": 1e-2,
"cm": 1e-2,
"meter": 1.0,
"inch": 0.0254,
"in": 0.0254,
"foot": 0.3048,
"ft": 0.3048,
"yard": 0.9144,
"yd": 0.9144,
"radian": 1.0,
"rad": 1.0,
"degree": np.pi / 180,
"deg": np.pi / 180,
"m": 1.0
}
for unit, factor in units.items():
expr = expr.replace(f" {unit}", f"*{factor}")
expr = expr.replace("#", "")
expr = expr.replace("^", "**")
return self.eval_(ast.parse(expr, mode="eval").body)
def eval_(self, node):
if isinstance(node, ast.Constant):
return float(node.value)
elif isinstance(node, ast.BinOp):
return self.operators[type(node.op)](
self.eval_(node.left), self.eval_(node.right)
)
elif isinstance(node, ast.UnaryOp): # e.g., -1
return self.operators[type(node.op)](self.eval_(node.operand))
elif isinstance(node, ast.Name):
if (
node.id not in self.variables
and self.variables_lazy_loading is not None
):
self.variables_lazy_loading()
self.variables_lazy_loading = None
if node.id not in self.variables:
raise ValueError(f"Unknown variable in expression: {node.id}")
return self.variables[node.id]
elif isinstance(node, ast.Call):
if node.func.id not in self.functions:
raise ValueError(f"Unknown function in expression: {node.func.id}")
return self.functions[node.func.id](*[self.eval_(arg) for arg in node.args])
else:
raise TypeError(node)
if __name__ == "__main__":
ep = ExpressionParser()
ep.variables["x"] = 5
print(ep.eval_expr("(cos(5 deg)) mm + #x inch"))
print(ep.eval_expr("-sin(3/(2^2) deg)"))