diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index a3b1888a0c..d619c1c9f2 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,7 +1,7 @@ # More info: # https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners -* @chapulina +* @mjcarroll */rendering/* @iche033 examples/* @mabelzhang src/systems/physics/* @azeey diff --git a/Changelog.md b/Changelog.md index 1cfad69867..6055004f31 100644 --- a/Changelog.md +++ b/Changelog.md @@ -348,6 +348,84 @@ ## Gazebo Sim 6.x +### Gazebo Sim 6.11.0 (2022-08-17) + +1. Add support for specifying log record period + * [Pull request #1636](https://github.com/gazebosim/gz-sim/pull/1636) + +1. Common widget GzColor replacement + * [Pull request #1530](https://github.com/gazebosim/gz-sim/pull/1530) + +1. Replace plotIcon in ComponentInspector with GzPlotIcon + * [Pull request #1638](https://github.com/gazebosim/gz-sim/pull/1638) + +1. Component Inspector with common widget pose plotting + * [Pull request #1607](https://github.com/gazebosim/gz-sim/pull/1607) + +1. Change CODEOWNERS and maintainer to Michael + * [Pull request #1644](https://github.com/gazebosim/gz-sim/pull/1644) + +1. Replace pose in ViewAngle with GzPose + * [Pull request #1641](https://github.com/gazebosim/gz-sim/pull/1641) + +1. Add system to an entity through Component Inspector + * [Pull request #1549](https://github.com/gazebosim/gz-sim/pull/1549) + +1. Quick start dialog + * [Pull request #1536](https://github.com/gazebosim/gz-sim/pull/1536) + * [Pull request #1627](https://github.com/gazebosim/gz-sim/pull/1627) + +1. Quiet libSDFormat console on --verbose 0 + * [Pull request #1621](https://github.com/gazebosim/gz-sim/pull/1621) + +1. New Apply Link Wrench system + * [Pull request #1593](https://github.com/gazebosim/gz-sim/pull/1593) + +1. Add Tf publishing to AckermannSteering system + * [Pull request #1576](https://github.com/gazebosim/gz-sim/pull/1576) + +1. Fix component updates + * [Pull request #1580](https://github.com/gazebosim/gz-sim/pull/1580) + +1. Implement vector3 with common widget vector3 + * [Pull request #1569](https://github.com/gazebosim/gz-sim/pull/1569) + +1. Fix to modelphotoshoot test + * [Pull request #1570](https://github.com/gazebosim/gz-sim/pull/1570) + +1. Update log playback gui config + * [Pull request #1590](https://github.com/gazebosim/gz-sim/pull/1590) + +1. Helper function to get an entity from an entity message + * [Pull request #1595](https://github.com/gazebosim/gz-sim/pull/1595) + +1. Fix compilation of scene broadcaster test + * [Pull request #1599](https://github.com/gazebosim/gz-sim/pull/1599) + +1. Ignition -> Gazebo + * [Pull request #1596](https://github.com/gazebosim/gz-sim/pull/1596) + +1. Add Model::CanonicalLink getter + * [Pull request #1594](https://github.com/gazebosim/gz-sim/pull/1594) + +1. Implement Pose3d with common widget pose + * [Pull request #1571](https://github.com/gazebosim/gz-sim/pull/1571) + +1. Fix UNIT_Server_TEST on Windows + * [Pull request #1577](https://github.com/gazebosim/gz-sim/pull/1577) + +1. Use pytest to generate junit xml files for python tests + * [Pull request #1562](https://github.com/gazebosim/gz-sim/pull/1562) + +1. Refactor: Utilizes function to load animations + * [Pull request #1568](https://github.com/gazebosim/gz-sim/pull/1568) + +1. Utilizes function to sequence trajectories + * [Pull request #1565](https://github.com/gazebosim/gz-sim/pull/1565) + +1. Disable MacOS flakies Citadel + * [Pull request #1545](https://github.com/gazebosim/gz-sim/pull/1545) + ### Gazebo Sim 6.10.0 (2022-06-24) 1. Expose the ability to stop a server from C++ diff --git a/README.md b/README.md index 36c1796029..926ecd51a7 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Gazebo Sim : A Robotic Simulator -**Maintainer:** louise AT openrobotics DOT org +**Maintainer:** michael AT openrobotics DOT org [![GitHub open issues](https://img.shields.io/github/issues-raw/gazebosim/gz-sim.svg)](https://github.com/gazebosim/gz-sim/issues) [![GitHub open pull requests](https://img.shields.io/github/issues-pr-raw/gazebosim/gz-sim.svg)](https://github.com/gazebosim/gz-sim/pulls) diff --git a/include/gz/sim/ServerConfig.hh b/include/gz/sim/ServerConfig.hh index e002deb84a..2eaae435eb 100644 --- a/include/gz/sim/ServerConfig.hh +++ b/include/gz/sim/ServerConfig.hh @@ -306,6 +306,15 @@ namespace gz /// \param[in] _recordPath Path to place recorded states public: void SetLogRecordPath(const std::string &_recordPath); + /// \brief Get time period to record states + /// \return Time period to record states + public: std::chrono::steady_clock::duration LogRecordPeriod() const; + + /// \brief Set time period to record states + /// \param[in] _period Time period to record states + public: void SetLogRecordPeriod( + const std::chrono::steady_clock::duration &_period); + /// \brief Add a topic to record. /// \param[in] _topic Topic name, which can include wildcards. public: void AddLogRecordTopic(const std::string &_topic); diff --git a/src/EntityComponentManager.cc b/src/EntityComponentManager.cc index 36437d1390..9236144d06 100644 --- a/src/EntityComponentManager.cc +++ b/src/EntityComponentManager.cc @@ -977,6 +977,12 @@ bool EntityComponentManager::HasOneTimeComponentChanges() const return !this->dataPtr->oneTimeChangedComponents.empty(); } +///////////////////////////////////////////////// +bool EntityComponentManager::HasPeriodicComponentChanges() const +{ + return !this->dataPtr->periodicChangedComponents.empty(); +} + ///////////////////////////////////////////////// std::unordered_set EntityComponentManager::ComponentTypesWithPeriodicChanges() const diff --git a/src/EntityComponentManager_TEST.cc b/src/EntityComponentManager_TEST.cc index 7e60f90c34..e17cac0940 100644 --- a/src/EntityComponentManager_TEST.cc +++ b/src/EntityComponentManager_TEST.cc @@ -2232,6 +2232,7 @@ TEST_P(EntityComponentManagerFixture, ASSERT_NE(nullptr, c2); EXPECT_TRUE(manager.HasOneTimeComponentChanges()); + EXPECT_FALSE(manager.HasPeriodicComponentChanges()); EXPECT_EQ(0u, manager.ComponentTypesWithPeriodicChanges().size()); EXPECT_EQ(ComponentState::OneTimeChange, manager.ComponentState(e1, c1->TypeId())); @@ -2244,6 +2245,7 @@ TEST_P(EntityComponentManagerFixture, // updated manager.RunSetAllComponentsUnchanged(); EXPECT_FALSE(manager.HasOneTimeComponentChanges()); + EXPECT_FALSE(manager.HasPeriodicComponentChanges()); EXPECT_EQ(0u, manager.ComponentTypesWithPeriodicChanges().size()); EXPECT_EQ(ComponentState::NoChange, manager.ComponentState(e1, c1->TypeId())); @@ -2258,6 +2260,7 @@ TEST_P(EntityComponentManagerFixture, EXPECT_EQ(ComponentState::NoChange, manager.ComponentState(e1, c1->TypeId())); EXPECT_FALSE(manager.HasOneTimeComponentChanges()); + EXPECT_FALSE(manager.HasPeriodicComponentChanges()); EXPECT_EQ(0u, manager.ComponentTypesWithPeriodicChanges().size()); EXPECT_EQ(0, manager.ChangedState().entities_size()); @@ -2285,6 +2288,7 @@ TEST_P(EntityComponentManagerFixture, EXPECT_TRUE(manager.HasOneTimeComponentChanges()); // Expect a single component type to be marked as PeriodicChange + EXPECT_TRUE(manager.HasPeriodicComponentChanges()); ASSERT_EQ(1u, manager.ComponentTypesWithPeriodicChanges().size()); EXPECT_EQ(IntComponent().TypeId(), *manager.ComponentTypesWithPeriodicChanges().begin()); @@ -2297,6 +2301,7 @@ TEST_P(EntityComponentManagerFixture, EXPECT_TRUE(manager.RemoveComponent(e1, c1->TypeId())); EXPECT_TRUE(manager.HasOneTimeComponentChanges()); + EXPECT_FALSE(manager.HasPeriodicComponentChanges()); EXPECT_EQ(0u, manager.ComponentTypesWithPeriodicChanges().size()); EXPECT_EQ(ComponentState::NoChange, manager.ComponentState(e1, c1->TypeId())); diff --git a/src/ServerConfig.cc b/src/ServerConfig.cc index 9a726ccb4f..6c325ba684 100644 --- a/src/ServerConfig.cc +++ b/src/ServerConfig.cc @@ -251,6 +251,7 @@ class gz::sim::ServerConfigPrivate useLevels(_cfg->useLevels), useLogRecord(_cfg->useLogRecord), logRecordPath(_cfg->logRecordPath), + logRecordPeriod(_cfg->logRecordPeriod), logPlaybackPath(_cfg->logPlaybackPath), logRecordResources(_cfg->logRecordResources), logRecordCompressPath(_cfg->logRecordCompressPath), @@ -283,6 +284,9 @@ class gz::sim::ServerConfigPrivate /// \brief Path to place recorded states public: std::string logRecordPath = ""; + /// \brief Time period to record states + public: std::chrono::steady_clock::duration logRecordPeriod{0}; + /// \brief Path to recorded states to play back using logging system public: std::string logPlaybackPath = ""; @@ -480,6 +484,19 @@ void ServerConfig::SetLogRecordPath(const std::string &_recordPath) this->dataPtr->logRecordPath = _recordPath; } +///////////////////////////////////////////////// +std::chrono::steady_clock::duration ServerConfig::LogRecordPeriod() const +{ + return this->dataPtr->logRecordPeriod; +} + +///////////////////////////////////////////////// +void ServerConfig::SetLogRecordPeriod( + const std::chrono::steady_clock::duration &_period) +{ + this->dataPtr->logRecordPeriod = _period; +} + ///////////////////////////////////////////////// const std::string ServerConfig::LogPlaybackPath() const { @@ -692,6 +709,17 @@ ServerConfig::LogRecordPlugin() const plugin.InsertContent(topicElem); } + if (this->LogRecordPeriod() > std::chrono::steady_clock::duration::zero()) + { + sdf::ElementPtr periodElem = std::make_shared(); + periodElem->SetName("record_period"); + periodElem->AddValue("double", "0", false, ""); + double t = std::chrono::duration_cast( + this->LogRecordPeriod()).count() * 1e-3; + periodElem->Set(t); + plugin.InsertContent(periodElem); + } + gzdbg << plugin.ToElement()->ToString("") << std::endl; return ServerConfig::PluginInfo(entityName, diff --git a/src/ServerConfig_TEST.cc b/src/ServerConfig_TEST.cc index 29ce6b3703..2f7d250408 100644 --- a/src/ServerConfig_TEST.cc +++ b/src/ServerConfig_TEST.cc @@ -221,6 +221,11 @@ TEST(ServerConfig, GenerateRecordPlugin) config.SetUseLogRecord(true); config.SetLogRecordPath("foo/bar"); config.SetLogRecordResources(true); + auto period = + std::chrono::duration_cast( + std::chrono::duration(0.04)); + config.SetLogRecordPeriod(period); + EXPECT_EQ(period, config.LogRecordPeriod()); auto plugin = config.LogRecordPlugin(); EXPECT_EQ(plugin.EntityName(), "*"); diff --git a/src/ServerPrivate.cc b/src/ServerPrivate.cc index de51e8fbee..debfb7afce 100644 --- a/src/ServerPrivate.cc +++ b/src/ServerPrivate.cc @@ -257,6 +257,11 @@ void ServerPrivate::AddRecordPlugin(const ServerConfig &_config) this->config.SetLogRecordPath(_config.LogRecordPath()); } + if (_config.LogRecordPeriod() > std::chrono::steady_clock::duration::zero()) + { + this->config.SetLogRecordPeriod(_config.LogRecordPeriod()); + } + if (_config.LogRecordResources()) this->config.SetLogRecordResources(true); diff --git a/src/cmd/cmdsim.rb.in b/src/cmd/cmdsim.rb.in index 137767a759..b93523945f 100755 --- a/src/cmd/cmdsim.rb.in +++ b/src/cmd/cmdsim.rb.in @@ -98,6 +98,9 @@ COMMANDS = { 'sim' => " --record-topic /stats \ \n"\ " --record-topic /clock \n"\ "\n"\ + " --record-period [arg] Specify the time period (seconds) between \n"\ + " state recording. \n"\ + "\n"\ " --log-overwrite When recording, overwrite existing files. \n"\ " Only valid if recording is enabled. \n"\ "\n"\ @@ -213,6 +216,7 @@ class Cmd 'record-path' => '', 'record-resources' => 0, 'record-topics' => [], + 'record-period' => -1, 'log-overwrite' => 0, 'log-compress' => 0, 'playback' => '', @@ -283,6 +287,9 @@ class Cmd opts.on('--record-topic [arg]', String) do |t| options['record-topics'].append(t) end + opts.on('--record-period [arg]', Float) do |d| + options['record-period'] = d + end opts.on('--log-overwrite') do options['log-overwrite'] = 1 end @@ -455,7 +462,7 @@ Please use [GZ_SIM_RESOURCE_PATH] instead." const char *, int, int, const char *, int, int, int, const char *, const char *, const char *, const char *, const char *, - const char *, int, int)' + const char *, int, int, float)' # Import the runGui function Importer.extern 'int runGui(const char *, const char *, int, const char *)' @@ -491,7 +498,7 @@ See https://github.com/gazebosim/gz-sim/issues/44 for more info." options['render_engine_server'], options['render_engine_gui'], options['file'], options['record-topics'].join(':'), options['wait_gui'], - options['headless-rendering']) + options['headless-rendering'], options['record-period']) end guiPid = Process.fork do @@ -528,7 +535,7 @@ See https://github.com/gazebosim/gz-sim/issues/44 for more info." options['playback'], options['physics_engine'], options['render_engine_server'], options['render_engine_gui'], options['file'], options['record-topics'].join(':'), - options['wait_gui'], options['headless-rendering']) + options['wait_gui'], options['headless-rendering'], options['record-period']) # Otherwise run the gui else options['gui'] ENV['RMT_PORT'] = '1501' diff --git a/src/cmd/sim.bash_completion.sh b/src/cmd/sim.bash_completion.sh index d2d53705a3..dd41488da7 100644 --- a/src/cmd/sim.bash_completion.sh +++ b/src/cmd/sim.bash_completion.sh @@ -30,6 +30,7 @@ GZ_SIM_COMPLETION_LIST=" --record-path --record-resources --record-topic + --record-period --log-overwrite --log-compress --playback diff --git a/src/gui/plugins/component_inspector/ComponentInspector.cc b/src/gui/plugins/component_inspector/ComponentInspector.cc index 797fef4228..d2dae34430 100644 --- a/src/gui/plugins/component_inspector/ComponentInspector.cc +++ b/src/gui/plugins/component_inspector/ComponentInspector.cc @@ -291,22 +291,22 @@ void gz::sim::setData(QStandardItem *_item, _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) + QVariant(_data.Ambient().R()), + QVariant(_data.Ambient().G()), + QVariant(_data.Ambient().B()), + QVariant(_data.Ambient().A()), + QVariant(_data.Diffuse().R()), + QVariant(_data.Diffuse().G()), + QVariant(_data.Diffuse().B()), + QVariant(_data.Diffuse().A()), + QVariant(_data.Specular().R()), + QVariant(_data.Specular().G()), + QVariant(_data.Specular().B()), + QVariant(_data.Specular().A()), + QVariant(_data.Emissive().R()), + QVariant(_data.Emissive().G()), + QVariant(_data.Emissive().B()), + QVariant(_data.Emissive().A()) }), ComponentsModel::RoleNames().key("data")); // TODO(anyone) Only shows colors of material, @@ -1086,57 +1086,10 @@ void ComponentInspector::OnMaterialColor( 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) + 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 - { - gzerr << "Invalid material type: " << type << std::endl; - return; - } - } - - std::function cb = - [](const gz::msgs::Boolean &/*_rep*/, const bool _result) + std::function cb = + [](const msgs::Boolean &/*_rep*/, const bool _result) { if (!_result) gzerr << "Error setting material color configuration" @@ -1147,17 +1100,13 @@ void ComponentInspector::OnMaterialColor( 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)); + math::Color(_rAmbient, _gAmbient, _bAmbient, _aAmbient)); msgs::Set(req.mutable_material()->mutable_diffuse(), - math::Color(_rDiffuse / 255.0, _gDiffuse / 255.0, - _bDiffuse / 255.0, _aDiffuse / 255.0)); + math::Color(_rDiffuse, _gDiffuse, _bDiffuse, _aDiffuse)); msgs::Set(req.mutable_material()->mutable_specular(), - math::Color(_rSpecular / 255.0, _gSpecular / 255.0, - _bSpecular / 255.0, _aSpecular / 255.0)); + math::Color(_rSpecular, _gSpecular, _bSpecular, _aSpecular)); msgs::Set(req.mutable_material()->mutable_emissive(), - math::Color(_rEmissive / 255.0, _gEmissive / 255.0, - _bEmissive / 255.0, _aEmissive / 255.0)); + math::Color(_rEmissive, _gEmissive, _bEmissive, _aEmissive)); auto materialCmdService = "/world/" + this->dataPtr->worldName + "/visual_config"; diff --git a/src/gui/plugins/component_inspector/Light.qml b/src/gui/plugins/component_inspector/Light.qml index ea33d6596a..88d1e04dff 100644 --- a/src/gui/plugins/component_inspector/Light.qml +++ b/src/gui/plugins/component_inspector/Light.qml @@ -39,29 +39,17 @@ Rectangle { property int iconWidth: 20 property int iconHeight: 20 - // Loaded item for specular red - property var rSpecularItem: {} - - // Loaded item for specular green - property var gSpecularItem: {} - - // Loaded item for specular blue - property var bSpecularItem: {} - - // Loaded item for specular alpha - property var aSpecularItem: {} + // Loaded item for specular RGBA + property double rSpecularValue: model.data[0] + property double gSpecularValue: model.data[1] + property double bSpecularValue: model.data[2] + property double aSpecularValue: model.data[3] // Loaded item for diffuse red - property var rDiffuseItem: {} - - // Loaded item for diffuse green - property var gDiffuseItem: {} - - // Loaded item for diffuse blue - property var bDiffuseItem: {} - - // Loaded item for diffuse alpha - property var aDiffuseItem: {} + property double rDiffuseValue: model.data[4] + property double gDiffuseValue: model.data[5] + property double bDiffuseValue: model.data[6] + property double aDiffuseValue: model.data[7] // Loaded item for attenuation range property var attRangeItem: {} @@ -109,14 +97,14 @@ Rectangle { function sendLight() { // TODO(anyone) There's a loss of precision when these values get to C++ componentInspector.onLight( - rSpecularItem.value, - gSpecularItem.value, - bSpecularItem.value, - aSpecularItem.value, - rDiffuseItem.value, - gDiffuseItem.value, - bDiffuseItem.value, - aDiffuseItem.value, + rSpecularValue, + gSpecularValue, + bSpecularValue, + aSpecularValue, + rDiffuseValue, + gDiffuseValue, + bDiffuseValue, + aDiffuseValue, attRangeItem.value, attLinearItem.value, attConstantItem.value, @@ -198,31 +186,12 @@ Rectangle { } Component { - id: plotIcon - Image { - property string componentInfo: "" - source: "plottable_icon.svg" - anchors.top: parent.top - anchors.left: parent.left - - Drag.mimeData: { "text/plain" : (model === null) ? "" : - "Component," + model.entity + "," + model.typeId + "," + - model.dataType + "," + componentInfo + "," + model.shortName + id: gzPlotIcon + GzPlotIcon { + gzMimeData: { "text/plain" : (model === null) ? "" : + "Component," + model.entity + "," + model.typeId + "," + + model.dataType + "," + gzComponentInfo + "," + model.shortName } - Drag.dragType: Drag.Automatic - Drag.supportedActions : Qt.CopyAction - Drag.active: dragMouse.drag.active - // a point to drag from - Drag.hotSpot.x: 0 - Drag.hotSpot.y: y - MouseArea { - id: dragMouse - anchors.fill: parent - drag.target: (model === null) ? null : parent - onPressed: parent.grabToImage(function(result) {parent.Drag.imageSource = result.url }) - onReleased: parent.Drag.drop(); - cursorShape: Qt.DragCopyCursor - } } } @@ -240,7 +209,8 @@ Rectangle { Rectangle { id: content property bool show: false - width: parent.width + x: 10 + width: parent.width - 10 height: show ? grid.height : 0 clip: true color: "transparent" @@ -325,12 +295,9 @@ Rectangle { Layout.preferredWidth: intensityText.width + indentation*3 Loader { id: loaderIntensity - width: iconWidth - height: iconHeight - y:10 - sourceComponent: plotIcon + sourceComponent: gzPlotIcon + property string gzComponentInfo: "intensity" } - Component.onCompleted: loaderIntensity.item.componentInfo = "intensity" Text { id : intensityText @@ -356,366 +323,93 @@ Rectangle { } } } - RowLayout { - Layout.alignment : Qt.AlignLeft - Text { - text: " Specular" - color: "dimgrey" - width: margin + indentation - } - } - RowLayout { - Rectangle { - color: "transparent" - height: 40 - Layout.preferredWidth: rSpecularText.width + indentation*3 - Loader { - id: loaderSpecularR - width: iconWidth - height: iconHeight - y:10 - sourceComponent: plotIcon - } - Component.onCompleted: loaderSpecularR.item.componentInfo = "specularR" - Text { - id : rSpecularText - text: ' R' - leftPadding: 5 - color: Material.theme == Material.Light ? "#444444" : "#bbbbbb" - font.pointSize: 12 - anchors.centerIn: parent - } - } - Item { - Layout.fillWidth: true - height: 40 - Loader { - id: rSpecularLoader - anchors.fill: parent - property double numberValue: model.data[0] - sourceComponent: sliderZeroOne - onLoaded: { - rSpecularItem = rSpecularLoader.item - } - } - } - Rectangle { - color: "transparent" - height: 40 - Layout.preferredWidth: gSpecularText.width + indentation*3 - Loader { - id: loaderSpecularG - width: iconWidth - height: iconHeight - y:10 - sourceComponent: plotIcon - } - Component.onCompleted: loaderSpecularG.item.componentInfo = "specularG" - - Text { - id : gSpecularText - text: ' G' - leftPadding: 5 - color: Material.theme == Material.Light ? "#444444" : "#bbbbbb" - font.pointSize: 12 - anchors.centerIn: parent - } - } - Item { - Layout.fillWidth: true - height: 40 - Loader { - id: gSpecularLoader - anchors.fill: parent - property double numberValue: model.data[1] - sourceComponent: sliderZeroOne - onLoaded: { - gSpecularItem = gSpecularLoader.item - } - } - } - } - RowLayout { - Rectangle { - color: "transparent" - height: 40 - Layout.preferredWidth: bSpecularText.width + indentation*3 - Loader { - id: loaderSpecularB - width: iconWidth - height: iconHeight - y:10 - sourceComponent: plotIcon - } - Component.onCompleted: loaderSpecularB.item.componentInfo = "specularB" - - Text { - id : bSpecularText - text: ' B' - leftPadding: 5 - color: Material.theme == Material.Light ? "#444444" : "#bbbbbb" - font.pointSize: 12 - anchors.centerIn: parent - } - } - Item { - Layout.fillWidth: true - height: 40 - Loader { - id: bSpecularLoader - anchors.fill: parent - property double numberValue: model.data[2] - sourceComponent: sliderZeroOne - onLoaded: { - bSpecularItem = bSpecularLoader.item - } - } - } - Rectangle { - color: "transparent" - height: 40 - Layout.preferredWidth: aSpecularText.width + indentation*3 - Loader { - id: loaderSpecularA - width: iconWidth - height: iconHeight - y:10 - sourceComponent: plotIcon - } - Component.onCompleted: loaderSpecularA.item.componentInfo = "specularA" - - Text { - id : aSpecularText - text: ' A' - leftPadding: 5 - color: Material.theme == Material.Light ? "#444444" : "#bbbbbb" - font.pointSize: 12 - anchors.centerIn: parent - } - } - Item { - Layout.fillWidth: true - height: 40 - Loader { - id: aSpecularLoader - anchors.fill: parent - property double numberValue: model.data[3] - sourceComponent: sliderZeroOne - onLoaded: { - aSpecularItem = aSpecularLoader.item - } - } - } - } - RowLayout { - Layout.alignment: Qt.AlignHCenter - Button { - Layout.alignment: Qt.AlignHCenter - id: specularColor - text: qsTr("Specular Color") - onClicked: colorDialog.open() - ColorDialog { - id: colorDialog - title: "Choose a specular color" - visible: false - onAccepted: { - rSpecularLoader.item.value = colorDialog.color.r - gSpecularLoader.item.value = colorDialog.color.g - bSpecularLoader.item.value = colorDialog.color.b - aSpecularLoader.item.value = colorDialog.color.a - sendLight() - colorDialog.close() - } - onRejected: { - colorDialog.close() - } - } - } - } RowLayout { + // Color Text { Layout.columnSpan: 6 - text: " Diffuse" + text: " Color" color: "dimgrey" - width: margin + indentation + font.bold: true } } + RowLayout { + // Specular Rectangle { color: "transparent" - height: 40 - Layout.preferredWidth: rDiffuseText.width + indentation*3 - Loader { - id: loaderDiffuseR - width: iconWidth - height: iconHeight - y:10 - sourceComponent: plotIcon - } - Component.onCompleted: loaderDiffuseR.item.componentInfo = "diffuseR" - + height: 50 + Layout.preferredWidth: specularText.width + indentation*3 Text { - id : rDiffuseText - text: ' R' + id : specularText + Layout.columnSpan: 2 + text: ' Specular' leftPadding: 5 color: Material.theme == Material.Light ? "#444444" : "#bbbbbb" font.pointSize: 12 anchors.centerIn: parent } } - Item { - Layout.fillWidth: true - height: 40 - Loader { - id: rDiffuseLoader - anchors.fill: parent - property double numberValue: model.data[4] - sourceComponent: sliderZeroOne - onLoaded: { - rDiffuseItem = rDiffuseLoader.item - } - } - } - Rectangle { - color: "transparent" - height: 40 - Layout.preferredWidth: gDiffuseText.width + indentation*3 - Loader { - id: loaderDiffuseG - width: iconWidth - height: iconHeight - y:10 - sourceComponent: plotIcon - } - Component.onCompleted: loaderDiffuseG.item.componentInfo = "diffuseG" - Text { - id : gDiffuseText - text: ' G' - leftPadding: 5 - color: Material.theme == Material.Light ? "#444444" : "#bbbbbb" - font.pointSize: 12 - anchors.centerIn: parent - } - } - Item { - Layout.fillWidth: true - height: 40 - Loader { - id: gDiffuseLoader - anchors.fill: parent - property double numberValue: model.data[5] - sourceComponent: sliderZeroOne - onLoaded: { - gDiffuseItem = gDiffuseLoader.item - } + // Specular + GzColor { + id: gzColorSpecular + r: model.data[0] + g: model.data[1] + b: model.data[2] + a: model.data[3] + onGzColorSet: { + rSpecularValue = r + gSpecularValue = g + bSpecularValue = b + aSpecularValue = a + sendLight() } } - } - RowLayout { - Rectangle { - color: "transparent" - height: 40 - Layout.preferredWidth: bDiffuseText.width + indentation*3 - Loader { - id: loaderDiffuseB - width: iconWidth - height: iconHeight - y:10 - sourceComponent: plotIcon - } - Component.onCompleted: loaderDiffuseB.item.componentInfo = "diffuseB" - Text { - id : bDiffuseText - text: ' B' - leftPadding: 5 - color: Material.theme == Material.Light ? "#444444" : "#bbbbbb" - font.pointSize: 12 - anchors.centerIn: parent - } - } - Item { - Layout.fillWidth: true - height: 40 - Loader { - id: bDiffuseLoader - anchors.fill: parent - property double numberValue: model.data[6] - sourceComponent: sliderZeroOne - onLoaded: { - bDiffuseItem = bDiffuseLoader.item - } - } - } + // Diffuse Rectangle { color: "transparent" - height: 40 - Layout.preferredWidth: aDiffuseText.width + indentation*3 - Loader { - id: loaderDiffuseA - width: iconWidth - height: iconHeight - y:10 - sourceComponent: plotIcon - } - Component.onCompleted: loaderDiffuseA.item.componentInfo = "diffuseA" + height: 50 + Layout.preferredWidth: diffuseText.width + indentation*3 Text { - id : aDiffuseText - text: ' A' + id : diffuseText + Layout.columnSpan: 2 + text: ' Diffuse' leftPadding: 5 color: Material.theme == Material.Light ? "#444444" : "#bbbbbb" font.pointSize: 12 anchors.centerIn: parent } } - Item { - Layout.fillWidth: true - height: 40 - Loader { - id: aDiffuseLoader - anchors.fill: parent - property double numberValue: model.data[7] - sourceComponent: sliderZeroOne - onLoaded: { - aDiffuseItem = aDiffuseLoader.item - } - } - } - } - RowLayout { - Layout.alignment: Qt.AlignHCenter - Button { - Layout.alignment: Qt.AlignHCenter - id: diffuseColor - text: qsTr("Diffuse Color") - onClicked: colorDialogDiffuse.open() - ColorDialog { - id: colorDialogDiffuse - title: "Choose a diffuse color" - visible: false - onAccepted: { - rDiffuseLoader.item.value = colorDialogDiffuse.color.r - gDiffuseLoader.item.value = colorDialogDiffuse.color.g - bDiffuseLoader.item.value = colorDialogDiffuse.color.b - aDiffuseLoader.item.value = colorDialogDiffuse.color.a - sendLight() - colorDialogDiffuse.close() - } - onRejected: { - colorDialogDiffuse.close() - } + + // Diffuse + GzColor { + id: gzColorDiffuse + r: model.data[4] + g: model.data[5] + b: model.data[6] + a: model.data[7] + onGzColorSet: { + rDiffuseValue = r + gDiffuseValue = g + bDiffuseValue = b + aDiffuseValue = a + sendLight() } } } + RowLayout { Text { + Layout.topMargin: 10 + Layout.bottomMargin: 10 Layout.columnSpan: 6 text: " Attenuation" color: "dimgrey" width: margin + indentation + font.bold: true } } RowLayout { @@ -725,12 +419,9 @@ Rectangle { Layout.preferredWidth: attRangeText.width + indentation*3 Loader { id: loaderAttRange - width: iconWidth - height: iconHeight - y:10 - sourceComponent: plotIcon + sourceComponent: gzPlotIcon + property string gzComponentInfo: "attRange" } - Component.onCompleted: loaderAttRange.item.componentInfo = "attRange" Text { id : attRangeText @@ -762,12 +453,9 @@ Rectangle { Layout.preferredWidth: attLinearText.width + indentation*3 Loader { id: loaderAttLinear - width: iconWidth - height: iconHeight - y:10 - sourceComponent: plotIcon + sourceComponent: gzPlotIcon + property string gzComponentInfo: "attLinear" } - Component.onCompleted: loaderAttLinear.item.componentInfo = "attLinear" Text { id : attLinearText @@ -799,12 +487,9 @@ Rectangle { Layout.preferredWidth: attConstantText.width + indentation*3 Loader { id: loaderAttConstant - width: iconWidth - height: iconHeight - y:10 - sourceComponent: plotIcon + sourceComponent: gzPlotIcon + property string gzComponentInfo: "attConstant" } - Component.onCompleted: loaderAttConstant.item.componentInfo = "attConstant" Text { id : attConstantText @@ -836,12 +521,9 @@ Rectangle { Layout.preferredWidth: attQuadraticText.width + indentation*3 Loader { id: loaderAttQuadratic - width: iconWidth - height: iconHeight - y:10 - sourceComponent: plotIcon + sourceComponent: gzPlotIcon + property string gzComponentInfo: "attQuadratic" } - Component.onCompleted: loaderAttQuadratic.item.componentInfo = "attQuadratic" Text { id : attQuadraticText @@ -873,12 +555,9 @@ Rectangle { Layout.preferredWidth: castShadowsText.width + indentation*3 Loader { id: loaderCastShadows - width: iconWidth - height: iconHeight - y:10 - sourceComponent: plotIcon + sourceComponent: gzPlotIcon + property string gzComponentInfo: "castshadows" } - Component.onCompleted: loaderCastShadows.item.componentInfo = "castshadows" Text { id : castShadowsText @@ -911,6 +590,7 @@ Rectangle { text: " Direction" color: "dimgrey" width: margin + indentation + font.bold: true } } RowLayout { @@ -921,12 +601,9 @@ Rectangle { Layout.preferredWidth: xDirectionText.width + indentation*3 Loader { id: loaderDirectionX - width: iconWidth - height: iconHeight - y:10 - sourceComponent: plotIcon + sourceComponent: gzPlotIcon + property string gzComponentInfo: "directionX" } - Component.onCompleted: loaderDirectionX.item.componentInfo = "directionX" Text { visible: model.data[20] === 1 || model.data[20] === 2 @@ -962,12 +639,9 @@ Rectangle { Layout.preferredWidth: yDirectionText.width + indentation*3 Loader { id: loaderDirectionY - width: iconWidth - height: iconHeight - y:10 - sourceComponent: plotIcon + sourceComponent: gzPlotIcon + property string gzComponentInfo: "directionY" } - Component.onCompleted: loaderDirectionY.item.componentInfo = "directionY" Text { visible: model.data[20] === 1 || model.data[20] === 2 @@ -1003,12 +677,9 @@ Rectangle { Layout.preferredWidth: zDirectionText.width + indentation*3 Loader { id: loaderDirectionZ - width: iconWidth - height: iconHeight - y:10 - sourceComponent: plotIcon + sourceComponent: gzPlotIcon + property string gzComponentInfo: "directionZ" } - Component.onCompleted: loaderDirectionZ.item.componentInfo = "directionZ" Text { visible: model.data[20] === 1 || model.data[20] === 2 @@ -1043,6 +714,7 @@ Rectangle { text: " Spot features" color: "dimgrey" width: margin + indentation + font.bold: true } } RowLayout { @@ -1053,12 +725,9 @@ Rectangle { Layout.preferredWidth: innerAngleText.width + indentation*3 Loader { id: loaderInnerAngle - width: iconWidth - height: iconHeight - y:10 - sourceComponent: plotIcon + sourceComponent: gzPlotIcon + property string gzComponentInfo: "innerAngle" } - Component.onCompleted: loaderInnerAngle.item.componentInfo = "innerAngle" Text { visible: model.data[20] === 1 @@ -1094,12 +763,9 @@ Rectangle { Layout.preferredWidth: outerAngleText.width + indentation*3 Loader { id: loaderOuterAngle - width: iconWidth - height: iconHeight - y:10 - sourceComponent: plotIcon + sourceComponent: gzPlotIcon + property string gzComponentInfo: "outerAngle" } - Component.onCompleted: loaderOuterAngle.item.componentInfo = "outerAngle" Text { visible: model.data[20] === 1 @@ -1135,12 +801,9 @@ Rectangle { Layout.preferredWidth: fallOffText.width + indentation*3 Loader { id: loaderFallOff - width: iconWidth - height: iconHeight - y:10 - sourceComponent: plotIcon + sourceComponent: gzPlotIcon + property string gzComponentInfo: "falloff" } - Component.onCompleted: loaderFallOff.item.componentInfo = "falloff" Text { visible: model.data[20] === 1 diff --git a/src/gui/plugins/component_inspector/Material.qml b/src/gui/plugins/component_inspector/Material.qml index a38a5da499..5f281b0585 100644 --- a/src/gui/plugins/component_inspector/Material.qml +++ b/src/gui/plugins/component_inspector/Material.qml @@ -40,50 +40,37 @@ Rectangle { property int iconHeight: 20 // Loaded items for ambient red, green, blue, alpha - property var rAmbientItem: {} - property var gAmbientItem: {} - property var bAmbientItem: {} - property var aAmbientItem: {} + property double rAmbientItem: model.data[0] + property double gAmbientItem: model.data[1] + property double bAmbientItem: model.data[2] + property double aAmbientItem: model.data[3] // Loaded items for diffuse red, green, blue, alpha - property var rDiffuseItem: {} - property var gDiffuseItem: {} - property var bDiffuseItem: {} - property var aDiffuseItem: {} + property double rDiffuseItem: model.data[4] + property double gDiffuseItem: model.data[5] + property double bDiffuseItem: model.data[6] + property double aDiffuseItem: model.data[7] // Loaded items for specular red, green, blue, alpha - property var rSpecularItem: {} - property var gSpecularItem: {} - property var bSpecularItem: {} - property var aSpecularItem: {} + property double rSpecularItem: model.data[8] + property double gSpecularItem: model.data[9] + property double bSpecularItem: model.data[10] + property double aSpecularItem: model.data[11] // Loaded items for emissive red, green, blue, alpha - property var rEmissiveItem: {} - property var gEmissiveItem: {} - property var bEmissiveItem: {} - property var aEmissiveItem: {} + property double rEmissiveItem: model.data[12] + property double gEmissiveItem: model.data[13] + property double bEmissiveItem: model.data[14] + property double aEmissiveItem: model.data[15] // send new material color data to C++ - function sendMaterialColor(_type, _currColor) { + function sendMaterialColor() { 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 + rAmbientItem, gAmbientItem, bAmbientItem, aAmbientItem, + rDiffuseItem, gDiffuseItem, bDiffuseItem, aDiffuseItem, + rSpecularItem, gSpecularItem, bSpecularItem, aSpecularItem, + rEmissiveItem, gEmissiveItem, bEmissiveItem, aEmissiveItem, + "", Qt.rgba(0, 0, 0, 0) // dummy placeholders (no longer needed) ); } @@ -114,19 +101,9 @@ Rectangle { font.family: "Roboto" } - // Used to create rgba spin boxes Component { - id: spinBoxMaterialColor - GzSpinBox { - 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)) - } + id: gzcolor + GzColor { } } @@ -198,408 +175,257 @@ Rectangle { 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 - } + RowLayout { + // Color 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 + Layout.topMargin: 10 + Layout.columnSpan: 6 + text: " Color" + color: "dimgrey" + font.bold: true } + } + RowLayout { // 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)) + Rectangle { + color: "transparent" + height: 50 + Layout.preferredWidth: ambientText.width + indentation*3 + + Text { + id : ambientText + Layout.columnSpan: 2 + text: ' Ambient' + leftPadding: 5 + color: Material.theme == Material.Light ? "#444444" : "#bbbbbb" + font.pointSize: 12 + anchors.centerIn: parent } } - // Ambient red + + // Ambient Item { - Layout.row: 2 - Layout.column: 3 Layout.fillWidth: true + Layout.bottomMargin: 10 height: 40 Loader { - id: rAmbientLoader + id: ambientLoader anchors.fill: parent - property double numberValue: model.data[0] - sourceComponent: spinBoxMaterialColor - onLoaded: { - rAmbientItem = rAmbientLoader.item - } + sourceComponent: gzcolor } - } - // 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 - } + Binding { + target: ambientLoader.item + property: "r" + value: model.data[0] } - } - // 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 - } + Binding { + target: ambientLoader.item + property: "g" + value: model.data[1] } - } - // 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 + Binding { + target: ambientLoader.item + property: "b" + value: model.data[2] + } + Binding { + target: ambientLoader.item + property: "a" + value: model.data[3] + } + Connections { + target: ambientLoader.item + onGzColorSet: { + rAmbientItem = ambientLoader.item.r + gAmbientItem = ambientLoader.item.g + bAmbientItem = ambientLoader.item.b + aAmbientItem = ambientLoader.item.a + sendMaterialColor() } } - } // 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)) + Rectangle { + color: "transparent" + height: 50 + Layout.preferredWidth: diffuseText.width + indentation*3 + + Text { + id : diffuseText + Layout.columnSpan: 2 + text: ' Diffuse' + leftPadding: 5 + color: Material.theme == Material.Light ? "#444444" : "#bbbbbb" + font.pointSize: 12 + anchors.centerIn: parent } } - // Diffuse red + + // Diffuse Item { - Layout.row: 3 - Layout.column: 3 Layout.fillWidth: true + Layout.bottomMargin: 10 height: 40 Loader { - id: rDiffuseLoader + id: diffuseLoader anchors.fill: parent - property double numberValue: model.data[4] - sourceComponent: spinBoxMaterialColor - onLoaded: { - rDiffuseItem = rDiffuseLoader.item - } + sourceComponent: gzcolor } - } - // 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 - } + Binding { + target: diffuseLoader.item + property: "r" + value: model.data[4] } - } - // 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 - } + Binding { + target: diffuseLoader.item + property: "g" + value: model.data[5] } - } - // 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 + Binding { + target: diffuseLoader.item + property: "b" + value: model.data[6] + } + Binding { + target: diffuseLoader.item + property: "a" + value: model.data[7] + } + Connections { + target: diffuseLoader.item + onGzColorSet: { + rDiffuseItem = diffuseLoader.item.r + gDiffuseItem = diffuseLoader.item.g + bDiffuseItem = diffuseLoader.item.b + aDiffuseItem = diffuseLoader.item.a + sendMaterialColor() } } - } // end Diffuse + } + } + RowLayout { // 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)) + Rectangle { + color: "transparent" + height: 50 + Layout.preferredWidth: specularText.width + indentation*3 + + Text { + id : specularText + Layout.columnSpan: 2 + text: ' Specular' + leftPadding: 5 + color: Material.theme == Material.Light ? "#444444" : "#bbbbbb" + font.pointSize: 12 + anchors.centerIn: parent } } - // Specular red + + // Specular Item { - Layout.row: 4 - Layout.column: 3 Layout.fillWidth: true + Layout.bottomMargin: 10 height: 40 Loader { - id: rSpecularLoader + id: specularLoader anchors.fill: parent - property double numberValue: model.data[8] - sourceComponent: spinBoxMaterialColor - onLoaded: { - rSpecularItem = rSpecularLoader.item - } + sourceComponent: gzcolor } - } - // 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 - } + Binding { + target: specularLoader.item + property: "r" + value: model.data[8] } - } - // 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 - } + Binding { + target: specularLoader.item + property: "g" + value: model.data[9] } - } - // 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 + Binding { + target: specularLoader.item + property: "b" + value: model.data[10] + } + Binding { + target: specularLoader.item + property: "a" + value: model.data[11] + } + Connections { + target: specularLoader.item + onGzColorSet: { + rSpecularItem = specularLoader.item.r + gSpecularItem = specularLoader.item.g + bSpecularItem = specularLoader.item.b + aSpecularItem = specularLoader.item.a + sendMaterialColor() } } - } // 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)) + Rectangle { + color: "transparent" + height: 50 + Layout.preferredWidth: emissiveText.width + indentation*3 + + Text { + id : emissiveText + Layout.columnSpan: 2 + text: 'Emissive' + leftPadding: 5 + color: Material.theme == Material.Light ? "#444444" : "#bbbbbb" + font.pointSize: 12 + anchors.centerIn: parent } } - // Emissive red + + // Emissive Item { - Layout.row: 5 - Layout.column: 3 Layout.fillWidth: true + Layout.bottomMargin: 10 height: 40 Loader { - id: rEmissiveLoader + id: emissiveLoader anchors.fill: parent - property double numberValue: model.data[12] - sourceComponent: spinBoxMaterialColor - onLoaded: { - rEmissiveItem = rEmissiveLoader.item - } + sourceComponent: gzcolor } - } - // 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 - } + Binding { + target: emissiveLoader.item + property: "r" + value: model.data[12] } - } - // 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 - } + Binding { + target: emissiveLoader.item + property: "g" + value: model.data[13] } - } - // 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 + Binding { + target: emissiveLoader.item + property: "b" + value: model.data[14] + } + Binding { + target: emissiveLoader.item + property: "a" + value: model.data[15] + } + Connections { + target: emissiveLoader.item + onGzColorSet: { + rEmissiveItem = emissiveLoader.item.r + gEmissiveItem = emissiveLoader.item.g + bEmissiveItem = emissiveLoader.item.b + aEmissiveItem = emissiveLoader.item.a + sendMaterialColor() } } - } // end Emissive - } // end GridLayout + } + } // end RowLayout } // end ColumnLayout (id: grid) } // Rectangle (id: content) } diff --git a/src/gui/plugins/component_inspector/Physics.qml b/src/gui/plugins/component_inspector/Physics.qml index 378c78bbf8..a65caa1070 100644 --- a/src/gui/plugins/component_inspector/Physics.qml +++ b/src/gui/plugins/component_inspector/Physics.qml @@ -85,30 +85,11 @@ Rectangle { } Component { - id: plotIcon - Image { - property string componentInfo: "" - source: "plottable_icon.svg" - anchors.top: parent.top - anchors.left: parent.left - - Drag.mimeData: { "text/plain" : (model === null) ? "" : - "Component," + model.entity + "," + model.typeId + "," + - model.dataType + "," + componentInfo + "," + model.shortName - } - Drag.dragType: Drag.Automatic - Drag.supportedActions : Qt.CopyAction - Drag.active: dragMouse.drag.active - // a point to drag from - Drag.hotSpot.x: 0 - Drag.hotSpot.y: y - MouseArea { - id: dragMouse - anchors.fill: parent - drag.target: (model === null) ? null : parent - onPressed: parent.grabToImage(function(result) {parent.Drag.imageSource = result.url }) - onReleased: parent.Drag.drop(); - cursorShape: Qt.DragCopyCursor + id: gzPlotIcon + GzPlotIcon { + gzMimeData: { "text/plain" : (model === null) ? "" : + "Component," + model.entity + "," + model.typeId + "," + + model.dataType + "," + gzComponentInfo + "," + model.shortName } } } @@ -156,12 +137,9 @@ Rectangle { Layout.preferredWidth: stepSizeText.width + indentation*3 Loader { id: loaderStepSize - width: iconWidth - height: iconHeight - y:10 - sourceComponent: plotIcon + sourceComponent: gzPlotIcon + property string gzComponentInfo: "stepSize" } - Component.onCompleted: loaderStepSize.item.componentInfo = "stepSize" Text { id : stepSizeText @@ -191,12 +169,9 @@ Rectangle { Layout.preferredWidth: realTimeFactorText.width + indentation*3 Loader { id: loaderRealTimeFactor - width: iconWidth - height: iconHeight - y:10 - sourceComponent: plotIcon + sourceComponent: gzPlotIcon + property string gzComponentInfo: "realTimeFactor" } - Component.onCompleted: loaderRealTimeFactor.item.componentInfo = "realTimeFactor" Text { id : realTimeFactorText diff --git a/src/gui/plugins/component_inspector/SphericalCoordinates.qml b/src/gui/plugins/component_inspector/SphericalCoordinates.qml index a7b3b3970f..930fe67502 100644 --- a/src/gui/plugins/component_inspector/SphericalCoordinates.qml +++ b/src/gui/plugins/component_inspector/SphericalCoordinates.qml @@ -53,30 +53,11 @@ Rectangle { } Component { - id: plotIcon - Image { - property string componentInfo: "" - source: "plottable_icon.svg" - anchors.top: parent.top - anchors.left: parent.left - - Drag.mimeData: { "text/plain" : (model === null) ? "" : - "Component," + model.entity + "," + model.typeId + "," + - model.dataType + "," + componentInfo + "," + model.shortName - } - Drag.dragType: Drag.Automatic - Drag.supportedActions : Qt.CopyAction - Drag.active: dragMouse.drag.active - // a point to drag from - Drag.hotSpot.x: 0 - Drag.hotSpot.y: y - MouseArea { - id: dragMouse - anchors.fill: parent - drag.target: (model === null) ? null : parent - onPressed: parent.grabToImage(function(result) {parent.Drag.imageSource = result.url }) - onReleased: parent.Drag.drop(); - cursorShape: Qt.DragCopyCursor + id: gzPlotIcon + GzPlotIcon { + gzMimeData: { "text/plain" : (model === null) ? "" : + "Component," + model.entity + "," + model.typeId + "," + + model.dataType + "," + gzComponentInfo + "," + model.shortName } } } @@ -163,12 +144,9 @@ Rectangle { Layout.preferredWidth: latText.width + indentation*3 Loader { id: loaderPlotLat - width: iconWidth - height: iconHeight - y:10 - sourceComponent: plotIcon + sourceComponent: gzPlotIcon + property string gzComponentInfo: "latitude" } - Component.onCompleted: loaderPlotLat.item.componentInfo = "latitude" Text { id : latText @@ -201,12 +179,9 @@ Rectangle { Layout.preferredWidth: lonText.width + indentation*3 Loader { id: loaderPlotLon - width: iconWidth - height: iconHeight - y:10 - sourceComponent: plotIcon + sourceComponent: gzPlotIcon + property string gzComponentInfo: "longitude" } - Component.onCompleted: loaderPlotLon.item.componentInfo = "longitude" Text { id : lonText @@ -239,12 +214,9 @@ Rectangle { Layout.preferredWidth: elevationText.width + indentation*3 Loader { id: loaderPlotElevation - width: iconWidth - height: iconHeight - y:10 - sourceComponent: plotIcon + sourceComponent: gzPlotIcon + property string gzComponentInfo: "elevation" } - Component.onCompleted: loaderPlotElevation.item.componentInfo = "elevation" Text { id : elevationText @@ -277,12 +249,9 @@ Rectangle { Layout.preferredWidth: headingText.width + indentation*3 Loader { id: loaderPlotHeading - width: iconWidth - height: iconHeight - y:10 - sourceComponent: plotIcon + sourceComponent: gzPlotIcon + property string gzComponentInfo: "heading" } - Component.onCompleted: loaderPlotHeading.item.componentInfo = "heading" Text { id : headingText diff --git a/src/gui/plugins/view_angle/ViewAngle.qml b/src/gui/plugins/view_angle/ViewAngle.qml index e637e335fc..db87a27859 100644 --- a/src/gui/plugins/view_angle/ViewAngle.qml +++ b/src/gui/plugins/view_angle/ViewAngle.qml @@ -216,125 +216,23 @@ ColumnLayout { font.bold: true } - GridLayout { + GzPose { + y: 30 width: parent.width - columns: 6 - - Text { - text: "X (m)" - color: "dimgrey" - Layout.row: 0 - Layout.column: 0 - leftPadding: 5 - } - GzSpinBox { - id: x - Layout.fillWidth: true - Layout.row: 0 - Layout.column: 1 - value: ViewAngle.camPose[0] - maximumValue: Number.MAX_VALUE - minimumValue: -Number.MAX_VALUE - decimals: 6 - stepSize: 0.01 - onEditingFinished: ViewAngle.SetCamPose(x.value, y.value, z.value, roll.value, pitch.value, yaw.value) - } - Text { - text: "Y (m)" - color: "dimgrey" - Layout.row: 1 - Layout.column: 0 - leftPadding: 5 - } - GzSpinBox { - id: y - Layout.fillWidth: true - Layout.row: 1 - Layout.column: 1 - value: ViewAngle.camPose[1] - maximumValue: Number.MAX_VALUE - minimumValue: -Number.MAX_VALUE - decimals: 6 - stepSize: 0.01 - onEditingFinished: ViewAngle.SetCamPose(x.value, y.value, z.value, roll.value, pitch.value, yaw.value) - } - Text { - text: "Z (m)" - color: "dimgrey" - Layout.row: 2 - Layout.column: 0 - leftPadding: 5 - } - GzSpinBox { - id: z - Layout.fillWidth: true - Layout.row: 2 - Layout.column: 1 - value: ViewAngle.camPose[2] - maximumValue: Number.MAX_VALUE - minimumValue: -Number.MAX_VALUE - decimals: 6 - stepSize: 0.01 - onEditingFinished: ViewAngle.SetCamPose(x.value, y.value, z.value, roll.value, pitch.value, yaw.value) - } - - Text { - text: "Roll (rad)" - color: "dimgrey" - Layout.row: 0 - Layout.column: 2 - leftPadding: 5 - } - GzSpinBox { - id: roll - Layout.fillWidth: true - Layout.row: 0 - Layout.column: 3 - value: ViewAngle.camPose[3] - maximumValue: 6.28 - minimumValue: -6.28 - decimals: 6 - stepSize: 0.01 - onEditingFinished: ViewAngle.SetCamPose(x.value, y.value, z.value, roll.value, pitch.value, yaw.value) - } - Text { - text: "Pitch (rad)" - color: "dimgrey" - Layout.row: 1 - Layout.column: 2 - leftPadding: 5 - } - GzSpinBox { - id: pitch - Layout.fillWidth: true - Layout.row: 1 - Layout.column: 3 - value: ViewAngle.camPose[4] - maximumValue: 6.28 - minimumValue: -6.28 - decimals: 6 - stepSize: 0.01 - onEditingFinished: ViewAngle.SetCamPose(x.value, y.value, z.value, roll.value, pitch.value, yaw.value) - } - Text { - text: "Yaw (rad)" - color: "dimgrey" - Layout.row: 2 - Layout.column: 2 - leftPadding: 5 - } - GzSpinBox { - id: yaw - Layout.fillWidth: true - Layout.row: 2 - Layout.column: 3 - value: ViewAngle.camPose[5] - maximumValue: 6.28 - minimumValue: -6.28 - decimals: 6 - stepSize: 0.01 - onEditingFinished: ViewAngle.SetCamPose(x.value, y.value, z.value, roll.value, pitch.value, yaw.value) + Layout.fillWidth: true + readOnly: false + xValue: ViewAngle.camPose[0] + yValue: ViewAngle.camPose[1] + zValue: ViewAngle.camPose[2] + rollValue: ViewAngle.camPose[3] + pitchValue: ViewAngle.camPose[4] + yawValue: ViewAngle.camPose[5] + onGzPoseSet: { + // _x, _y, _z, _roll, _pitch, _yaw are parameters of signal gzPoseSet + // from gz-gui GzPose.qml + ViewAngle.SetCamPose(_x, _y, _z, _roll, _pitch, _yaw) } + expand: true } // Set camera's near/far clipping distance diff --git a/src/gz.cc b/src/gz.cc index 7f5f045e02..1337ab96e1 100644 --- a/src/gz.cc +++ b/src/gz.cc @@ -136,7 +136,7 @@ extern "C" int runServer(const char *_sdfString, const char *_playback, const char *_physicsEngine, const char *_renderEngineServer, const char *_renderEngineGui, const char *_file, const char *_recordTopics, int _waitGui, - int _headless) + int _headless, float _recordPeriod) { std::string startingWorldPath{""}; gz::sim::ServerConfig serverConfig; @@ -183,7 +183,7 @@ extern "C" int runServer(const char *_sdfString, // Initialize console log if ((_recordPath != nullptr && std::strlen(_recordPath) > 0) || - _record > 0 || _recordResources > 0 || + _record > 0 || _recordResources > 0 || _recordPeriod >= 0 || (_recordTopics != nullptr && std::strlen(_recordTopics) > 0)) { if (_playback != nullptr && std::strlen(_playback) > 0) @@ -194,6 +194,12 @@ extern "C" int runServer(const char *_sdfString, serverConfig.SetUseLogRecord(true); serverConfig.SetLogRecordResources(_recordResources); + if (_recordPeriod >= 0) + { + serverConfig.SetLogRecordPeriod( + std::chrono::duration_cast( + std::chrono::duration(_recordPeriod))); + } // If a record path is specified if (_recordPath != nullptr && std::strlen(_recordPath) > 0) diff --git a/src/gz.hh b/src/gz.hh index 2d958a7408..d594588613 100644 --- a/src/gz.hh +++ b/src/gz.hh @@ -57,6 +57,7 @@ extern "C" const char *worldInstallDir(); /// it receives a world path from GUI. /// null to record the default topics. /// \param[in] _headless True if server rendering should run headless +/// \param[in] _recordPeriod --record-period option /// \return 0 if successful, 1 if not. extern "C" int runServer(const char *_sdfString, int _iterations, int _run, float _hz, int _levels, @@ -65,7 +66,8 @@ extern "C" int runServer(const char *_sdfString, int _logCompress, const char *_playback, const char *_physicsEngine, const char *_renderEngineServer, const char *_renderEngineGui, const char *_file, - const char *_recordTopics, int _waitGui, int _headless); + const char *_recordTopics, int _waitGui, int _headless, + float _recordPeriod); /// \brief External hook to run simulation GUI. /// \param[in] _guiConfig Path to Gazebo GUI configuration file. diff --git a/src/systems/hydrodynamics/Hydrodynamics.cc b/src/systems/hydrodynamics/Hydrodynamics.cc index 57722685ce..fe0b3f8492 100644 --- a/src/systems/hydrodynamics/Hydrodynamics.cc +++ b/src/systems/hydrodynamics/Hydrodynamics.cc @@ -288,7 +288,7 @@ void Hydrodynamics::PreUpdate( // These variables follow Fossen's scheme in "Guidance and Control // of Ocean Vehicles." The `state` vector contains the ship's current velocity - // in the formate [x_vel, y_vel, z_vel, roll_vel, pitch_vel, yaw_vel]. + // in the format [x_vel, y_vel, z_vel, roll_vel, pitch_vel, yaw_vel]. // `stateDot` consists of the first derivative in time of the state vector. // `Cmat` corresponds to the Centripetal matrix // `Dmat` is the drag matrix @@ -332,13 +332,15 @@ void Hydrodynamics::PreUpdate( state(4) = localRotationalVelocity.Y(); state(5) = localRotationalVelocity.Z(); + // TODO(anyone) Make this configurable auto dt = static_cast(_info.dt.count())/1e9; stateDot = (state - this->dataPtr->prevState)/dt; this->dataPtr->prevState = state; // The added mass - const Eigen::VectorXd kAmassVec = this->dataPtr->Ma * stateDot; + // Negative sign signifies the behaviour change + const Eigen::VectorXd kAmassVec = - this->dataPtr->Ma * stateDot; // Coriolis and Centripetal forces for under water vehicles (Fossen P. 37) // Note: this is significantly different from VRX because we need to account diff --git a/src/systems/joint_state_publisher/JointStatePublisher.hh b/src/systems/joint_state_publisher/JointStatePublisher.hh index a6358da4b3..c83d9a1197 100644 --- a/src/systems/joint_state_publisher/JointStatePublisher.hh +++ b/src/systems/joint_state_publisher/JointStatePublisher.hh @@ -35,7 +35,7 @@ namespace systems { /// \brief The JointStatePub system publishes state information for /// a model. The published message type is gz::msgs::Model, and the - /// publication topic is "/world//model//state". + /// publication topic is determined by the `` parameter. /// /// By default the JointStatePublisher will publish all joints for /// a model. Use the `` system parameter, described below, to @@ -43,6 +43,9 @@ namespace systems /// /// # System Parameters /// + /// ``: Name of the topic to publish to. This parameter is optional, + /// and if not provided, the joint state will be published to + /// "/world//model//state". /// ``: Name of a joint to publish. This parameter can be /// specified multiple times, and is optional. All joints in a model will /// be published if joint names are not specified. diff --git a/src/systems/log/LogRecord.cc b/src/systems/log/LogRecord.cc index e0055e2300..7ea230f9d7 100644 --- a/src/systems/log/LogRecord.cc +++ b/src/systems/log/LogRecord.cc @@ -157,6 +157,12 @@ class gz::sim::systems::LogRecordPrivate /// \brief List of saved models if record with resources is enabled. public: std::set savedModels; + + /// \brief Time period between state recording + public: std::chrono::steady_clock::duration recordPeriod{0}; + + /// \brief Last time states are recorded + public: std::chrono::steady_clock::duration lastRecordSimTime{0}; }; bool LogRecordPrivate::started{false}; @@ -209,6 +215,11 @@ void LogRecord::Configure(const Entity &_entity, this->dataPtr->SetRecordResources(_sdf->Get("record_resources", false).first); + this->dataPtr->recordPeriod = + std::chrono::duration_cast( + std::chrono::duration( + _sdf->Get("record_period", 0.0).first)); + this->dataPtr->compress = _sdf->Get("compress", false).first; this->dataPtr->cmpPath = _sdf->Get("compress_path", "").first; @@ -690,16 +701,34 @@ void LogRecord::PostUpdate(const UpdateInfo &_info, } } + bool record = true; + if (this->dataPtr->recordPeriod > std::chrono::steady_clock::duration::zero()) + { + if (_ecm.HasOneTimeComponentChanges() || + (_info.simTime - this->dataPtr->lastRecordSimTime) >= + this->dataPtr->recordPeriod) + { + this->dataPtr->lastRecordSimTime = _info.simTime; + } + else + { + record = false; + } + } + // TODO(louise) Use the SceneBroadcaster's topic once that publishes // the changed state // \todo(anyone) A potential enhancement here is have a keyframe mechanism // to store complete state periodically, and then store incremental from // that. It would reduce some of the compute on replaying // (especially in tools like plotting or seeking through logs). - msgs::SerializedStateMap stateMsg; - _ecm.ChangedState(stateMsg); - if (!stateMsg.entities().empty()) - this->dataPtr->statePub.Publish(stateMsg); + if (record) + { + msgs::SerializedStateMap stateMsg; + _ecm.ChangedState(stateMsg); + if (!stateMsg.entities().empty()) + this->dataPtr->statePub.Publish(stateMsg); + } // If there are new models loaded, save meshes and textures if (this->dataPtr->RecordResources() && _ecm.HasNewEntities()) diff --git a/src/systems/scene_broadcaster/SceneBroadcaster.cc b/src/systems/scene_broadcaster/SceneBroadcaster.cc index 0d739130b1..68b309d7a2 100644 --- a/src/systems/scene_broadcaster/SceneBroadcaster.cc +++ b/src/systems/scene_broadcaster/SceneBroadcaster.cc @@ -45,6 +45,7 @@ #include "gz/sim/components/Light.hh" #include "gz/sim/components/Link.hh" #include "gz/sim/components/LogicalCamera.hh" +#include "gz/sim/components/LogPlaybackStatistics.hh" #include "gz/sim/components/Material.hh" #include "gz/sim/components/Model.hh" #include "gz/sim/components/Name.hh" @@ -252,6 +253,10 @@ class gz::sim::systems::SceneBroadcasterPrivate /// \brief Store SDF scene information so that it can be inserted into /// scene message. public: sdf::Scene sdfScene; + + /// \brief Flag used to indicate if periodic changes need to be published + /// This is currently only used in playback mode. + public: bool pubPeriodicChanges{false}; }; ////////////////////////////////////////////////// @@ -351,8 +356,11 @@ void SceneBroadcaster::PostUpdate(const UpdateInfo &_info, auto now = std::chrono::system_clock::now(); bool itsPubTime = (now - this->dataPtr->lastStatePubTime > this->dataPtr->statePublishPeriod[_info.paused]); + // check if we need to publish periodic changes in playback mode. + bool pubChanges = this->dataPtr->pubPeriodicChanges && + _manager.HasPeriodicComponentChanges(); auto shouldPublish = this->dataPtr->statePub.HasConnections() && - (changeEvent || itsPubTime); + (changeEvent || itsPubTime || pubChanges); if (this->dataPtr->stateServiceRequest || shouldPublish) { @@ -375,9 +383,39 @@ void SceneBroadcaster::PostUpdate(const UpdateInfo &_info, else if (!_info.paused) { GZ_PROFILE("SceneBroadcast::PostUpdate UpdateState"); - auto periodicComponents = _manager.ComponentTypesWithPeriodicChanges(); - _manager.State(*this->dataPtr->stepMsg.mutable_state(), - {}, periodicComponents); + + if (_manager.HasPeriodicComponentChanges()) + { + auto periodicComponents = _manager.ComponentTypesWithPeriodicChanges(); + _manager.State(*this->dataPtr->stepMsg.mutable_state(), + {}, periodicComponents); + this->dataPtr->pubPeriodicChanges = false; + } + else + { + // log files may be recorded at lower rate than sim time step. So in + // playback mode, the scene broadcaster may not see any periodic + // changed states here since it no longer happens every iteration. + // As the result, no state changes are published to be GUI, causing + // visuals in the GUI scene to miss updates. The visuals are only + // updated if by some timing coincidence that log playback updates + // the ECM at the same iteration as when the scene broadcaster is going + // to publish perioidc changes here. + // To work around the issue, we force the scene broadcaster + // to publish states at an offcycle iteration the next time it sees + // periodic changes. + auto playbackComp = + _manager.Component( + this->dataPtr->worldEntity); + if (playbackComp) + { + this->dataPtr->pubPeriodicChanges = true; + } + // this creates an empty state in the msg even there are no periodic + // changed components - done to preseve existing behavior. + // we may be able to remove this in the future and update tests + this->dataPtr->stepMsg.mutable_state(); + } } // Full state on demand diff --git a/test/integration/CMakeLists.txt b/test/integration/CMakeLists.txt index 2d5babb5ae..5a5ed0d506 100644 --- a/test/integration/CMakeLists.txt +++ b/test/integration/CMakeLists.txt @@ -24,6 +24,7 @@ set(tests force_torque_system.cc fuel_cached_server.cc halt_motion.cc + hydrodynamics.cc imu_system.cc joint_controller_system.cc joint_position_controller_system.cc diff --git a/test/integration/ackermann_steering_system.cc b/test/integration/ackermann_steering_system.cc index b05d8647c2..886cc43c3d 100644 --- a/test/integration/ackermann_steering_system.cc +++ b/test/integration/ackermann_steering_system.cc @@ -414,7 +414,8 @@ TEST_P(AckermannSteeringTest, GZ_UTILS_TEST_DISABLED_ON_WIN32(TfPublishes)) int sleep = 0; int maxSleep = 30; - for (; odomPoses.size() < 3 && sleep < maxSleep; ++sleep) + for (; (odomPoses.size() < 3 || odomPoses.size() != tfPoses.size()) && + sleep < maxSleep; ++sleep) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); } diff --git a/test/integration/hydrodynamics.cc b/test/integration/hydrodynamics.cc new file mode 100644 index 0000000000..97627b2e9f --- /dev/null +++ b/test/integration/hydrodynamics.cc @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2022 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. + * + */ + +#include + +#include + +#include +#include +#include +#include + +#include "gz/sim/Link.hh" +#include "gz/sim/Model.hh" +#include "gz/sim/Server.hh" +#include "gz/sim/SystemLoader.hh" +#include "gz/sim/TestFixture.hh" +#include "gz/sim/Util.hh" +#include "gz/sim/World.hh" + +#include "test_config.hh" +#include "../helpers/EnvTestFixture.hh" + +using namespace gz; +using namespace sim; + +class HydrodynamicsTest : public InternalFixture<::testing::Test> +{ + /// \brief Test a world file + /// \param[in] _world Path to world file + /// \param[in] _namespace Namespace for topic + /// \param[in] _density Fluid density + /// \param[in] _viscosity Fluid viscosity + /// \param[in] _radius Body's radius + /// \param[in] _area Body surface area + /// \param[in] _drag_coeff Body drag coefficient + public: std::vector TestWorld(const std::string &_world, + const std::string &_namespace); +}; + +////////////////////////////////////////////////// +std::vector HydrodynamicsTest::TestWorld( + const std::string &_world, const std::string &_namespace) +{ + // Maximum verbosity for debugging + common::Console::SetVerbosity(4); + + // Start server + ServerConfig serverConfig; + serverConfig.SetSdfFile(_world); + + TestFixture fixture(serverConfig); + + Model model; + Link body; + std::vector bodyVels; + fixture. + OnConfigure( + [&](const Entity &_worldEntity, + const std::shared_ptr &/*_sdf*/, + EntityComponentManager &_ecm, + EventManager &/*eventMgr*/) + { + World world(_worldEntity); + + auto modelEntity = world.ModelByName(_ecm, _namespace); + EXPECT_NE(modelEntity, kNullEntity); + model = Model(modelEntity); + + auto bodyEntity = model.LinkByName(_ecm, _namespace + "_link"); + EXPECT_NE(bodyEntity, kNullEntity); + + body = Link(bodyEntity); + body.EnableVelocityChecks(_ecm); + + // Add force + math::Vector3d force(0, 0, 10.0); + body.AddWorldForce(_ecm, force); + }). + OnPostUpdate([&](const UpdateInfo &/*_info*/, + const EntityComponentManager &_ecm) + { + auto bodyVel = body.WorldLinearVelocity(_ecm); + ASSERT_TRUE(bodyVel); + bodyVels.push_back(bodyVel.value()); + }). + Finalize(); + + fixture.Server()->Run(true, 1000, false); + EXPECT_EQ(1000u, bodyVels.size()); + + EXPECT_NE(model.Entity(), kNullEntity); + EXPECT_NE(body.Entity(), kNullEntity); + + return bodyVels; +} + +///////////////////////////////////////////////// +/// This test evaluates whether the hydrodynamic plugin affects the motion +/// of the body when a force is applied. +TEST_F(HydrodynamicsTest, VelocityTestinOil) +{ + auto world = common::joinPaths(std::string(PROJECT_SOURCE_PATH), + "test", "worlds", "hydrodynamics.sdf"); + + auto sphere1Vels = this->TestWorld(world, "sphere1"); + auto sphere2Vels = this->TestWorld(world, "sphere2"); + + for (unsigned int i = 0; i < 1000; ++i) + { + // Sanity check + EXPECT_FLOAT_EQ(0.0, sphere1Vels[i].X()); + EXPECT_FLOAT_EQ(0.0, sphere1Vels[i].Y()); + EXPECT_FLOAT_EQ(0.0, sphere2Vels[i].X()); + EXPECT_FLOAT_EQ(0.0, sphere2Vels[i].Y()); + + // Wait a couple of iterations for the body to move + if(i > 4) + { + EXPECT_LT(sphere1Vels[i].Z(), sphere2Vels[i].Z()); + + if (i > 900) + { + // Expect for the velocity to stabilize + EXPECT_NEAR(sphere1Vels[i-1].Z(), sphere1Vels[i].Z(), 1e-6); + EXPECT_NEAR(sphere2Vels[i-1].Z(), sphere2Vels[i].Z(), 1e-6); + } + } + } +} + +///////////////////////////////////////////////// +/// This test makes sure that the transforms of the hydrodynamics +/// plugin are correct by comparing 3 cylinders in different +/// positions and orientations. +TEST_F(HydrodynamicsTest, TransformsTestinWater) +{ + auto world = common::joinPaths(std::string(PROJECT_SOURCE_PATH), + "test", "worlds", "hydrodynamics.sdf"); + + auto cylinder1Vels = this->TestWorld(world, "cylinder1"); + auto cylinder2Vels = this->TestWorld(world, "cylinder2"); + auto cylinder3Vels = this->TestWorld(world, "cylinder3"); + + for (unsigned int i = 900; i < 1000; ++i) + { + // Expect for the velocity to stabilize + EXPECT_NEAR(cylinder1Vels[i-1].Z(), cylinder1Vels[i].Z(), 1e-6); + EXPECT_NEAR(cylinder2Vels[i-1].Z(), cylinder2Vels[i].Z(), 1e-6); + EXPECT_NEAR(cylinder3Vels[i-1].Z(), cylinder3Vels[i].Z(), 1e-6); + + // Expect for final velocities to be similar + EXPECT_NEAR(cylinder1Vels[i].Z(), cylinder2Vels[i].Z(), 1e-4); + EXPECT_NEAR(cylinder2Vels[i].Z(), cylinder3Vels[i].Z(), 1e-4); + } +} diff --git a/test/integration/log_system.cc b/test/integration/log_system.cc index fbf025ef26..ef91066f2c 100644 --- a/test/integration/log_system.cc +++ b/test/integration/log_system.cc @@ -1523,7 +1523,7 @@ TEST_F(LogSystemTest, GZ_UTILS_TEST_DISABLED_ON_WIN32(LogResources)) // Recorded models should exist EXPECT_GT(entryCount(recordPath), 2); EXPECT_TRUE(common::exists(common::joinPaths(recordPath, homeFake, - ".gz", "fuel", "fuel.ignitionrobotics.org", "openrobotics", + ".gz", "fuel", "fuel.gazebosim.org", "openrobotics", "models", "x2 config 1"))); // Remove artifacts. Recreate new directory @@ -1558,11 +1558,11 @@ TEST_F(LogSystemTest, GZ_UTILS_TEST_DISABLED_ON_WIN32(LogResources)) EXPECT_GT(entryCount(recordPath), 1); #endif EXPECT_TRUE(common::exists(common::joinPaths(recordPath, homeFake, - ".gz", "fuel", "fuel.ignitionrobotics.org", "openrobotics", + ".gz", "fuel", "fuel.gazebosim.org", "openrobotics", "models", "x2 config 1"))); // Revert environment variable after test is done - EXPECT_TRUE(gz::common::setenv(GZ_HOMEDIR, homeOrig.c_str())); + EXPECT_TRUE(common::setenv(GZ_HOMEDIR, homeOrig.c_str())); // Remove artifacts this->RemoveLogsDir(); @@ -1643,3 +1643,84 @@ TEST_F(LogSystemTest, GZ_UTILS_TEST_DISABLED_ON_WIN32(LogTopics)) this->CreateLogsDir(); #endif } + +///////////////////////////////////////////////// +TEST_F(LogSystemTest, GZ_UTILS_TEST_DISABLED_ON_WIN32(RecordPeriod)) +{ + // Create temp directory to store log + this->CreateLogsDir(); + + // test world + const auto recordSdfPath = common::joinPaths( + std::string(PROJECT_SOURCE_PATH), "test", "worlds", + "log_record_resources.sdf"); + + // Change environment variable so that downloaded fuel files aren't written + // to $HOME + std::string homeOrig; + common::env(GZ_HOMEDIR, homeOrig); + std::string homeFake = common::joinPaths(this->logsDir, "default"); + EXPECT_TRUE(common::setenv(GZ_HOMEDIR, homeFake.c_str())); + + const std::string recordPath = this->logDir; + std::string statePath = common::joinPaths(recordPath, "state.tlog"); + + int numIterations = 100; +#ifndef __APPLE__ + // Log from command line + { + // Command line triggers ign.cc, which handles initializing ignLogDirectory + std::string cmd = kGzCommand + " -r -v 4 --iterations " + + std::to_string(numIterations) + " " + + "--record-period 0.002 " + + "--record-path " + recordPath + " " + recordSdfPath; + std::cout << "Running command [" << cmd << "]" << std::endl; + + // Run + std::string output = customExecStr(cmd); + std::cout << output << std::endl; + } + + std::string consolePath = common::joinPaths(recordPath, "server_console.log"); + EXPECT_TRUE(common::exists(consolePath)) << consolePath; + EXPECT_TRUE(common::exists(statePath)) << statePath; + + // Recorded models should exist + EXPECT_GT(entryCount(recordPath), 1); + + // Verify file is created + auto logFile = common::joinPaths(recordPath, "state.tlog"); + EXPECT_TRUE(common::exists(logFile)); + + // Load the state log file into a player. + transport::log::Playback player(statePath); + const int64_t addTopicResult = player.AddTopic(std::regex(".*")); + + // There should be 2 topics (sdf, & state) + EXPECT_EQ(2, addTopicResult); + + int msgCount = 0; + std::function stateCb = + [&](const msgs::SerializedStateMap &) -> void + { + msgCount++; + }; + + // Subscribe to the state topic + transport::Node node; + node.Subscribe("/world/default/changed_state", stateCb); + + // Begin playback + transport::log::PlaybackHandlePtr handle = + player.Start(std::chrono::seconds(5), false); + handle->WaitUntilFinished(); + + // There were 100 iterations of simulation, and we were recording at 2ms + // so there should be 50 state messages. + EXPECT_EQ(50, msgCount); + + // Remove artifacts. Recreate new directory + this->RemoveLogsDir(); + this->CreateLogsDir(); +#endif +} diff --git a/test/worlds/hydrodynamics.sdf b/test/worlds/hydrodynamics.sdf new file mode 100644 index 0000000000..234d6314ea --- /dev/null +++ b/test/worlds/hydrodynamics.sdf @@ -0,0 +1,307 @@ + + + + + + 0.001 + + 0 + + + + 0 0 0 + + + + + + + + + + true + 0 0 10 0 0 0 + 1 1 1 1 + 0.5 0.5 0.5 1 + + 1000 + 0.9 + 0.01 + 0.001 + + -0.5 0.1 -0.9 + + + + + 1 0.25 0 0 0 0 + + 25 + + 0.4 + 0 + 0 + 0.4 + 0 + 0.4 + + + + + + + 0.2 + + + + + + + 0.2 + + + + + + + + + 1 -0.25 0 0 0 0 + + 25 + + 0.4 + 0 + 0 + 0.4 + 0 + 0.4 + + + + + + + 0.2 + + + + + + + 0.2 + + + + + + + sphere2_link + 918 + 0 + 0 + 15.381 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 11.5359 + 0.211869 + 0 + 0 + 0 + 0 + 0 + 0 + + + + + + 0 0 0 0 0 0 + + 40 + + 0.8 + 0 + 0 + 0.8 + 0 + 0.8 + + + + + + + 0.2 + 1 + + + + + + + 0.2 + 0.1 + + + + + + + cylinder1_link + 1000 + 0 + 0 + 12.5664 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 94.2475 + 0.0037699 + 0 + 0 + 0 + 0 + 0 + 0 + + + + + + -1 0.5 0 45 0 45 + + 40 + + 0.8 + 0 + 0 + 0.8 + 0 + 0.8 + + + + + + + 0.2 + 1 + + + + + + + 0.2 + 0.1 + + + + + + + cylinder2_link + 1000 + 0 + 0 + 12.5664 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 94.2475 + 0.0037699 + 0 + 0 + 0 + 0 + 0 + 0 + + + + + + -1 -0.5 0 0 90 0 + + 40 + + 0.8 + 0 + 0 + 0.8 + 0 + 0.8 + + + + + + + 0.2 + 1 + + + + + + + 0.2 + 0.1 + + + + + + + cylinder3_link + 1000 + 0 + 0 + 12.5664 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 94.2475 + 0.0037699 + 0 + 0 + 0 + 0 + 0 + 0 + + + + diff --git a/test/worlds/log_record_resources.sdf b/test/worlds/log_record_resources.sdf index c95e80ca59..6a75af7983 100644 --- a/test/worlds/log_record_resources.sdf +++ b/test/worlds/log_record_resources.sdf @@ -53,7 +53,7 @@ false staging_area 0 0 0 0 0 0 - https://fuel.ignitionrobotics.org/1.0/OpenRobotics/models/X2 Config 1 + https://fuel.gazebosim.org/1.0/OpenRobotics/models/X2 Config 1