Skip to content

Commit 11015a3

Browse files
committed
[ui] expressionTextField : move evaluation to python
1 parent 9b003f6 commit 11015a3

File tree

4 files changed

+87
-18
lines changed

4 files changed

+87
-18
lines changed

meshroom/core/evaluation.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#!/usr/bin/env python
2+
3+
import ast, math
4+
5+
6+
class MathEvaluator:
7+
""" Evaluate math expressions
8+
9+
..code::py
10+
# Example usage
11+
mev = MathEvaluator()
12+
print(mev.evaluate("e-1+cos(2*pi)"))
13+
print(mev.evaluate("pow(2, 8)"))
14+
print(mev.evaluate("round(sin(pi), 3)"))
15+
"""
16+
17+
# Allowed math symbols
18+
allowed_symbols = {
19+
"e": math.e, "pi": math.pi,
20+
"cos": math.cos, "sin": math.sin, "tan": math.tan, "exp": math.exp,
21+
"pow": pow, "round": round, "abs": abs, "min": min, "max": max,
22+
"sqrt": math.sqrt, "log": math.log
23+
}
24+
# Allowed AST node types
25+
allowed_nodes = (
26+
ast.Expression, ast.BinOp, ast.UnaryOp, ast.Call, ast.Name, ast.Load,
27+
ast.Add, ast.Sub, ast.Mult, ast.Div, ast.Pow, ast.Mod, ast.FloorDiv,
28+
ast.USub, ast.UAdd, ast.BitXor, ast.BitOr, ast.BitAnd,
29+
ast.LShift, ast.RShift, ast.Invert,
30+
ast.Constant
31+
)
32+
def _validate_ast(self, node):
33+
for child in ast.walk(node):
34+
if not isinstance(child, self.allowed_nodes):
35+
raise ValueError(f"Bad expression: {ast.dump(child)}")
36+
# Check that all variable/function names are whitelisted
37+
if isinstance(child, ast.Name):
38+
if child.id not in self.allowed_symbols:
39+
raise ValueError(f"Unknown symbol: {child.id}")
40+
def evaluate(self, expr: str):
41+
if any(bad in expr for bad in ('\n', '#')):
42+
raise ValueError(f"Invalid expression: {expr}")
43+
try:
44+
node = ast.parse(expr.strip(), mode="eval")
45+
self._validate_ast(node)
46+
return eval(compile(node, "<expr>", "eval"), {"__builtins__": {}}, self.allowed_symbols)
47+
except Exception:
48+
raise ValueError(f"Invalid expression: {expr}")

meshroom/ui/qml/Utils/ExpressionTextField.qml

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,15 @@ TextField {
4141
}
4242

4343
function getEvalExpression(_text) {
44-
try {
45-
var result = MathEvaluator.eval(_text)
44+
var [_res, _err] = _reconstruction.evaluateMathExpression(_text)
45+
if (_err == false) {
4646
if (isInt)
47-
result = Math.round(result)
47+
_res = Math.round(_res)
4848
else
49-
result = result.toFixed(decimals)
50-
return result
51-
} catch (err) {
52-
console.error("Error evaluating expression (", _text, "):", err)
49+
_res = _res.toFixed(decimals)
50+
return _res
51+
} else {
52+
console.error("Error evaluating expression (", _text, "):", _err)
5353
return NaN
5454
}
5555
}
@@ -63,24 +63,28 @@ TextField {
6363
}
6464

6565
function updateExpression() {
66+
var previousEvaluatedValue = evaluatedValue
6667
var result = getEvalExpression(root.text)
68+
console.log("[ExpressionTextField] updateExpression", root.text, "->", result)
6769
if (!isNaN(result)) {
6870
evaluatedValue = result
6971
clearError()
70-
return result
72+
// return result
7173
} else {
72-
evaluatedValue = NaN
74+
evaluatedValue = previousEvaluatedValue
7375
raiseError()
74-
return NaN
76+
// return NaN
7577
}
7678
}
7779

7880
// When user commits input, evaluate but do NOT overwrite text
7981
onAccepted: {
82+
console.log("[ExpressionTextField] onAccepted", root.text)
8083
updateExpression()
8184
}
8285

8386
onEditingFinished: {
87+
console.log("[ExpressionTextField] onEditingFinished", root.text)
8488
updateExpression()
8589
}
8690

@@ -91,6 +95,7 @@ TextField {
9195
}
9296

9397
Component.onCompleted: {
98+
console.log("[ExpressionTextField] onCompleted", root.text)
9499
refreshStatus()
95100
}
96101
}

