Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[meshcat] Add realtime rate display to MeshcatVisualizer #16981

Merged
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
9358870
Add realtime rate display to MeshcatVisualizer
Apr 18, 2022
8a21e08
Review comments, mostly style
Apr 26, 2022
4423a16
Make rtr plot hidden depending on meshcat params.
Apr 28, 2022
43127c4
Add Timer interface which can be mocked to test realtime rate calcula…
Apr 28, 2022
48ec72c
Add realtimerate display unit tests to Meshcat and realtime rate calc…
Apr 29, 2022
fd52446
Fix time_test whitespace.
Apr 30, 2022
d491ac5
Fix style linting and remove test code from meshcat_visualizer_test.cc
May 4, 2022
9601347
Alphabetize some build parts. Point stats.js repo to head of master b…
May 12, 2022
a43d17f
rename time library "timer" and make SteadyTimer calls not inlined
Jun 28, 2022
20325aa
Naming cleanup and style guide fixes.
Jun 28, 2022
401102b
Make show_stats_plot positive. Change Timer to return doubles and not…
Jun 29, 2022
4d306ce
fix InstantaneousRealtimeRateCalculatorTest to use plain double
Jun 29, 2022
c981b4b
Fix timer.h include path
Jun 29, 2022
69981d1
point new TODOs to #16486
Jun 29, 2022
414b0e1
protect timer copy move assign constructors.
Jun 30, 2022
883e7bf
add pybinding for show_stats_plot and disable the plot in meldis.
Jun 30, 2022
4bf6648
add pybinding for show_stats_plot to pybinding unit tests.
Jun 30, 2022
4aac533
add pybinding Meshcat::SetRealtimeRate.
Jun 30, 2022
90e7098
__repr__ for new MeshcatParams field and fixed pyformatting.
Jul 1, 2022
9e5c255
Merge branch 'master' into feature/rtr_display_merge
pathammer Jul 6, 2022
d376952
platform review comments
Jul 7, 2022
5e84740
small formatting fix and new todo
Jul 7, 2022
72e4db3
make timer_test more robust.
Jul 7, 2022
f9f64e4
comment indentation
Jul 7, 2022
584949a
fix indentation
Jul 7, 2022
56c3b16
rename CalculateRealtimeRate
Jul 7, 2022
48daa57
make timer_test test the function of Start()
Jul 7, 2022
e9c35e8
make steady timer constructor call Start()
Jul 7, 2022
beef7ef
remove unnecessary call to chrono
Jul 7, 2022
2c0e82a
add more strongly worded comment about timer interface ctor
Jul 7, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
16 changes: 16 additions & 0 deletions common/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ drake_cc_package_library(
":symbolic_latex",
":symbolic_trigonometric_polynomial",
":temp_directory",
":time",
":type_safe_index",
":unused",
":value",
Expand Down Expand Up @@ -599,6 +600,14 @@ drake_cc_library(
],
)

drake_cc_library(
name = "time",
hdrs = ["timer.h"],
deps = [
":essential",
],
)

drake_cc_library(
name = "value",
srcs = ["value.cc"],
Expand Down Expand Up @@ -1465,6 +1474,13 @@ drake_cc_googletest(
],
)

drake_cc_googletest(
name = "time_test",
deps = [
":time",
],
)

drake_cc_googletest(
name = "drake_cc_googletest_main_test_device",
args = ["--magic_number=1.0"],
Expand Down
20 changes: 20 additions & 0 deletions common/test/time_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#include "gtest/gtest.h"

#include "drake/common/timer.h"

namespace drake {
GTEST_TEST(TimeTest, TimerFunction) {
std::unique_ptr<Timer> timer = std::make_unique<SteadyTimer>();
timer->Start();
// Assert that some positive time has passed
EXPECT_GT(timer->Tick().count(), 0.0);
EXPECT_GT(timer->Stop().count(), 0.0);
}

GTEST_TEST(TimeTest, ConstructorStartsTimer) {
SteadyTimer timer{};
// Assert that some positive time has passed
EXPECT_GT(timer.Tick().count(), 0.0);
EXPECT_GT(timer.Stop().count(), 0.0);
}
} // namespace drake
37 changes: 37 additions & 0 deletions common/timer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#pragma once

#include <chrono>

/// @file
/// Provides drake::Timer interface and drake::SteadyTimer for timing events

namespace drake {

/// Abstract base class for timing utility.
struct Timer {
using duration = std::chrono::duration<double>;
virtual ~Timer() = default;
/// begin timing
virtual void Start() = 0;
/// stop timing
/// @return the ammount of time since the timer started
virtual duration Stop() = 0;
/// obtain multiple measurements from the same baseline
/// @return the ammount of time since the timer started
virtual duration Tick() = 0;
};

/// Implementation of timing utility that uses monotonic
/// std::chrono::steady_clock
class SteadyTimer : public Timer {
public:
using clock = std::chrono::steady_clock;
void Start() override { start_time = clock::now(); }
duration Tick() override { return duration(clock::now() - start_time); }
duration Stop() override { return Tick(); }

protected:
clock::time_point start_time{clock::now()};
};

} // namespace drake
19 changes: 19 additions & 0 deletions geometry/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,22 @@ drake_cc_library(
],
)

