diff --git a/include/ignition/gazebo/components/VisualCmd.hh b/include/ignition/gazebo/components/VisualCmd.hh new file mode 100644 index 0000000000..794057aabb --- /dev/null +++ b/include/ignition/gazebo/components/VisualCmd.hh @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2021 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#ifndef IGNITION_GAZEBO_COMPONENTS_VISUALCMD_HH_ +#define IGNITION_GAZEBO_COMPONENTS_VISUALCMD_HH_ + +#include +#include +#include +#include +#include + +#include + +#include + +namespace ignition +{ +namespace gazebo +{ +// Inline bracket to help doxygen filtering. +inline namespace IGNITION_GAZEBO_VERSION_NAMESPACE { +namespace components +{ + /// \brief A component type that contains commanded visual of an + /// entity in the world frame represented by msgs::Visual. + using VisualCmd = Component; + IGN_GAZEBO_REGISTER_COMPONENT("ign_gazebo_components.VisualCmd", + VisualCmd) +} +} +} +} +#endif diff --git a/src/gui/plugins/component_inspector/ComponentInspector.cc b/src/gui/plugins/component_inspector/ComponentInspector.cc index 471b9c51ce..97fdef2438 100644 --- a/src/gui/plugins/component_inspector/ComponentInspector.cc +++ b/src/gui/plugins/component_inspector/ComponentInspector.cc @@ -17,6 +17,7 @@ #include #include +#include #include #include #include @@ -41,6 +42,7 @@ #include "ignition/gazebo/components/LinearVelocitySeed.hh" #include "ignition/gazebo/components/Link.hh" #include "ignition/gazebo/components/MagneticField.hh" +#include "ignition/gazebo/components/Material.hh" #include "ignition/gazebo/components/Model.hh" #include "ignition/gazebo/components/Name.hh" #include "ignition/gazebo/components/ParentEntity.hh" @@ -259,6 +261,39 @@ void ignition::gazebo::setData(QStandardItem *_item, const sdf::Physics &_data) }), ComponentsModel::RoleNames().key("data")); } +////////////////////////////////////////////////// +template<> +void ignition::gazebo::setData(QStandardItem *_item, + const sdf::Material &_data) +{ + if (nullptr == _item) + return; + + _item->setData(QString("Material"), + ComponentsModel::RoleNames().key("dataType")); + _item->setData(QList({ + QVariant(_data.Ambient().R() * 255), + QVariant(_data.Ambient().G() * 255), + QVariant(_data.Ambient().B() * 255), + QVariant(_data.Ambient().A() * 255), + QVariant(_data.Diffuse().R() * 255), + QVariant(_data.Diffuse().G() * 255), + QVariant(_data.Diffuse().B() * 255), + QVariant(_data.Diffuse().A() * 255), + QVariant(_data.Specular().R() * 255), + QVariant(_data.Specular().G() * 255), + QVariant(_data.Specular().B() * 255), + QVariant(_data.Specular().A() * 255), + QVariant(_data.Emissive().R() * 255), + QVariant(_data.Emissive().G() * 255), + QVariant(_data.Emissive().B() * 255), + QVariant(_data.Emissive().A() * 255) + }), ComponentsModel::RoleNames().key("data")); + + // TODO(anyone) Only shows colors of material, + // need to add others (e.g., pbr) +} + ////////////////////////////////////////////////// void ignition::gazebo::setUnit(QStandardItem *_item, const std::string &_unit) { @@ -697,6 +732,15 @@ void ComponentInspector::Update(const UpdateInfo &, if (comp) setData(item, comp->Data()); } + else if (typeId == components::Material::typeId) + { + auto comp = _ecm.Component(this->dataPtr->entity); + if (comp) + { + this->SetType("material"); + setData(item, comp->Data()); + } + } } // Remove components no longer present @@ -906,6 +950,96 @@ void ComponentInspector::OnPhysics(double _stepSize, double _realTimeFactor) this->dataPtr->node.Request(physicsCmdService, req, cb); } +///////////////////////////////////////////////// +void ComponentInspector::OnMaterialColor( + double _rAmbient, double _gAmbient, double _bAmbient, double _aAmbient, + double _rDiffuse, double _gDiffuse, double _bDiffuse, double _aDiffuse, + double _rSpecular, double _gSpecular, double _bSpecular, double _aSpecular, + double _rEmissive, double _gEmissive, double _bEmissive, double _aEmissive, + QString _type, QColor _currColor) +{ + // when type is not empty, open qt color dialog + std::string type = _type.toStdString(); + if (!type.empty()) + { + QColor newColor = QColorDialog::getColor( + _currColor, nullptr, "Pick a color", + {QColorDialog::DontUseNativeDialog, QColorDialog::ShowAlphaChannel}); + + // returns if the user hits cancel + if (!newColor.isValid()) + return; + + if (type == "ambient") + { + _rAmbient = newColor.red(); + _gAmbient = newColor.green(); + _bAmbient = newColor.blue(); + _aAmbient = newColor.alpha(); + } + else if (type == "diffuse") + { + _rDiffuse = newColor.red(); + _gDiffuse = newColor.green(); + _bDiffuse = newColor.blue(); + _aDiffuse = newColor.alpha(); + } + else if (type == "specular") + { + _rSpecular = newColor.red(); + _gSpecular = newColor.green(); + _bSpecular = newColor.blue(); + _aSpecular = newColor.alpha(); + } + else if (type == "emissive") + { + _rEmissive = newColor.red(); + _gEmissive = newColor.green(); + _bEmissive = newColor.blue(); + _aEmissive = newColor.alpha(); + } + else + { + ignerr << "Invalid material type: " << type << std::endl; + return; + } + } + + std::function cb = + [](const ignition::msgs::Boolean &/*_rep*/, const bool _result) + { + if (!_result) + ignerr << "Error setting material color configuration" + << " on visual" << std::endl; + }; + + msgs::Visual req; + req.set_id(this->dataPtr->entity); + + msgs::Set(req.mutable_material()->mutable_ambient(), + math::Color(_rAmbient / 255.0, _gAmbient / 255.0, + _bAmbient / 255.0, _aAmbient / 255.0)); + msgs::Set(req.mutable_material()->mutable_diffuse(), + math::Color(_rDiffuse / 255.0, _gDiffuse / 255.0, + _bDiffuse / 255.0, _aDiffuse / 255.0)); + msgs::Set(req.mutable_material()->mutable_specular(), + math::Color(_rSpecular / 255.0, _gSpecular / 255.0, + _bSpecular / 255.0, _aSpecular / 255.0)); + msgs::Set(req.mutable_material()->mutable_emissive(), + math::Color(_rEmissive / 255.0, _gEmissive / 255.0, + _bEmissive / 255.0, _aEmissive / 255.0)); + + auto materialCmdService = "/world/" + this->dataPtr->worldName + + "/visual_config"; + materialCmdService = transport::TopicUtils::AsValidTopic(materialCmdService); + if (materialCmdService.empty()) + { + ignerr << "Invalid material command service topic provided" << std::endl; + return; + } + this->dataPtr->node.Request(materialCmdService, req, cb); +} + ///////////////////////////////////////////////// bool ComponentInspector::NestedModel() const { diff --git a/src/gui/plugins/component_inspector/ComponentInspector.hh b/src/gui/plugins/component_inspector/ComponentInspector.hh index cd6cc470b2..e518f0e44a 100644 --- a/src/gui/plugins/component_inspector/ComponentInspector.hh +++ b/src/gui/plugins/component_inspector/ComponentInspector.hh @@ -115,6 +115,13 @@ namespace gazebo template<> void setData(QStandardItem *_item, const std::ostream &_data); + /// \brief Specialized to set material data. + /// \param[in] _item Item whose data will be set. + /// \param[in] _data Data to set. + template<> + void setData(QStandardItem *_item, const sdf::Material &_data); + + /// \brief Set the unit of a given item. /// \param[in] _item Item whose unit will be set. /// \param[in] _unit Unit to be displayed, such as 'm' for meters. @@ -259,6 +266,35 @@ namespace gazebo public: Q_INVOKABLE void OnPhysics(double _stepSize, double _realTimeFactor); + // \brief Callback in Qt thread when material color changes for a visual + /// \param[in] _rAmbient ambient red + /// \param[in] _gAmbient ambient green + /// \param[in] _bAmbient ambient blue + /// \param[in] _aAmbient ambient alpha + /// \param[in] _rDiffuse diffuse red + /// \param[in] _gDiffuse diffuse green + /// \param[in] _bDiffuse diffuse blue + /// \param[in] _aDiffuse diffuse alpha + /// \param[in] _rSpecular specular red + /// \param[in] _gSpecular specular green + /// \param[in] _bSpecular specular blue + /// \param[in] _aSpecular specular alpha + /// \param[in] _rEmissive emissive red + /// \param[in] _gEmissive emissive green + /// \param[in] _bEmissive emissive blue + /// \param[in] _aEmissive emissive alpha + /// \param[in] _type if type is not empty, opens QColorDialog. + /// The possible types are ambient, diffuse, specular, or emissive. + /// \param[in] _currColor used for QColorDialog to show the current color + /// in the open dialog. + public: Q_INVOKABLE void OnMaterialColor( + double _rAmbient, double _gAmbient, double _bAmbient, + double _aAmbient, double _rDiffuse, double _gDiffuse, + double _bDiffuse, double _aDiffuse, double _rSpecular, + double _gSpecular, double _bSpecular, double _aSpecular, + double _rEmissive, double _gEmissive, double _bEmissive, + double _aEmissive, QString _type, QColor _currColor); + /// \brief Get whether the entity is a nested model or not /// \return True if the entity is a nested model, false otherwise public: Q_INVOKABLE bool NestedModel() const; diff --git a/src/gui/plugins/component_inspector/ComponentInspector.qml b/src/gui/plugins/component_inspector/ComponentInspector.qml index 9063a992d9..997e6dcd19 100644 --- a/src/gui/plugins/component_inspector/ComponentInspector.qml +++ b/src/gui/plugins/component_inspector/ComponentInspector.qml @@ -116,6 +116,22 @@ Rectangle { ComponentInspector.OnPhysics(_stepSize, _realTimeFactor) } + /** + * Forward material color changes to C++ + */ + function onMaterialColor(_rAmbient, _gAmbient, _bAmbient, _aAmbient, + _rDiffuse, _gDiffuse, _bDiffuse, _aDiffuse, + _rSpecular, _gSpecular, _bSpecular, _aSpecular, + _rEmissive, _gEmissive, _bEmissive, _aEmissive, + _type, _currColor) { + ComponentInspector.OnMaterialColor( + _rAmbient, _gAmbient, _bAmbient, _aAmbient, + _rDiffuse, _gDiffuse, _bDiffuse, _aDiffuse, + _rSpecular, _gSpecular, _bSpecular, _aSpecular, + _rEmissive, _gEmissive, _bEmissive, _aEmissive, + _type, _currColor) + } + Rectangle { id: header height: lockButton.height diff --git a/src/gui/plugins/component_inspector/ComponentInspector.qrc b/src/gui/plugins/component_inspector/ComponentInspector.qrc index 1996720cbc..e993f9f5ba 100644 --- a/src/gui/plugins/component_inspector/ComponentInspector.qrc +++ b/src/gui/plugins/component_inspector/ComponentInspector.qrc @@ -4,6 +4,7 @@ ComponentInspector.qml Light.qml NoData.qml + Material.qml Physics.qml Pose3d.qml String.qml diff --git a/src/gui/plugins/component_inspector/Material.qml b/src/gui/plugins/component_inspector/Material.qml new file mode 100644 index 0000000000..a7d82cc6c8 --- /dev/null +++ b/src/gui/plugins/component_inspector/Material.qml @@ -0,0 +1,606 @@ +/* + * Copyright (C) 2021 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ +import QtQuick 2.9 +import QtQuick.Controls 1.4 +import QtQuick.Controls 2.2 +import QtQuick.Controls.Material 2.1 +import QtQuick.Dialogs 1.0 +import QtQuick.Layouts 1.3 +import QtQuick.Controls.Styles 1.4 +import "qrc:/ComponentInspector" +import "qrc:/qml" + +// Item displaying material color information +Rectangle { + height: header.height + content.height + width: componentInspector.width + color: index % 2 == 0 ? lightGrey : darkGrey + + // Left indentation + property int indentation: 10 + + // Horizontal margins + property int margin: 5 + + property int iconWidth: 20 + property int iconHeight: 20 + + // Loaded items for ambient red, green, blue, alpha + property var rAmbientItem: {} + property var gAmbientItem: {} + property var bAmbientItem: {} + property var aAmbientItem: {} + + // Loaded items for diffuse red, green, blue, alpha + property var rDiffuseItem: {} + property var gDiffuseItem: {} + property var bDiffuseItem: {} + property var aDiffuseItem: {} + + // Loaded items for specular red, green, blue, alpha + property var rSpecularItem: {} + property var gSpecularItem: {} + property var bSpecularItem: {} + property var aSpecularItem: {} + + // Loaded items for emissive red, green, blue, alpha + property var rEmissiveItem: {} + property var gEmissiveItem: {} + property var bEmissiveItem: {} + property var aEmissiveItem: {} + + // send new material color data to C++ + function sendMaterialColor(_type, _currColor) { + componentInspector.onMaterialColor( + rAmbientItem.value, + gAmbientItem.value, + bAmbientItem.value, + aAmbientItem.value, + rDiffuseItem.value, + gDiffuseItem.value, + bDiffuseItem.value, + aDiffuseItem.value, + rSpecularItem.value, + gSpecularItem.value, + bSpecularItem.value, + aSpecularItem.value, + rEmissiveItem.value, + gEmissiveItem.value, + bEmissiveItem.value, + aEmissiveItem.value, + _type, + _currColor + ); + } + + // Get button color from model.data, related start indices + // 0 = ambient + // 4 = diffuse + // 8 = specular + // 12 = emissive + function getButtonColor(_start_index, _isFillColor) + { + if (_isFillColor) { + // fill color (of object in the scene) + return Qt.rgba(model.data[_start_index], + model.data[_start_index + 1], + model.data[_start_index + 2], + model.data[_start_index + 3]) + } else { + // border color's alpha set to 1 incase fill color is 0 + return Qt.rgba(model.data[_start_index], + model.data[_start_index + 1], + model.data[_start_index + 2], + 1.0) + } + } + + FontMetrics { + id: fontMetrics + font.family: "Roboto" + } + + // Used to create rgba spin boxes + Component { + id: spinBoxMaterialColor + IgnSpinBox { + id: writableSpin + value: writableSpin.activeFocus ? writableSpin.value : numberValue + minimumValue: 0 + maximumValue: 255 + decimals: 0 + onEditingFinished: { + // sending empty params to not open color dialog + sendMaterialColor("", Qt.rgba(0, 0, 0, 0)) + } + } + } + + Column { + anchors.fill: parent + + // Header + Rectangle { + id: header + width: parent.width + height: typeHeader.height + color: "transparent" + + RowLayout { + anchors.fill: parent + Item { + width: margin + } + Image { + id: icon + sourceSize.height: indentation + sourceSize.width: indentation + fillMode: Image.Pad + Layout.alignment : Qt.AlignVCenter + source: content.show ? + "qrc:/Gazebo/images/minus.png" : "qrc:/Gazebo/images/plus.png" + } + TypeHeader { + id: typeHeader + } + Item { + Layout.fillWidth: true + } + } + MouseArea { + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + content.show = !content.show + } + onEntered: { + header.color = highlightColor + } + onExited: { + header.color = "transparent" + } + } + } + + // Content + Rectangle { + id: content + property bool show: false + width: parent.width + height: show ? grid.height : 0 + clip: true + color: "transparent" + + Behavior on height { + NumberAnimation { + duration: 200 + easing.type: Easing.InOutQuad + } + } + + ColumnLayout { + id: grid + width: parent.width + spacing: 20 + + GridLayout { + width: parent.width + columns: 6 + + // rgba headers + Text { + text: "Red " + color: Material.theme == Material.Light ? "#444444" : "#bbbbbb" + font.pointSize: 12 + Layout.row: 1 + Layout.column: 3 + } + Text { + text: "Green" + color: Material.theme == Material.Light ? "#444444" : "#bbbbbb" + font.pointSize: 12 + Layout.row: 1 + Layout.column: 4 + } + Text { + text: "Blue " + color: Material.theme == Material.Light ? "#444444" : "#bbbbbb" + font.pointSize: 12 + Layout.row: 1 + Layout.column: 5 + } + Text { + text: "Alpha" + color: Material.theme == Material.Light ? "#444444" : "#bbbbbb" + font.pointSize: 12 + Layout.row: 1 + Layout.column: 6 + } + + // Ambient + Text { + text: " Ambient" + color: Material.theme == Material.Light ? "#444444" : "#bbbbbb" + Layout.row: 2 + Layout.column: 1 + } + // Ambient color dialog + Button { + id: ambientButton + Layout.row: 2 + Layout.column: 2 + ToolTip.text: "Open color dialog" + ToolTip.visible: hovered + ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval + background: Rectangle { + implicitWidth: 40 + implicitHeight: 40 + radius: 5 + border.color: getButtonColor(0, false) + border.width: 2 + color: getButtonColor(0, true) + } + onClicked: { + sendMaterialColor("ambient", getButtonColor(0, true)) + } + } + // Ambient red + Item { + Layout.row: 2 + Layout.column: 3 + Layout.fillWidth: true + height: 40 + Loader { + id: rAmbientLoader + anchors.fill: parent + property double numberValue: model.data[0] + sourceComponent: spinBoxMaterialColor + onLoaded: { + rAmbientItem = rAmbientLoader.item + } + } + } + // Ambient green + Item { + Layout.row: 2 + Layout.column: 4 + Layout.fillWidth: true + height: 40 + Loader { + id: gAmbientLoader + anchors.fill: parent + property double numberValue: model.data[1] + sourceComponent: spinBoxMaterialColor + onLoaded: { + gAmbientItem = gAmbientLoader.item + } + } + } + // Ambient blue + Item { + Layout.row: 2 + Layout.column: 5 + Layout.fillWidth: true + height: 40 + Loader { + id: bAmbientLoader + anchors.fill: parent + property double numberValue: model.data[2] + sourceComponent: spinBoxMaterialColor + onLoaded: { + bAmbientItem = bAmbientLoader.item + } + } + } + // Ambient alpha + Item { + Layout.row: 2 + Layout.column: 6 + Layout.fillWidth: true + height: 40 + Loader { + id: aAmbientLoader + anchors.fill: parent + property double numberValue: model.data[3] + sourceComponent: spinBoxMaterialColor + onLoaded: { + aAmbientItem = aAmbientLoader.item + } + } + } // end Ambient + + // Diffuse + Text { + text: " Diffuse" + color: Material.theme == Material.Light ? "#444444" : "#bbbbbb" + Layout.row: 3 + Layout.column: 1 + } + // Diffuse color dialog + Button { + id: diffuseButton + Layout.row: 3 + Layout.column: 2 + ToolTip.text: "Open color dialog" + ToolTip.visible: hovered + ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval + background: Rectangle { + implicitWidth: 40 + implicitHeight: 40 + radius: 5 + border.color: getButtonColor(4, false) + border.width: 2 + color: getButtonColor(4, true) + } + onClicked: { + sendMaterialColor("diffuse", getButtonColor(4, true)) + } + } + // Diffuse red + Item { + Layout.row: 3 + Layout.column: 3 + Layout.fillWidth: true + height: 40 + Loader { + id: rDiffuseLoader + anchors.fill: parent + property double numberValue: model.data[4] + sourceComponent: spinBoxMaterialColor + onLoaded: { + rDiffuseItem = rDiffuseLoader.item + } + } + } + // Diffuse green + Item { + Layout.row: 3 + Layout.column: 4 + Layout.fillWidth: true + height: 40 + Loader { + id: gDiffuseLoader + anchors.fill: parent + property double numberValue: model.data[5] + sourceComponent: spinBoxMaterialColor + onLoaded: { + gDiffuseItem = gDiffuseLoader.item + } + } + } + // Diffuse blue + Item { + Layout.row: 3 + Layout.column: 5 + Layout.fillWidth: true + height: 40 + Loader { + id: bDiffuseLoader + anchors.fill: parent + property double numberValue: model.data[6] + sourceComponent: spinBoxMaterialColor + onLoaded: { + bDiffuseItem = bDiffuseLoader.item + } + } + } + // Diffuse alpha + Item { + Layout.row: 3 + Layout.column: 6 + Layout.fillWidth: true + height: 40 + Loader { + id: aDiffuseLoader + anchors.fill: parent + property double numberValue: model.data[7] + sourceComponent: spinBoxMaterialColor + onLoaded: { + aDiffuseItem = aDiffuseLoader.item + } + } + } // end Diffuse + + // Specular + Text { + text: " Specular" + color: Material.theme == Material.Light ? "#444444" : "#bbbbbb" + Layout.row: 4 + Layout.column: 1 + } + // Specular color dialog + Button { + id: specularButton + Layout.row: 4 + Layout.column: 2 + ToolTip.text: "Open color dialog" + ToolTip.visible: hovered + ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval + background: Rectangle { + implicitWidth: 40 + implicitHeight: 40 + radius: 5 + border.color: getButtonColor(8, false) + border.width: 2 + color: getButtonColor(8, true) + } + onClicked: { + sendMaterialColor("specular", getButtonColor(8, true)) + } + } + // Specular red + Item { + Layout.row: 4 + Layout.column: 3 + Layout.fillWidth: true + height: 40 + Loader { + id: rSpecularLoader + anchors.fill: parent + property double numberValue: model.data[8] + sourceComponent: spinBoxMaterialColor + onLoaded: { + rSpecularItem = rSpecularLoader.item + } + } + } + // Specular green + Item { + Layout.row: 4 + Layout.column: 4 + Layout.fillWidth: true + height: 40 + Loader { + id: gSpecularLoader + anchors.fill: parent + property double numberValue: model.data[9] + sourceComponent: spinBoxMaterialColor + onLoaded: { + gSpecularItem = gSpecularLoader.item + } + } + } + // Specular blue + Item { + Layout.row: 4 + Layout.column: 5 + Layout.fillWidth: true + height: 40 + Loader { + id: bSpecularLoader + anchors.fill: parent + property double numberValue: model.data[10] + sourceComponent: spinBoxMaterialColor + onLoaded: { + bSpecularItem = bSpecularLoader.item + } + } + } + // Specular alpha + Item { + Layout.row: 4 + Layout.column: 6 + Layout.fillWidth: true + height: 40 + Loader { + id: aSpecularLoader + anchors.fill: parent + property double numberValue: model.data[11] + sourceComponent: spinBoxMaterialColor + onLoaded: { + aSpecularItem = aSpecularLoader.item + } + } + } // end Specular + + // Emissive + Text { + text: " Emissive" + color: Material.theme == Material.Light ? "#444444" : "#bbbbbb" + Layout.row: 5 + Layout.column: 1 + } + // Emissive color dialog + Button { + id: emissiveButton + Layout.row: 5 + Layout.column: 2 + ToolTip.text: "Open color dialog" + ToolTip.visible: hovered + ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval + background: Rectangle { + implicitWidth: 40 + implicitHeight: 40 + radius: 5 + border.color: getButtonColor(12, false) + border.width: 2 + color: getButtonColor(12, true) + } + onClicked: { + sendMaterialColor("emissive", getButtonColor(12, true)) + } + } + // Emissive red + Item { + Layout.row: 5 + Layout.column: 3 + Layout.fillWidth: true + height: 40 + Loader { + id: rEmissiveLoader + anchors.fill: parent + property double numberValue: model.data[12] + sourceComponent: spinBoxMaterialColor + onLoaded: { + rEmissiveItem = rEmissiveLoader.item + } + } + } + // Emissive green + Item { + Layout.row: 5 + Layout.column: 4 + Layout.fillWidth: true + height: 40 + Loader { + id: gEmissiveLoader + anchors.fill: parent + property double numberValue: model.data[13] + sourceComponent: spinBoxMaterialColor + onLoaded: { + gEmissiveItem = gEmissiveLoader.item + } + } + } + // Emissive blue + Item { + Layout.row: 5 + Layout.column: 5 + Layout.fillWidth: true + height: 40 + Loader { + id: bEmissiveLoader + anchors.fill: parent + property double numberValue: model.data[14] + sourceComponent: spinBoxMaterialColor + onLoaded: { + bEmissiveItem = bEmissiveLoader.item + } + } + } + // Emissive alpha + Item { + Layout.row: 5 + Layout.column: 6 + Layout.fillWidth: true + height: 40 + Loader { + id: aEmissiveLoader + anchors.fill: parent + property double numberValue: model.data[15] + sourceComponent: spinBoxMaterialColor + onLoaded: { + aEmissiveItem = aEmissiveLoader.item + } + } + } // end Emissive + } // end GridLayout + } // end ColumnLayout (id: grid) + } // Rectangle (id: content) + } +} diff --git a/src/gui/plugins/shapes/Shapes.cc b/src/gui/plugins/shapes/Shapes.cc index dc00229760..d9d3c76cb5 100644 --- a/src/gui/plugins/shapes/Shapes.cc +++ b/src/gui/plugins/shapes/Shapes.cc @@ -76,6 +76,7 @@ void Shapes::OnMode(const QString &_mode) std::transform(modelSdfString.begin(), modelSdfString.end(), modelSdfString.begin(), ::tolower); + // TODO(anyone) when porting to v5 add tag to capsule & ellipsoid if (modelSdfString == "box") { modelSdfString = std::string("" @@ -107,6 +108,11 @@ void Shapes::OnMode(const QString &_mode) "1 1 1" "" "" + "" + "0.3 0.3 0.3 1" + "0.7 0.7 0.7 1" + "1 1 1 1" + "" "" "" "" @@ -143,6 +149,11 @@ void Shapes::OnMode(const QString &_mode) "0.5" "" "" + "" + "0.3 0.3 0.3 1" + "0.7 0.7 0.7 1" + "1 1 1 1" + "" "" "" "" @@ -181,6 +192,11 @@ void Shapes::OnMode(const QString &_mode) "1.0" "" "" + "" + "0.3 0.3 0.3 1" + "0.7 0.7 0.7 1" + "1 1 1 1" + "" "" "" "" diff --git a/src/rendering/RenderUtil.cc b/src/rendering/RenderUtil.cc index 7af78219a5..7b29e6bd3a 100644 --- a/src/rendering/RenderUtil.cc +++ b/src/rendering/RenderUtil.cc @@ -75,6 +75,7 @@ #include "ignition/gazebo/components/Transparency.hh" #include "ignition/gazebo/components/Visibility.hh" #include "ignition/gazebo/components/Visual.hh" +#include "ignition/gazebo/components/VisualCmd.hh" #include "ignition/gazebo/components/World.hh" #include "ignition/gazebo/EntityComponentManager.hh" @@ -195,6 +196,25 @@ class ignition::gazebo::RenderUtilPrivate /// \brief A map of entity ids and light updates. public: std::vector entityLightsCmdToDelete; + /// \brief A map of entity ids and visual updates. + public: std::map entityVisuals; + + /// \brief A vector of entity ids of VisualCmds to delete + public: std::vector entityVisualsCmdToDelete; + + /// \brief Visual material equality comparision function + /// TODO(anyone) Currently only checks for material colors equality, + /// need to extend to others (e.g., PbrMaterial) + public: std::function + materialEql { [](const sdf::Material &_a, const sdf::Material &_b) + { + return + _a.Ambient() == _b.Ambient() && + _a.Diffuse() == _b.Diffuse() && + _a.Specular() == _b.Specular() && + _a.Emissive() == _b.Emissive(); + }}; + /// \brief A map of entity ids and actor transforms. public: std::map> actorTransforms; @@ -485,6 +505,41 @@ void RenderUtil::UpdateECM(const UpdateInfo &/*_info*/, } return true; }); + + // visual commands + { + auto olderEntityVisualsCmdToDelete + = std::move(this->dataPtr->entityVisualsCmdToDelete); + this->dataPtr->entityVisualsCmdToDelete.clear(); + + // TODO(anyone) Currently only updates material colors, + // need to extend to others + _ecm.Each( + [&](const Entity &_entity, + const components::VisualCmd *_visualCmd) -> bool + { + this->dataPtr->entityVisuals[_entity] = _visualCmd->Data(); + this->dataPtr->entityVisualsCmdToDelete.push_back(_entity); + + auto materialComp = _ecm.Component(_entity); + if (materialComp) + { + msgs::Material materialMsg = _visualCmd->Data().material(); + sdf::Material sdfMaterial = convert(materialMsg); + + auto state = + materialComp->SetData(sdfMaterial, this->dataPtr->materialEql) ? + ComponentState::OneTimeChange : ComponentState::NoChange; + _ecm.SetChanged(_entity, components::Material::typeId, state); + } + return true; + }); + + for (const auto entity : olderEntityVisualsCmdToDelete) + { + _ecm.RemoveComponent(entity); + } + } } ////////////////////////////////////////////////// @@ -575,6 +630,7 @@ void RenderUtil::Update() auto removeEntities = std::move(this->dataPtr->removeEntities); auto entityPoses = std::move(this->dataPtr->entityPoses); auto entityLights = std::move(this->dataPtr->entityLights); + auto entityVisuals = std::move(this->dataPtr->entityVisuals); auto trajectoryPoses = std::move(this->dataPtr->trajectoryPoses); auto actorTransforms = std::move(this->dataPtr->actorTransforms); auto actorAnimationData = std::move(this->dataPtr->actorAnimationData); @@ -593,6 +649,7 @@ void RenderUtil::Update() this->dataPtr->removeEntities.clear(); this->dataPtr->entityPoses.clear(); this->dataPtr->entityLights.clear(); + this->dataPtr->entityVisuals.clear(); this->dataPtr->trajectoryPoses.clear(); this->dataPtr->actorTransforms.clear(); this->dataPtr->actorAnimationData.clear(); @@ -875,6 +932,66 @@ void RenderUtil::Update() } this->dataPtr->UpdateThermalCamera(thermalCameraData); + + // update visuals + // TODO(anyone) currently updates material colors of visual only, + // need to extend to other updates + { + IGN_PROFILE("RenderUtil::Update Visuals"); + for (const auto &visual : entityVisuals) + { + if (!visual.second.has_material()) + continue; + + auto node = this->dataPtr->sceneManager.NodeById(visual.first); + if (!node) + continue; + + auto vis = std::dynamic_pointer_cast(node); + if (vis) + { + msgs::Material matMsg = visual.second.material(); + + // Geometry material + for (auto g = 0u; g < vis->GeometryCount(); ++g) + { + rendering::GeometryPtr geom = vis->GeometryByIndex(g); + rendering::MaterialPtr geomMat = geom->Material(); + if (!geomMat) + continue; + + math::Color color; + if (matMsg.has_ambient()) + { + color = msgs::Convert(matMsg.ambient()); + if (geomMat->Ambient() != color) + geomMat->SetAmbient(color); + } + + if (matMsg.has_diffuse()) + { + color = msgs::Convert(matMsg.diffuse()); + if (geomMat->Diffuse() != color) + geomMat->SetDiffuse(color); + } + + if (matMsg.has_specular()) + { + color = msgs::Convert(matMsg.specular()); + if (geomMat->Specular() != color) + geomMat->SetSpecular(color); + } + + if (matMsg.has_emissive()) + { + color = msgs::Convert(matMsg.emissive()); + if (geomMat->Emissive() != color) + geomMat->SetEmissive(color); + } + } + } + } + } } ////////////////////////////////////////////////// diff --git a/src/systems/user_commands/UserCommands.cc b/src/systems/user_commands/UserCommands.cc index 549aca774f..f9db007e21 100644 --- a/src/systems/user_commands/UserCommands.cc +++ b/src/systems/user_commands/UserCommands.cc @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -56,6 +57,7 @@ #include "ignition/gazebo/components/ContactSensorData.hh" #include "ignition/gazebo/components/ContactSensor.hh" #include "ignition/gazebo/components/Sensor.hh" +#include "ignition/gazebo/components/VisualCmd.hh" using namespace ignition; using namespace gazebo; @@ -267,6 +269,64 @@ class DisableCollisionCommand : public UserCommandBase // Documentation inherited public: bool Execute() final; }; + + +/// \brief Command to modify a visual entity from simulation. +class VisualCommand : public UserCommandBase +{ + /// \brief Constructor + /// \param[in] _msg Message containing the visual parameters. + /// \param[in] _iface Pointer to user commands interface. + public: VisualCommand(msgs::Visual *_msg, + std::shared_ptr &_iface); + + // Documentation inherited + public: bool Execute() final; + + /// \brief Visual equality comparision function + /// TODO(anyone) Currently only checks for material colors equality, + /// need to extend to others + public: std::function + visualEql { [](const msgs::Visual &_a, const msgs::Visual &_b) + { + auto aMaterial = _a.material(), bMaterial = _b.material(); + return + _a.name() == _b.name() && + _a.id() == _b.id() && + math::equal( + aMaterial.ambient().r(), bMaterial.ambient().r(), 1e-6f) && + math::equal( + aMaterial.ambient().g(), bMaterial.ambient().g(), 1e-6f) && + math::equal( + aMaterial.ambient().b(), bMaterial.ambient().b(), 1e-6f) && + math::equal( + aMaterial.ambient().a(), bMaterial.ambient().a(), 1e-6f) && + math::equal( + aMaterial.diffuse().r(), bMaterial.diffuse().r(), 1e-6f) && + math::equal( + aMaterial.diffuse().g(), bMaterial.diffuse().g(), 1e-6f) && + math::equal( + aMaterial.diffuse().b(), bMaterial.diffuse().b(), 1e-6f) && + math::equal( + aMaterial.diffuse().a(), bMaterial.diffuse().a(), 1e-6f) && + math::equal( + aMaterial.specular().r(), bMaterial.specular().r(), 1e-6f) && + math::equal( + aMaterial.specular().g(), bMaterial.specular().g(), 1e-6f) && + math::equal( + aMaterial.specular().b(), bMaterial.specular().b(), 1e-6f) && + math::equal( + aMaterial.specular().a(), bMaterial.specular().a(), 1e-6f) && + math::equal( + aMaterial.emissive().r(), bMaterial.emissive().r(), 1e-6f) && + math::equal( + aMaterial.emissive().g(), bMaterial.emissive().g(), 1e-6f) && + math::equal( + aMaterial.emissive().b(), bMaterial.emissive().b(), 1e-6f) && + math::equal( + aMaterial.emissive().a(), bMaterial.emissive().a(), 1e-6f); + }}; +}; } } } @@ -277,7 +337,7 @@ class ignition::gazebo::systems::UserCommandsPrivate { /// \brief Callback for create service /// \param[in] _req Request containing entity description. - /// \param[in] _res True if message successfully received and queued. + /// \param[out] _res True if message successfully received and queued. /// It does not mean that the entity will be successfully spawned. /// \return True if successful. public: bool CreateService(const msgs::EntityFactory &_req, @@ -285,7 +345,7 @@ class ignition::gazebo::systems::UserCommandsPrivate /// \brief Callback for multiple create service /// \param[in] _req Request containing one or more entity descriptions. - /// \param[in] _res True if message successfully received and queued. + /// \param[out] _res True if message successfully received and queued. /// It does not mean that the entities will be successfully spawned. /// \return True if successful. public: bool CreateServiceMultiple( @@ -293,7 +353,7 @@ class ignition::gazebo::systems::UserCommandsPrivate /// \brief Callback for remove service /// \param[in] _req Request containing identification of entity to be removed. - /// \param[in] _res True if message successfully received and queued. + /// \param[out] _res True if message successfully received and queued. /// It does not mean that the entity will be successfully removed. /// \return True if successful. public: bool RemoveService(const msgs::Entity &_req, @@ -301,28 +361,28 @@ class ignition::gazebo::systems::UserCommandsPrivate /// \brief Callback for light service /// \param[in] _req Request containing light update of an entity. - /// \param[in] _res True if message successfully received and queued. + /// \param[out] _res True if message successfully received and queued. /// It does not mean that the light will be successfully updated. /// \return True if successful. public: bool LightService(const msgs::Light &_req, msgs::Boolean &_res); /// \brief Callback for pose service /// \param[in] _req Request containing pose update of an entity. - /// \param[in] _res True if message successfully received and queued. + /// \param[out] _res True if message successfully received and queued. /// It does not mean that the entity will be successfully moved. /// \return True if successful. public: bool PoseService(const msgs::Pose &_req, msgs::Boolean &_res); /// \brief Callback for physics service /// \param[in] _req Request containing updates to the physics parameters. - /// \param[in] _res True if message successfully received and queued. + /// \param[out] _res True if message successfully received and queued. /// It does not mean that the physics parameters will be successfully updated. /// \return True if successful. public: bool PhysicsService(const msgs::Physics &_req, msgs::Boolean &_res); /// \brief Callback for enable collision service /// \param[in] _req Request containing collision entity. - /// \param[in] _res True if message successfully received and queued. + /// \param[out] _res True if message successfully received and queued. /// It does not mean that the collision will be successfully enabled. /// \return True if successful. public: bool EnableCollisionService( @@ -330,12 +390,19 @@ class ignition::gazebo::systems::UserCommandsPrivate /// \brief Callback for disable collision service /// \param[in] _req Request containing collision entity. - /// \param[in] _res True if message successfully received and queued. + /// \param[out] _res True if message successfully received and queued. /// It does not mean that the collision will be successfully disabled. /// \return True if successful. public: bool DisableCollisionService( const msgs::Entity &_req, msgs::Boolean &_res); + /// \brief Callback for visual service + /// \param[in] _req Request containing visual updates of an entity + /// \param[out] _res True if message sucessfully received and queued. + /// It does not mean that the viusal will be successfully updated + /// \return True if successful. + public: bool VisualService(const msgs::Visual &_req, msgs::Boolean &_res); + /// \brief Queue of commands pending execution. public: std::vector> pendingCmds; @@ -474,6 +541,14 @@ void UserCommands::Configure(const Entity &_entity, ignmsg << "Disable collision service on [" << disableCollisionService << "]" << std::endl; + + // Visual service + std::string visualService + {"/world/" + worldName + "/visual_config"}; + this->dataPtr->node.Advertise(visualService, + &UserCommandsPrivate::VisualService, this->dataPtr.get()); + + ignmsg << "Material service on [" << visualService << "]" << std::endl; } ////////////////////////////////////////////////// @@ -660,6 +735,24 @@ bool UserCommandsPrivate::PhysicsService(const msgs::Physics &_req, return true; } +////////////////////////////////////////////////// +bool UserCommandsPrivate::VisualService(const msgs::Visual &_req, + msgs::Boolean &_res) +{ + // Create command and push it to queue + auto msg = _req.New(); + msg->CopyFrom(_req); + auto cmd = std::make_unique(msg, this->iface); + // Push to pending + { + std::lock_guard lock(this->pendingMutex); + this->pendingCmds.push_back(std::move(cmd)); + } + + _res.set_data(true); + return true; +} + ////////////////////////////////////////////////// UserCommandBase::UserCommandBase(google::protobuf::Message *_msg, std::shared_ptr &_iface) @@ -1228,6 +1321,47 @@ bool DisableCollisionCommand::Execute() return true; } +////////////////////////////////////////////////// +VisualCommand::VisualCommand(msgs::Visual *_msg, + std::shared_ptr &_iface) + : UserCommandBase(_msg, _iface) +{ +} + +////////////////////////////////////////////////// +bool VisualCommand::Execute() +{ + auto visualMsg = dynamic_cast(this->msg); + if (nullptr == visualMsg) + { + ignerr << "Internal error, null visual message" << std::endl; + return false; + } + + if (visualMsg->id() == kNullEntity) + { + ignerr << "Failed to find visual entity" << std::endl; + return false; + } + + Entity visualEntity = visualMsg->id(); + auto visualCmdComp = + this->iface->ecm->Component(visualEntity); + if (!visualCmdComp) + { + this->iface->ecm->CreateComponent( + visualEntity, components::VisualCmd(*visualMsg)); + } + else + { + auto state = visualCmdComp->SetData(*visualMsg, this->visualEql) ? + ComponentState::OneTimeChange : ComponentState::NoChange; + this->iface->ecm->SetChanged( + visualEntity, components::VisualCmd::typeId, state); + } + return true; +} + IGNITION_ADD_PLUGIN(UserCommands, System, UserCommands::ISystemConfigure, UserCommands::ISystemPreUpdate