diff --git a/src/Vehicle/Actuators/Mixer.cc b/src/Vehicle/Actuators/Mixer.cc index bdfd54b..e81bdda 100644 --- a/src/Vehicle/Actuators/Mixer.cc +++ b/src/Vehicle/Actuators/Mixer.cc @@ -15,6 +15,215 @@ using namespace Mixer; +ChannelConfigInstance* ChannelConfig::instantiate(int paramIndex, int actuatorTypeIndex, + ParameterManager* parameterManager, std::function factAddedCb) +{ + QString param = _parameter.param.name; + int usedParamIndex; + if (_isActuatorTypeConfig) { + usedParamIndex = actuatorTypeIndex + indexOffset(); + } else { + usedParamIndex = paramIndex + indexOffset(); + } + param.replace("${i}", QString::number(usedParamIndex)); + + Fact* fact = nullptr; + if (param == "" && !_isActuatorTypeConfig) { // constant value + float value = 0.f; + if (fixedValues().size() == 1) { + value = fixedValues()[0]; + } else if (paramIndex < fixedValues().size()) { + value = fixedValues()[paramIndex]; + } + + FactMetaData* metaData = new FactMetaData(FactMetaData::valueTypeFloat, "", this); + metaData->setReadOnly(true); + metaData->setDecimalPlaces(4); + fact = new Fact("", metaData, this); + fact->setRawValue(value); + + } else if (parameterManager->parameterExists(FactSystem::defaultComponentId, param)) { + fact = parameterManager->getParameter(FactSystem::defaultComponentId, param); + if (displayOption() == Parameter::DisplayOption::Bitset) { + fact = new FactBitset(this, fact, usedParamIndex); + } else if (displayOption() == Parameter::DisplayOption::BoolTrueIfPositive) { + fact = new FactFloatAsBool(this, fact); + } + factAddedCb(function(), fact); + } else { + qCDebug(ActuatorsConfigLog) << "ActuatorOutputChannel: Param does not exist:" << param; + } + + ChannelConfigInstance* instance = new ChannelConfigInstance(this, fact, *this); + channelInstanceCreated(instance); + return instance; +} + +void ChannelConfig::channelInstanceCreated(ChannelConfigInstance* instance) +{ + _instances.append(instance); + connect(instance, &ChannelConfigInstance::visibleChanged, + this, &ChannelConfig::instanceVisibleChanged); +} + +void ChannelConfig::instanceVisibleChanged() +{ + bool visible = false; + for (const auto& instance : _instances) { + if (instance->visible()) { + visible = true; + } + } + + if (visible != _visible) { + _visible = visible; + emit visibleChanged(); + } +} + +ChannelConfigInstance* ChannelConfigVirtualAxis::instantiate(int paramIndex, int actuatorTypeIndex, + ParameterManager* parameterManager, std::function factAddedCb) +{ + ChannelConfigInstance* instance = new ChannelConfigInstanceVirtualAxis(this, *this); + channelInstanceCreated(instance); + return instance; +} + +void ChannelConfigInstanceVirtualAxis::allInstancesInitialized(QmlObjectListModel* configInstances) +{ + for (int i = 0; i < configInstances->count(); ++i) { + auto channelConfigInstance = configInstances->value(i); + if (channelConfigInstance->channelConfig()->function() == Function::AxisX) { + _axes[0] = channelConfigInstance; + } else if (channelConfigInstance->channelConfig()->function() == Function::AxisY) { + _axes[1] = channelConfigInstance; + } else if (channelConfigInstance->channelConfig()->function() == Function::AxisZ) { + _axes[2] = channelConfigInstance; + } + } + Q_ASSERT(_axes[0] && _axes[1] && _axes[2]); + + for (int i = 0; i < 3; ++i) { + if (!_axes[i]->fact()) + return; + } + + // Initialize fact + QStringList enumStrings{tr("Custom"), tr("Upwards"), tr("Downwards"), tr("Forwards"), tr("Backwards"), + tr("Leftwards"), tr("Rightwards")}; + QVariantList enumValues{0, 1, 2, 3, 4, 5, 6}; + FactMetaData* metaData = new FactMetaData(FactMetaData::valueTypeUint32, this); + metaData->setEnumInfo(enumStrings, enumValues); + _fact = new Fact("", metaData, this); + setFactFromAxes(); + + connect(_fact, &Fact::rawValueChanged, this, &ChannelConfigInstanceVirtualAxis::setAxesFromFact); + for (int i=0; i < 3; ++i) { + connect(_axes[i]->fact(), &Fact::rawValueChanged, + this, [this](){ ChannelConfigInstanceVirtualAxis::setFactFromAxes(true); }); + } + // Inherit visibility & enabled from the first axis + connect(_axes[0], &ChannelConfigInstance::visibleChanged, + this, &ChannelConfigInstanceVirtualAxis::axisVisibleChanged); + connect(_axes[0], &ChannelConfigInstance::enabledChanged, + this, &ChannelConfigInstanceVirtualAxis::axisEnableChanged); + axisVisibleChanged(); + axisEnableChanged(); +} + +void ChannelConfigInstanceVirtualAxis::axisVisibleChanged() +{ + if (_axes[0]->visibleRule() != visibleRule()) { + setVisibleRule(_axes[0]->visibleRule()); + } +} + +void ChannelConfigInstanceVirtualAxis::axisEnableChanged() +{ + if (_axes[0]->enabledRule() != enabledRule()) { + setEnabledRule(_axes[0]->enabledRule()); + } +} + +void ChannelConfigInstanceVirtualAxis::setFactFromAxes(bool keepVisible) +{ + if (_ignoreChange) { + return; + } + _ignoreChange = true; + float x = _axes[0]->fact()->rawValue().toFloat(); + float y = _axes[1]->fact()->rawValue().toFloat(); + float z = _axes[2]->fact()->rawValue().toFloat(); + Direction direction{Direction::Custom}; // set to custom if no match + const float eps = 0.00001f; + if (fabsf(x) < eps && fabsf(y) < eps && fabsf(z + 1.f) < eps) { + direction = Direction::Upwards; + } else if (fabsf(x) < eps && fabsf(y) < eps && fabsf(z - 1.f) < eps) { + direction = Direction::Downwards; + } else if (fabsf(x - 1.f) < eps && fabsf(y) < eps && fabsf(z) < eps) { + direction = Direction::Forwards; + } else if (fabsf(x + 1.f) < eps && fabsf(y) < eps && fabsf(z) < eps) { + direction = Direction::Backwards; + } else if (fabsf(x) < eps && fabsf(y + 1.f) < eps && fabsf(z) < eps) { + direction = Direction::Leftwards; + } else if (fabsf(x) < eps && fabsf(y - 1.f) < eps && fabsf(z) < eps) { + direction = Direction::Rightwards; + } + _fact->setRawValue((uint32_t)direction); + + bool visible = direction == Direction::Custom || keepVisible; + for(int i=0; i < 3; ++i) { + _axes[i]->setVisibleAxis(visible); + } + _ignoreChange = false; +} + +void ChannelConfigInstanceVirtualAxis::setAxesFromFact() +{ + if (_ignoreChange) { + return; + } + _ignoreChange = true; + int directionIdx = _fact->rawValue().toInt(); + + if (directionIdx > 0) { + Direction direction = (Direction)directionIdx; + float x{}, y{}, z{}; + switch (direction) { + case Direction::Upwards: + x = 0.f; y = 0.f; z = -1.f; + break; + case Direction::Downwards: + x = 0.f; y = 0.f; z = 1.f; + break; + case Direction::Forwards: + x = 1.f; y = 0.f; z = 0.f; + break; + case Direction::Backwards: + x = -1.f; y = 0.f; z = 0.f; + break; + case Direction::Leftwards: + x = 0.f; y = -1.f; z = 0.f; + break; + case Direction::Rightwards: + x = 0.f; y = 1.f; z = 0.f; + break; + case Direction::Custom: + break; + } + _axes[0]->fact()->setRawValue(x); + _axes[1]->fact()->setRawValue(y); + _axes[2]->fact()->setRawValue(z); + } + + + bool visible = directionIdx == 0; + for(int i=0; i < 3; ++i) { + _axes[i]->setVisibleAxis(visible); + } + _ignoreChange = false; +} + MixerChannel::MixerChannel(QObject *parent, const QString &label, int actuatorFunction, int paramIndex, int actuatorTypeIndex, QmlObjectListModel &channelConfigs, ParameterManager* parameterManager, const Rule* rule, std::function factAddedCb) @@ -23,44 +232,11 @@ MixerChannel::MixerChannel(QObject *parent, const QString &label, int actuatorFu { for (int i = 0; i < channelConfigs.count(); ++i) { auto channelConfig = channelConfigs.value(i); - QString param = channelConfig->parameter(); - int usedParamIndex; - if (channelConfig->isActuatorTypeConfig()) { - usedParamIndex = actuatorTypeIndex + channelConfig->indexOffset(); - } else { - usedParamIndex = paramIndex + channelConfig->indexOffset(); - } - param.replace("${i}", QString::number(usedParamIndex)); - - Fact* fact = nullptr; - if (param == "" && !channelConfig->isActuatorTypeConfig()) { // constant value - float value = 0.f; - if (channelConfig->fixedValues().size() == 1) { - value = channelConfig->fixedValues()[0]; - } else if (paramIndex < channelConfig->fixedValues().size()) { - value = channelConfig->fixedValues()[paramIndex]; - } - FactMetaData* metaData = new FactMetaData(FactMetaData::valueTypeFloat, "", this); - metaData->setReadOnly(true); - metaData->setDecimalPlaces(4); - fact = new Fact("", metaData, this); - fact->setRawValue(value); - - } else if (parameterManager->parameterExists(FactSystem::defaultComponentId, param)) { - fact = parameterManager->getParameter(FactSystem::defaultComponentId, param); - if (channelConfig->displayOption() == Parameter::DisplayOption::Bitset) { - fact = new FactBitset(channelConfig, fact, usedParamIndex); - } else if (channelConfig->displayOption() == Parameter::DisplayOption::BoolTrueIfPositive) { - fact = new FactFloatAsBool(channelConfig, fact); - } - factAddedCb(channelConfig->function(), fact); - } else { - qCDebug(ActuatorsConfigLog) << "ActuatorOutputChannel: Param does not exist:" << param; - } + ChannelConfigInstance* instance = channelConfig->instantiate(paramIndex, actuatorTypeIndex, parameterManager, factAddedCb); + Fact* fact = instance->fact(); // if we have a valid rule, check the identifiers - int applyIdentifierIdx = -1; if (rule) { if (channelConfig->identifier() == rule->selectIdentifier) { _ruleSelectIdentifierIdx = _configInstances->count(); @@ -70,7 +246,7 @@ MixerChannel::MixerChannel(QObject *parent, const QString &label, int actuatorFu } else { for (int i = 0; i < rule->applyIdentifiers.size(); ++i) { if (channelConfig->identifier() == rule->applyIdentifiers[i]) { - applyIdentifierIdx = i; + instance->setRuleApplyIdentifierIdx(i); } } } @@ -80,10 +256,14 @@ MixerChannel::MixerChannel(QObject *parent, const QString &label, int actuatorFu } } - ChannelConfigInstance* instance = new ChannelConfigInstance(this, fact, *channelConfig, applyIdentifierIdx); _configInstances->append(instance); } + for (int i = 0; i < _configInstances->count(); ++i) { + auto channelConfigInstance = _configInstances->value(i); + channelConfigInstance->allInstancesInitialized(_configInstances); + } + applyRule(true); } @@ -122,8 +302,8 @@ void MixerChannel::applyRule(bool noConstraints) // here we could notify the user that a constraint got applied... configInstance->fact()->setRawValue(factValue); } - configInstance->setVisible(!ruleItem.hidden); - configInstance->setEnabled(!ruleItem.disabled); + configInstance->setVisibleRule(!ruleItem.hidden); + configInstance->setEnabledRule(!ruleItem.disabled); } } @@ -131,8 +311,8 @@ void MixerChannel::applyRule(bool noConstraints) // no rule set for this value, just reset for (int i = 0; i < _configInstances->count(); ++i) { ChannelConfigInstance* configInstance = _configInstances->value(i); - configInstance->setVisible(true); - configInstance->setEnabled(true); + configInstance->setVisibleRule(true); + configInstance->setEnabledRule(true); } } _currentSelectIdentifierValue = value; @@ -280,9 +460,18 @@ void Mixers::update() } const Rule* selectedRule{nullptr}; // at most 1 rule can be applied + int axisIdx[3]{-1, -1, -1}; for (const auto& perItemParam : actuatorGroup.perItemParameters) { currentMixerGroup->addChannelConfig(new ChannelConfig(currentMixerGroup, perItemParam, false)); + if (perItemParam.function == Function::AxisX) { + axisIdx[0] = currentMixerGroup->channelConfigs()->count() - 1; + } else if (perItemParam.function == Function::AxisY) { + axisIdx[1] = currentMixerGroup->channelConfigs()->count() - 1; + } else if (perItemParam.function == Function::AxisZ) { + axisIdx[2] = currentMixerGroup->channelConfigs()->count() - 1; + } + if (!perItemParam.identifier.isEmpty()) { for (const auto& rule : _rules) { if (rule.selectIdentifier == perItemParam.identifier) { @@ -292,6 +481,18 @@ void Mixers::update() } } + // Add virtual axis dropdown configuration param if all 3 axes are found + if (axisIdx[0] >= 0 && axisIdx[1] >= 0 && axisIdx[2] >= 0) { + ChannelConfig* axisXConfig = currentMixerGroup->channelConfigs()->value(axisIdx[0]); + MixerParameter parameter = axisXConfig->config(); // use axisX as base (somewhat arbitrary) + parameter.function = Function::Unspecified; + parameter.param.name = ""; + parameter.param.label = tr("Axis"); + parameter.identifier = ""; + ChannelConfig* virtualChannelConfig = new ChannelConfigVirtualAxis(currentMixerGroup, parameter); + currentMixerGroup->channelConfigs()->insert(axisIdx[0], virtualChannelConfig); + } + // 'count' param if (actuatorGroup.count != "") { currentMixerGroup->setCountParam(new ConfigParameter(currentMixerGroup, getFact(actuatorGroup.count), diff --git a/src/Vehicle/Actuators/Mixer.h b/src/Vehicle/Actuators/Mixer.h index 8517652..955b519 100644 --- a/src/Vehicle/Actuators/Mixer.h +++ b/src/Vehicle/Actuators/Mixer.h @@ -128,6 +128,8 @@ private: const bool _advanced; }; +class ChannelConfigInstance; + /** * Config parameters that apply to individual channels */ @@ -140,16 +142,15 @@ public: : QObject(parent), _parameter(parameter), _isActuatorTypeConfig(isActuatorTypeConfig) {} Q_PROPERTY(QString label READ label CONSTANT) - Q_PROPERTY(bool visible READ visible CONSTANT) + Q_PROPERTY(bool visible READ visible NOTIFY visibleChanged) Q_PROPERTY(bool advanced READ advanced CONSTANT) const QString& label() const { return _parameter.param.label; } - const QString& parameter() const { return _parameter.param.name; } Function function() const { return _parameter.function; } bool advanced() const { return _parameter.param.advanced; } bool isActuatorTypeConfig() const { return _isActuatorTypeConfig; } - bool visible() const { return true; } + bool visible() const { return _visible; } const QList& fixedValues() const { return _parameter.values; } @@ -157,9 +158,37 @@ public: int indexOffset() const { return _parameter.param.indexOffset; } const QString& identifier() const { return _parameter.identifier; } + + const MixerParameter& config() const { return _parameter; } + + virtual ChannelConfigInstance* instantiate(int paramIndex, int actuatorTypeIndex, + ParameterManager* parameterManager, std::function factAddedCb); + +signals: + void visibleChanged(); +protected: + void channelInstanceCreated(ChannelConfigInstance* instance); + +private slots: + void instanceVisibleChanged(); + private: const MixerParameter _parameter; const bool _isActuatorTypeConfig; ///< actuator type config instead of mixer channel config + bool _visible{true}; ///< this is false if none of the instances is visible + QList _instances; +}; + +class ChannelConfigVirtualAxis : public ChannelConfig +{ + Q_OBJECT +public: + ChannelConfigVirtualAxis(QObject* parent, const MixerParameter& parameter) + : ChannelConfig(parent, parameter) {} + + ChannelConfigInstance* instantiate(int paramIndex, int actuatorTypeIndex, + ParameterManager* parameterManager, std::function factAddedCb) override; +private: }; /** @@ -169,8 +198,8 @@ class ChannelConfigInstance : public QObject { Q_OBJECT public: - ChannelConfigInstance(QObject* parent, Fact* fact, ChannelConfig& config, int ruleApplyIdentifierIdx) - : QObject(parent), _fact(fact), _config(config), _ruleApplyIdentifierIdx(ruleApplyIdentifierIdx) {} + ChannelConfigInstance(QObject* parent, Fact* fact, ChannelConfig& config) + : QObject(parent), _fact(fact), _config(config) {} Q_PROPERTY(ChannelConfig* config READ channelConfig CONSTANT) Q_PROPERTY(Fact* fact READ fact CONSTANT) @@ -181,26 +210,66 @@ public: Fact* fact() { return _fact; } - bool visible() const { return _visible; } - bool enabled() const { return _enabled; } + bool visible() const { return _visibleRule && _visibleAxis; } + bool enabled() const { return _enabledRule; } - // controlled via rules - void setVisible(bool visible) { _visible = visible; emit visibleChanged(); } - void setEnabled(bool enabled) { _enabled = enabled; emit enabledChanged(); } + bool visibleRule() const { return _visibleRule; } + bool enabledRule() const { return _enabledRule; } + + void setVisibleRule(bool visible) { _visibleRule = visible; emit visibleChanged(); } + void setEnabledRule(bool enabled) { _enabledRule = enabled; emit enabledChanged(); } + + void setVisibleAxis(bool visible) { _visibleAxis = visible; emit visibleChanged(); } int ruleApplyIdentifierIdx() const { return _ruleApplyIdentifierIdx; } + void setRuleApplyIdentifierIdx(int idx) { _ruleApplyIdentifierIdx = idx; } + + virtual void allInstancesInitialized(QmlObjectListModel* configInstances) {} signals: void visibleChanged(); void enabledChanged(); -private: +protected: Fact* _fact{nullptr}; + +private: ChannelConfig& _config; - const int _ruleApplyIdentifierIdx; + int _ruleApplyIdentifierIdx{-1}; + + bool _visibleRule{true}; + bool _enabledRule{true}; + + bool _visibleAxis{true}; +}; - bool _visible{true}; - bool _enabled{true}; +class ChannelConfigInstanceVirtualAxis : public ChannelConfigInstance +{ + Q_OBJECT +public: + enum class Direction { + Custom = 0, + Upwards = 1, + Downwards = 2, + Forwards = 3, + Backwards = 4, + Leftwards = 5, + Rightwards = 6, + }; + ChannelConfigInstanceVirtualAxis(QObject* parent, ChannelConfig& config) + : ChannelConfigInstance(parent, nullptr, config) {} + + void allInstancesInitialized(QmlObjectListModel* configInstances) override; + +private slots: + void setFactFromAxes(bool keepVisible = false); + void setAxesFromFact(); + void axisVisibleChanged(); + void axisEnableChanged(); + +private: + ChannelConfigInstance* _axes[3]{}; + bool _ignoreChange{false}; }; class MixerChannel : public QObject