genrule(
name = "stats_js_genrule",
srcs = ["@statsjs//:build/stats.min.js"],
outs = ["stats.min.js"],
cmd = "cp $< $@",
visibility = ["//visibility:private"],
)

genrule(
name = "msgpack_lite_js_genrule",
srcs = ["@msgpack_lite_js//:dist/msgpack.min.js"],
outs = ["msgpack.min.js"],
cmd = "cp $< $@",
visibility = ["//visibility:private"],
)

genrule(
name = "meshcat_ico_genrule",
srcs = ["//doc:favicon.ico"],
Expand All @@ -369,6 +385,8 @@ filegroup(
"meshcat.html",
":meshcat.ico",
":meshcat.js",
":msgpack.min.js",
":stats.min.js",
],
visibility = ["//visibility:private"],
)
Expand Down Expand Up @@ -517,6 +535,7 @@ drake_cc_library(
":scene_graph",
"//common:essential",
"//math:geometric_transform",
"//systems/analysis:instantaneous_realtime_rate_calculator",
"//systems/framework:context",
"//systems/framework:diagram_builder",
"//systems/framework:leaf_system",
Expand Down
62 changes: 56 additions & 6 deletions geometry/meshcat.cc
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ std::string LoadResource(const std::string& resource_name) {
const std::string& GetUrlContent(std::string_view url_path) {
static const drake::never_destroyed<std::string> meshcat_js(
LoadResource("drake/geometry/meshcat.js"));
static const drake::never_destroyed<std::string> stats_js(
LoadResource("drake/geometry/stats.min.js"));
static const drake::never_destroyed<std::string> msgpack_lite_js(
LoadResource("drake/geometry/msgpack.min.js"));
static const drake::never_destroyed<std::string> meshcat_ico(
LoadResource("drake/geometry/meshcat.ico"));
static const drake::never_destroyed<std::string> meshcat_html(
Expand All @@ -65,6 +69,12 @@ const std::string& GetUrlContent(std::string_view url_path) {
if (url_path == "/meshcat.js") {
return meshcat_js.access();
}
if (url_path == "/stats.min.js") {
return stats_js.access();
}
if (url_path == "/msgpack.min.js") {
return msgpack_lite_js.access();
}
if (url_path == "/favicon.ico") {
return meshcat_ico.access();
}
Expand Down Expand Up @@ -600,6 +610,21 @@ class Meshcat::Impl {
return port_;
}

// This function is public via the PIMPL.
void SetRealtimeRate(double rate) {
DRAKE_DEMAND(IsThread(main_thread_id_));
internal::RealtimeRateData data;
data.rate = rate;
Defer([this, data = std::move(data)]() {
DRAKE_DEMAND(IsThread(websocket_thread_id_));
DRAKE_DEMAND(app_ != nullptr);
std::stringstream message_stream;
msgpack::pack(message_stream, data);
std::string message = message_stream.str();
app_->publish("all", message, uWS::OpCode::BINARY, false);
});
}

// This function is public via the PIMPL.
std::string ws_url() const {
DRAKE_DEMAND(IsThread(main_thread_id_));
Expand Down Expand Up @@ -1359,7 +1384,13 @@ class Meshcat::Impl {
url = url.replace("https://", "wss://")
url = url.replace("/index.html", "/")
url = url.replace("/meshcat.html", "/")
viewer.connect(url);
connection = new WebSocket(url);
connection.binaryType = "arraybuffer";
connection.onmessage = (msg) => handle_message(msg);
connection.onclose = function(evt) {
console.log("onclose:", evt);
}
viewer.connection = connection
} catch (e) {
console.info("Not connected to MeshCat websocket server: ", e);
})""";
Expand All @@ -1368,11 +1399,18 @@ class Meshcat::Impl {
html.replace(pos, html_connect.size(), std::move(f.get()));

// Insert the javascript directly into the html.
const std::string meshcat_src_link(" src=\"meshcat.js\"");
pos = html.find(meshcat_src_link);
DRAKE_DEMAND(pos != std::string::npos);
html.erase(pos, meshcat_src_link.size());
html.insert(pos+1, GetUrlContent("/meshcat.js"));
std::vector<std::pair<std::string, std::string>> js_paths{
{" src=\"meshcat.js\"", "/meshcat.js"},
{" src=\"stats.min.js\"", "/stats.min.js"},
{" src=\"msgpack.min.js\"", "/msgpack.min.js"},
};
for (const auto& js_pair : js_paths) {
const std::string src_link(js_pair.first);
pos = html.find(src_link);
DRAKE_DEMAND(pos != std::string::npos);
html.erase(pos, src_link.size());
html.insert(pos+1, GetUrlContent(js_pair.second));
}

return html;
}
Expand Down Expand Up @@ -1645,6 +1683,14 @@ class Meshcat::Impl {
ws->send(message_stream.str());
}
}

// Tell client if the realtime rate plot should be hidden
internal::HideRealtimeRate rtr_hidden_msg{};
rtr_hidden_msg.hide = params_.hide_stats_plot;
std::stringstream rtr_msg_stream;
msgpack::pack(rtr_msg_stream, rtr_hidden_msg);
ws->send(rtr_msg_stream.str());

if (inject_open_fault_.load()) {
throw std::runtime_error(
"InjectWebsocketThreadFault during socket open");
Expand Down Expand Up @@ -1992,6 +2038,10 @@ void Meshcat::Delete(std::string_view path) {
impl().Delete(path);
}

void Meshcat::SetRealtimeRate(double rate) {
impl().SetRealtimeRate(rate);
}

void Meshcat::SetProperty(std::string_view path, std::string property,
bool value) {
impl().SetProperty(path, std::move(property), value);
Expand Down
10 changes: 10 additions & 0 deletions geometry/meshcat.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ struct MeshcatParams {
"all interfaces".
*/
std::string web_url_pattern{"http://{host}:{port}"};

/** Determines whether or not to display the stats plot including realtime
* rate display. */
bool hide_stats_plot{false};
};

/** Provides an interface to %Meshcat (https://github.com/rdeits/meshcat).
Expand Down Expand Up @@ -347,6 +351,12 @@ class Meshcat {
See @ref meshcat_path for the detailed semantics of deletion. */
void Delete(std::string_view path = "");

/** Sets the realtime rate that is displayed in the meshcat visualizer strip
chart.
@param rate the realtime rate value to be displayed, will be converted to a
percentage (multiplied by 100) */
void SetRealtimeRate(double rate);

/** Sets a single named property of the object at the given path. For example,
@verbatim
meshcat.SetProperty("/Background", "visible", false);
Expand Down
50 changes: 48 additions & 2 deletions geometry/meshcat.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<!-- This file is forked from dist/index.html in rdeits/meshcat. -->
<!-- This file is forked from dist/index.html in rdeits/meshcat.-->
<html>

<head>
Expand All @@ -12,8 +12,44 @@
</div>

<script type="text/javascript" src="meshcat.js"></script>
<script type="text/javascript" src="stats.min.js"></script>
<script type="text/javascript" src="msgpack.min.js"></script>
<script>
var stats = new Stats();
var rtrPanel = stats.addPanel( new Stats.Panel( 'rtr%', '#ff8', '#221' ) );
document.body.appendChild( stats.dom );
stats.dom.id = "stats-plot";
stats.showPanel(stats.dom.children.length - 1)
var rtr = 0;
var viewer = new MeshCat.Viewer(document.getElementById("meshcat-pane"));
viewer.animate = function () {
viewer.animator.update();
if (viewer.needs_render) {
viewer.render();
}
}

function animate() {
stats.begin();
rtrPanel.update(rtr*100, 100); // easier to read as percentage
viewer.animate()
stats.end();
requestAnimationFrame(animate);
}

function handle_message(ws_message) {
let decoded = msgpack.decode(new Uint8Array(ws_message.data));
if (decoded.type == "realtime_rate") {
rtr = decoded.rate;
} else if (decoded.type == "hide_realtime_rate") {
stats.dom.style.display = decoded.hide ? "none" : "block";
console.log("hiding", decoded.hide)
} else {
viewer.handle_command(decoded)
}
}

requestAnimationFrame( animate );
// Set background to match Drake Visualizer.
viewer.set_property(['Background'], "top_color", [.95, .95, 1.0])
viewer.set_property(['Background'], "bottom_color", [.32, .32, .35])
Expand All @@ -26,7 +62,13 @@
url = url.replace("https://", "wss://")
url = url.replace("/index.html", "/")
url = url.replace("/meshcat.html", "/")
viewer.connect(url);
connection = new WebSocket(url);
connection.binaryType = "arraybuffer";
connection.onmessage = (msg) => handle_message(msg);
connection.onclose = function(evt) {
console.log("onclose:", evt);
}
viewer.connection = connection
} catch (e) {
console.info("Not connected to MeshCat websocket server: ", e);
}
Expand All @@ -42,6 +84,10 @@
height: 100vh;
overflow: hidden;
}

#stats-plot {
display: none;
}
</style>
<script id="embedded-json"></script>
</body>
Expand Down
18 changes: 18 additions & 0 deletions geometry/meshcat_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,24 @@ struct SetTransformData {
MSGPACK_DEFINE_MAP(type, path, matrix);
};

// Note that this struct is unique to Drake's integration of meshcat; it is not
// part of upstream meshcat.js. We handle it directly within meshcat.html,
// without ever feeding it into meshcat.js.
struct RealtimeRateData {
std::string type{"realtime_rate"};
double rate{};
MSGPACK_DEFINE_MAP(type, rate);
};

// Note that this struct is unique to Drake's integration of meshcat; it is not
// part of upstream meshcat.js. We handle it directly within meshcat.html,
// without ever feeding it into meshcat.js.
struct HideRealtimeRate {
std::string type{"hide_realtime_rate"};
bool hide{false};
MSGPACK_DEFINE_MAP(type, hide);
};

struct DeleteData {
std::string type{"delete"};
std::string path;
Expand Down