Skip to content

Commit 42927ad

Browse files
committed
Add ExpressionTextField component for float/int TextFields with expressions
1 parent ad20cc5 commit 42927ad

File tree

6 files changed

+100
-14
lines changed

6 files changed

+100
-14
lines changed

meshroom/core/desc/attribute.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import os
44
import types
55
from collections.abc import Iterable
6+
import math
67

78
from meshroom.common import BaseObject, JSValue, Property, Variant, VariantList
89

@@ -341,7 +342,7 @@ def __init__(self, name, label, description, value, range=None, group="allParams
341342
self._valueType = int
342343

343344
def validateValue(self, value):
344-
if value is None:
345+
if value is None or math.isnan(value):
345346
return value
346347
# Handle unsigned int values that are translated to int by shiboken and may overflow
347348
try:
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import QtQuick
2+
import QtQuick.Controls
3+
4+
TextField {
5+
id: root
6+
7+
property bool hasExprError: false
8+
property bool isInt: false // If not then it's a float
9+
10+
signal expressionEditingFinished()
11+
signal expressionAccepted()
12+
13+
// Overlay for error state
14+
Rectangle {
15+
anchors.fill: parent
16+
radius: 4
17+
border.color: "red"
18+
color: "transparent"
19+
visible: root.hasExprError || root.text == "NaN"
20+
z: 1
21+
}
22+
23+
function evalExpression() {
24+
try {
25+
if (root.text == "NaN") {
26+
hasExprError = true
27+
return
28+
}
29+
var result = MathEvaluator.eval(root.text)
30+
if (isInt) {
31+
result = parseInt(result)
32+
}
33+
root.text = result
34+
hasExprError = false
35+
} catch (err) {
36+
console.error("Error evaluating expression (", root.text,"):", err)
37+
hasExprError = true
38+
root.text = "NaN"
39+
}
40+
}
41+
42+
onEditingFinished : {
43+
evalExpression()
44+
}
45+
46+
onAccepted : {
47+
evalExpression()
48+
}
49+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
.pragma library
2+
3+
4+
var symbols = {
5+
pi: Math.PI,
6+
e: Math.E,
7+
abs: Math.abs,
8+
min: Math.min,
9+
max: Math.max,
10+
sin: Math.sin,
11+
cos: Math.cos,
12+
tan: Math.tan,
13+
pow: Math.pow,
14+
sqrt: Math.sqrt,
15+
exp: Math.exp,
16+
log: Math.log
17+
};
18+
19+
20+
/**
21+
* Evaluate an expression
22+
*
23+
* @param {*} expr Math expression
24+
* @returns float or int
25+
*/
26+
function eval(expr) {
27+
// Replace symbols
28+
for (var symbol in symbols) {
29+
// Match each symbol only if they are at the beginning or end of the word
30+
expr = expr.replace(new RegExp("\\b" + symbol + "\\b", "g"), symbols[symbol]);
31+
}
32+
33+
// Additionally replace the "," to "."
34+
expr = expr.replace(',','.')
35+
36+
// Only allow numbers, operators, parentheses, and function names
37+
if (!/^[0-9+\-*/^().,\s]*$/.test(expr.replace(/\b[a-zA-Z]+\b/g, ""))) {
38+
throw "Invalid characters in expression";
39+
}
40+
41+
// Eval with function to avoid issues with undeclared variables
42+
return Function('"use strict"; return (' + expr + ')')();
43+
}

meshroom/ui/qml/Controls/qmldir

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
module Controls
22

3+
MathEvaluator 1.0 mathEvaluator.js
34
ColorChart 1.0 ColorChart.qml
45
ColorSelector 1.0 ColorSelector.qml
56
ExpandableGroup 1.0 ExpandableGroup.qml
@@ -21,3 +22,4 @@ SelectionBox 1.0 SelectionBox.qml
2122
SelectionLine 1.0 SelectionLine.qml
2223
DelegateSelectionBox 1.0 DelegateSelectionBox.qml
2324
DelegateSelectionLine 1.0 DelegateSelectionLine.qml
25+
ExpressionTextField 1.0 ExpressionTextField.qml

meshroom/ui/qml/GraphEditor/AttributeItemDelegate.qml

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -605,14 +605,7 @@ RowLayout {
605605
Component {
606606
id: sliderComponent
607607
RowLayout {
608-
TextField {
609-
IntValidator {
610-
id: intValidator
611-
}
612-
DoubleValidator {
613-
id: doubleValidator
614-
locale: 'C' // Use '.' decimal separator disregarding the system locale
615-
}
608+
ExpressionTextField {
616609
implicitWidth: 100
617610
Layout.fillWidth: !slider.active
618611
enabled: root.editable
@@ -624,7 +617,7 @@ RowLayout {
624617
// When the value change keep the text align to the left to be able to read the most important part
625618
// of the number. When we are editing (item is in focus), the content should follow the editing.
626619
autoScroll: activeFocus
627-
validator: attribute.type === "FloatParam" ? doubleValidator : intValidator
620+
isInt: attribute.type === "FloatParam" ? false : true
628621
onEditingFinished: setTextFieldAttribute(text)
629622
onAccepted: {
630623
setTextFieldAttribute(text)

meshroom/ui/qml/Viewer/HdrImageToolbar.qml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ FloatingPane {
114114
gainCtrl.value = gainDefaultValue
115115
}
116116
}
117-
TextField {
117+
ExpressionTextField {
118118
id: gainLabel
119119

120120
ToolTip.visible: ToolTip.text && hovered
@@ -124,7 +124,6 @@ FloatingPane {
124124
text: gainValue.toFixed(2)
125125
Layout.preferredWidth: textMetrics_gainValue.width
126126
selectByMouse: true
127-
validator: doubleValidator
128127
onAccepted: {
129128
gainCtrl.value = Math.pow(Number(gainLabel.text), 1.0 / slidersPowerValue)
130129
}
@@ -154,7 +153,7 @@ FloatingPane {
154153
gammaCtrl.value = gammaDefaultValue;
155154
}
156155
}
157-
TextField {
156+
ExpressionTextField {
158157
id: gammaLabel
159158

160159
ToolTip.visible: ToolTip.text && hovered
@@ -164,7 +163,6 @@ FloatingPane {
164163
text: gammaValue.toFixed(2)
165164
Layout.preferredWidth: textMetrics_gainValue.width
166165
selectByMouse: true
167-
validator: doubleValidator
168166
onAccepted: {
169167
gammaCtrl.value = Math.pow(Number(gammaLabel.text), 1.0 / slidersPowerValue)
170168
}

0 commit comments

Comments
 (0)