View Issue Details
| ID | Project | Category | View Status | Date Submitted | Last Update |
|---|---|---|---|---|---|
| 0004152 | NoesisGUI | C++ SDK | public | 2025-05-12 16:47 | 2026-04-16 13:21 |
| Reporter | Marco Pisanu | Assigned To | jsantos | ||
| Priority | normal | Severity | minor | ||
| Status | resolved | Resolution | fixed | ||
| Product Version | 3.2.7 | ||||
| Target Version | 3.2.13 | Fixed in Version | 4.0 | ||
| Summary | 0004152: MathConverter not existing | ||||
| Description | The MathConverter, used by Noesis Studio, does not seem to exist in the C++ SDK, causing UI elements created by Noesis Studio using the MathConverter to not function. | ||||
| Platform | Any | ||||
|
We are working on this. As a workaround, I am attaching the implementation of ArithmeticParser.cpp (12,578 bytes)
////////////////////////////////////////////////////////////////////////////////////////////////////
// NoesisGUI - http://www.noesisengine.com
// Copyright (c) 2013 Noesis Technologies S.L. All Rights Reserved.
////////////////////////////////////////////////////////////////////////////////////////////////////
#include "ArithmeticParser.h"
#include <NsCore/StringUtils.h>
using namespace Noesis;
namespace
{
struct Operator
{
enum class Associativity
{
Right, Left, None
};
char op;
uint32_t precedence;
Associativity associativity;
float (*func)(BaseVector<float>& stack);
};
}
////////////////////////////////////////////////////////////////////////////////////////////////////
static float Pop(BaseVector<float>& stack)
{
if (!stack.Empty())
{
float v = stack.Back();
stack.PopBack();
return v;
}
return 0.0f;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
static Operator ParseFunc(const char* expr, uint32_t& parsed)
{
if (StrStartsWith(expr, "min"))
{
parsed = (uint32_t)strlen("min");
return Operator{ 'F', 10, Operator::Associativity::None, [](BaseVector<float>& stack)
{
float b = Pop(stack);
float a = Pop(stack);
return Min(a, b);
}};
}
else if (StrStartsWith(expr, "max"))
{
parsed = (uint32_t)strlen("max");
return Operator{ 'F', 10, Operator::Associativity::None, [](BaseVector<float>& stack)
{
float b = Pop(stack);
float a = Pop(stack);
return Max(a, b);
}};
}
else if (StrStartsWith(expr, "floor"))
{
parsed = (uint32_t)strlen("floor");
return Operator{ 'F', 10, Operator::Associativity::None, [](BaseVector<float>& stack)
{
float a = Pop(stack);
return Floor(a);
}};
}
else if (StrStartsWith(expr, "round"))
{
parsed = (uint32_t)strlen("round");
return Operator{ 'F', 10, Operator::Associativity::None, [](BaseVector<float>& stack)
{
float a = Pop(stack);
return (float)Round(a);
}};
}
else if (StrStartsWith(expr, "ceil"))
{
parsed = (uint32_t)strlen("ceil");
return Operator{ 'F', 10, Operator::Associativity::None, [](BaseVector<float>& stack)
{
float a = Pop(stack);
return Ceil(a);
} };
}
else if (StrStartsWith(expr, "sin"))
{
parsed = (uint32_t)strlen("sin");
return Operator{ 'F', 10, Operator::Associativity::None, [](BaseVector<float>& stack)
{
float a = Pop(stack);
return sinf(a);
}};
}
else if (StrStartsWith(expr, "cos"))
{
parsed = (uint32_t)strlen("cos");
return Operator{ 'F', 10, Operator::Associativity::None, [](BaseVector<float>& stack)
{
float a = Pop(stack);
return cosf(a);
}};
}
else if (StrStartsWith(expr, "pow"))
{
parsed = (uint32_t)strlen("pow");
return Operator{ 'F', 10, Operator::Associativity::None, [](BaseVector<float>& stack)
{
float b = Pop(stack);
float a = Pop(stack);
return powf(a, b);
}};
}
else if (StrStartsWith(expr, "abs"))
{
parsed = (uint32_t)strlen("abs");
return Operator{ 'F', 10, Operator::Associativity::None, [](BaseVector<float>& stack)
{
float a = Pop(stack);
return fabsf(a);
} };
}
parsed = 0;
return Operator{};
}
////////////////////////////////////////////////////////////////////////////////////////////////////
static bool ParseOperator(const char* expr, Operator& op, bool binaryAllowed)
{
if (binaryAllowed)
{
switch (*expr)
{
case '*':
{
op = { '*', 8, Operator::Associativity::Left, [](BaseVector<float>& stack)
{
float b = Pop(stack);
float a = Pop(stack);
return a * b;
}};
return true;
}
case '/':
{
op = { '/', 8, Operator::Associativity::Left, [](BaseVector<float>& stack)
{
float b = Pop(stack);
float a = Pop(stack);
return a / b;
}};
return true;
}
case '%':
{
op = { '%', 8, Operator::Associativity::Left, [](BaseVector<float>& stack)
{
float b = Pop(stack);
float a = Pop(stack);
return fmodf(a, b);
}};
return true;
}
case '+':
{
op = { '+', 6, Operator::Associativity::Left, [](BaseVector<float>& stack)
{
float b = Pop(stack);
float a = Pop(stack);
return a + b;
}};
return true;
}
case '-':
{
op = { '-', 6, Operator::Associativity::Left, [](BaseVector<float>& stack)
{
float b = Pop(stack);
float a = Pop(stack);
return a - b;
}};
return true;
}
}
}
switch (*expr)
{
case '+':
{
// Unary plus
op = { '+', 9, Operator::Associativity::Right, [](BaseVector<float>& stack)
{
float a = Pop(stack);
return a;
}};
return true;
}
case '-':
{
// Unary minus
op = { '-', 9, Operator::Associativity::Right, [](BaseVector<float>& stack)
{
float a = Pop(stack);
return -a;
}};
return true;
}
case '(':
{
op= { '(', 0, Operator::Associativity::None, nullptr};
return true;
}
case ')':
{
op= { ')', 0, Operator::Associativity::None, nullptr};
return true;
}
}
return false;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
static float ParseConstant(const char* expr, ArrayRef<float> inputs, float param, uint32_t& parsed)
{
if (expr[0] == '{')
{
uint32_t charParsed;
uint32_t N = StrToUInt32(expr + 1, &charParsed);
if (N < inputs.Size() && charParsed > 0 && expr[charParsed + 1] == '}')
{
parsed = charParsed + 2;
return inputs[N];
}
}
if (StrStartsWith(expr, "{_}"))
{
parsed = 3;
return param;
}
parsed = 0;
return 0;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
bool Parse(const char* expr, ArrayRef<float> inputs, float param, float& value)
{
uint32_t numChars = (uint32_t)strlen(expr);
uint32_t pos = 0;
struct Out
{
enum Type
{
Value, Op
};
Type type;
union
{
float v;
Operator op;
};
};
Vector<Out, 256> queue;
Vector<Operator, 128> stack;
bool binaryOpAllowed = false;
while (pos < numChars)
{
// Skip spaces
while (expr[pos] == ' ') pos++;
if (pos == numChars) break;
uint32_t parsed;
// Function
Operator func = ParseFunc(expr + pos, parsed);
if (parsed > 0)
{
stack.PushBack(func);
pos += parsed;
binaryOpAllowed = false;
continue;
}
Operator op;
if (ParseOperator(expr + pos, op, binaryOpAllowed))
{
// Operator
if (op.op != '(' && op.op != ')')
{
while (!stack.Empty() && stack.Back().op != '(')
{
Operator op2 = stack.Back();
if (op2.precedence > op.precedence || (op2.precedence == op.precedence &&
op.associativity == Operator::Associativity::Left))
{
stack.PopBack();
Out out;
out.type = Out::Type::Op;
out.op = op2;
queue.PushBack(out);
}
else break;
}
stack.PushBack(op);
pos++;
binaryOpAllowed = false;
continue;
}
// Left parenthesis
if (op.op == '(')
{
stack.PushBack(op);
pos++;
binaryOpAllowed = false;
continue;
}
// Right parenthesis
if (op.op == ')')
{
while (!stack.Empty() && stack.Back().op != '(')
{
Out out;
out.type = Out::Type::Op;
out.op = stack.Back();
stack.PopBack();
queue.PushBack(out);
}
if (stack.Empty() || stack.Back().op != '(')
{
// Mismatched parentheses
return false;
}
NS_ASSERT(!stack.Empty() && stack.Back().op == '(');
stack.PopBack();
// If there is a function in the stack, move it to the queue
if (!stack.Empty() && stack.Back().op == 'F')
{
Out out;
out.type = Out::Type::Op;
out.op = stack.Back();
stack.PopBack();
queue.PushBack(out);
}
pos++;
binaryOpAllowed = true;
continue;
}
}
// Comma
if (expr[pos] == ',')
{
while (!stack.Empty() && stack.Back().op != '(')
{
Out out;
out.type = Out::Type::Op;
out.op = stack.Back();
stack.PopBack();
queue.PushBack(out);
}
pos++;
binaryOpAllowed = false;
continue;
}
// Number
float v = StrToFloat(expr + pos, &parsed);
if (parsed > 0)
{
queue.PushBack(Out{ Out::Type::Value, v });
pos += parsed;
binaryOpAllowed = true;
continue;
}
// Constant
v = ParseConstant(expr + pos, inputs, param, parsed);
if (parsed > 0)
{
queue.PushBack(Out{ Out::Type::Value, v });
pos += parsed;
binaryOpAllowed = true;
continue;
}
// Invalid expression
return false;
}
// Pop the remaining items from the operator stack into the output queue
while (!stack.Empty())
{
if (stack.Back().op == '(')
{
// Mismatched parentheses
return false;
}
Out out;
out.type = Out::Type::Op;
out.op = stack.Back();
stack.PopBack();
queue.PushBack(out);
}
// RPN evaluation
Vector<float, 128> values;
for (const Out& o : queue)
{
if (o.type == Out::Type::Value)
{
values.PushBack(o.v);
}
else
{
values.PushBack(o.op.func(values));
}
}
if (values.Size() != 1)
{
return false;
}
NS_ASSERT(values.Size() == 1);
value = values.Back();
return true;
}
MathConverter.h (1,754 bytes)
////////////////////////////////////////////////////////////////////////////////////////////////////
// NoesisGUI - http://www.noesisengine.com
// Copyright (c) 2013 Noesis Technologies S.L. All Rights Reserved.
////////////////////////////////////////////////////////////////////////////////////////////////////
#ifndef __APP_MATHCONVERTER_H__
#define __APP_MATHCONVERTER_H__
#include <NsCore/Noesis.h>
#include <NsCore/String.h>
#include <NsGui/BaseValueConverter.h>
#include <NsGui/IMultiValueConverter.h>
namespace NoesisApp
{
////////////////////////////////////////////////////////////////////////////////////////////////////
class MathConverter final : public Noesis::BaseValueConverter, public Noesis::IMultiValueConverter
{
public:
MathConverter();
const char* GetExpression() const;
void SetExpression(const char* expr);
/// From IValueConverter
//@{
bool TryConvert(Noesis::BaseComponent* value, const Noesis::Type* targetType,
Noesis::BaseComponent* parameter, Noesis::Ptr<Noesis::BaseComponent>& result) final;
//@}
/// From IMultiValueConverter
//@{
bool TryConvert(Noesis::ArrayRef<Noesis::BaseComponent*> values, const Noesis::Type* targetType,
Noesis::BaseComponent* parameter, Noesis::Ptr<Noesis::BaseComponent>& result) override;
bool TryConvertBack(Noesis::BaseComponent* value,
Noesis::ArrayRef<const Noesis::Type*> targetTypes, Noesis::BaseComponent* parameter,
Noesis::BaseVector<Noesis::Ptr<Noesis::BaseComponent>>& results) override;
//@}
NS_IMPLEMENT_INTERFACE_FIXUP
private:
Noesis::String mExpr;
NS_DECLARE_REFLECTION(MathConverter, BaseValueConverter)
};
}
#endif
MathConverter.cpp (3,138 bytes)
////////////////////////////////////////////////////////////////////////////////////////////////////
// NoesisGUI - http://www.noesisengine.com
// Copyright (c) 2013 Noesis Technologies S.L. All Rights Reserved.
////////////////////////////////////////////////////////////////////////////////////////////////////
#include "MathConverter.h"
#include "ArithmeticParser.h"
#include <NsCore/ReflectionImplement.h>
#ifdef NS_HAVE_STUDIO
#include <NsCore/Category.h>
#endif
#include <math.h>
using namespace Noesis;
using namespace NoesisApp;
////////////////////////////////////////////////////////////////////////////////////////////////////
MathConverter::MathConverter(): mExpr("{0}")
{
}
////////////////////////////////////////////////////////////////////////////////////////////////////
const char* MathConverter::GetExpression() const
{
return mExpr.Str();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void MathConverter::SetExpression(const char* expr)
{
mExpr = expr;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
bool MathConverter::TryConvert(BaseComponent* value, const Type*, BaseComponent* parameter,
Ptr<BaseComponent>& result)
{
if (!mExpr.Empty())
{
float input = value ? (float)atof(value->ToString().Str()) : 0.0f;
float param = parameter ? (float)atof(parameter->ToString().Str()) : 0.0f;
float output;
if (Parse(mExpr.Str(), input, param, output))
{
result.Reset(Boxing::Box(output));
return true;
}
}
return false;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
bool MathConverter::TryConvert(ArrayRef<BaseComponent*> values, const Type*,
BaseComponent* parameter, Ptr<BaseComponent>& result)
{
if (!mExpr.Empty())
{
Vector<float, 8> inputs;
for (uint32_t i = 0; i < values.Size(); ++i)
{
inputs.PushBack(values[i] ? (float)atof(values[i]->ToString().Str()) : 0.0f);
}
float param = parameter ? (float)atof(parameter->ToString().Str()) : 0.0f;
float output;
if (Parse(mExpr.Str(), inputs, param, output))
{
result.Reset(Boxing::Box(output));
return true;
}
}
return false;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
bool MathConverter::TryConvertBack(BaseComponent*, ArrayRef<const Type*>, BaseComponent*,
BaseVector<Ptr<BaseComponent>>&)
{
return false;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
NS_BEGIN_COLD_REGION
NS_IMPLEMENT_REFLECTION(MathConverter, "NoesisGUIExtensions.MathConverter")
{
#ifdef NS_HAVE_STUDIO
NsMeta<Category>("Converters");
#endif
NsImpl<IMultiValueConverter>();
NsProp("Expression", &MathConverter::mExpr);
}
NS_END_COLD_REGION
ArithmeticParser.h (829 bytes)
////////////////////////////////////////////////////////////////////////////////////////////////////
// NoesisGUI - http://www.noesisengine.com
// Copyright (c) 2013 Noesis Technologies S.L. All Rights Reserved.
////////////////////////////////////////////////////////////////////////////////////////////////////
#ifndef __APP_ARITHMETICPARSER_H__
#define __APP_ARITHMETICPARSER_H__
#include <NsCore/ArrayRef.h>
// Evaluates the mathematical expression, substituting the provided input values for any variables
// in the expression. Variables in the expression are expected to be in the form {N}, where N is
// the index of the corresponding input value in the inputs array. {_} is used for the float param
bool Parse(const char* expr, Noesis::ArrayRef<float> inputs, float param, float& value);
#endif
|
|
|
Great to hear, and thanks for providing a workaround! |
|
|
This is now available in the beta of 4.0 |
|
| Date Modified | Username | Field | Change |
|---|---|---|---|
| 2025-05-12 16:47 | Marco Pisanu | New Issue | |
| 2025-05-13 11:38 | jsantos | Assigned To | => jsantos |
| 2025-05-13 11:38 | jsantos | Status | new => assigned |
| 2025-05-13 11:38 | jsantos | Target Version | => Studio_Beta |
| 2025-05-13 11:39 | jsantos | Target Version | Studio_Beta => 3.2.8 |
| 2025-05-13 11:41 | jsantos | Note Added: 0010635 | |
| 2025-05-13 11:41 | jsantos | File Added: ArithmeticParser.cpp | |
| 2025-05-13 11:41 | jsantos | File Added: MathConverter.h | |
| 2025-05-13 11:41 | jsantos | File Added: MathConverter.cpp | |
| 2025-05-13 11:41 | jsantos | File Added: ArithmeticParser.h | |
| 2025-05-13 23:49 | Marco Pisanu | Note Added: 0010644 | |
| 2025-05-28 10:37 | jsantos | Target Version | 3.2.8 => 3.2.9 |
| 2025-10-02 00:49 | jsantos | Target Version | 3.2.9 => 3.2.10 |
| 2025-10-20 18:25 | jsantos | Target Version | 3.2.10 => 3.2.11 |
| 2026-01-20 19:32 | jsantos | Target Version | 3.2.11 => 3.2.12 |
| 2026-03-04 00:39 | jsantos | Target Version | 3.2.12 => 3.2.13 |
| 2026-04-16 13:20 | jsantos | Fixed in Version | => 4.0 |
| 2026-04-16 13:21 | jsantos | Status | assigned => resolved |
| 2026-04-16 13:21 | jsantos | Resolution | open => fixed |
| 2026-04-16 13:21 | jsantos | Note Added: 0012218 |