meshroom/ui/qml/Viewer/HdrImageToolbar.qml

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -129,15 +129,15 @@ FloatingPane {
129129
selectByMouse: true
130130
onAccepted: {
131131
if (!gainLabel.hasExprError) {
132-
if (gainLabel.evaluatedValue <= 0) {
133-
gainLabel.evaluatedValue = 0
134-
gainCtrl.value = gainLabel.evaluatedValue
132+
if (gainLabel.text <= 0) {
133+
gainLabel.text = 0
134+
gainCtrl.value = gainLabel.text
135135
} else {
136-
gainCtrl.value = Math.pow(Number(gainLabel.evaluatedValue), 1.0 / slidersPowerValue)
136+
gainCtrl.value = Math.pow(Number(gainLabel.text), 1.0 / slidersPowerValue)
137137
}
138138
} else {
139-
gainLabel.evaluatedValue = 0
140-
gainCtrl.value = gainLabel.evaluatedValue
139+
// gainLabel.text = 0
140+
// gainCtrl.value = gainLabel.text
141141
}
142142
}
143143
}
@@ -180,6 +180,7 @@ FloatingPane {
180180
Layout.preferredWidth: textMetrics_gainValue.width
181181
selectByMouse: true
182182
onAccepted: {
183+
console.log("[GammaTextField] onAccepted")
183184
if (!gammaLabel.hasExprError) {
184185
if (gammaLabel.evaluatedValue <= 0) {
185186
gammaLabel.evaluatedValue = 0
@@ -188,8 +189,8 @@ FloatingPane {
188189
gammaCtrl.value = Math.pow(Number(gammaLabel.evaluatedValue), 1.0 / slidersPowerValue)
189190
}
190191
} else {
191-
gainLabel.evaluatedValue = 0
192-
gainCtrl.value = gainLabel.evaluatedValue
192+
// gammaLabel.evaluatedValue = 0
193+
// gammaCtrl.value = gainLabel.evaluatedValue
193194
}
194195
}
195196
}

meshroom/ui/reconstruction.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from meshroom.core import Version
1919
from meshroom.core.node import Node, CompatibilityNode, Status, Position, CompatibilityIssue
2020
from meshroom.core.taskManager import TaskManager
21+
from meshroom.core.evaluation import MathEvaluator
2122

2223
from meshroom.ui import commands
2324
from meshroom.ui.graph import UIGraph
@@ -1166,6 +1167,20 @@ def setCurrentViewPath(self, path):
11661167
self._currentViewPath = path
11671168
self.currentViewPathChanged.emit()
11681169

1170+
@Slot(str, result="QVariantList")
1171+
def evaluateMathExpression(self, expr):
1172+
""" Evaluate a mathematical expression and return the result as a string
1173+
Returns a list of 2 values :
1174+
- the result value
1175+
- a boolean that indicate if an error occured
1176+
"""
1177+
mev = MathEvaluator()
1178+
try:
1179+
res = mev.evaluate(expr)
1180+
return [res, False]
1181+
except Exception as err:
1182+
return [None, err]
1183+
11691184
selectedViewIdChanged = Signal()
11701185
selectedViewId = Property(str, lambda self: self._selectedViewId, setSelectedViewId, notify=selectedViewIdChanged)
11711186
selectedViewpointChanged = Signal()

0 commit comments

Comments
 (0)