Skip to content

Commit

Permalink
[geometry] Establish Meshcat in C++ (step 4)
Browse files Browse the repository at this point in the history
Resolves RobotLocomotion#13038

Adds MeshcatVisualizer
  • Loading branch information
RussTedrake committed Aug 26, 2021
1 parent 1fa98f7 commit 7b6cc4c
Show file tree
Hide file tree
Showing 7 changed files with 723 additions and 12 deletions.
46 changes: 44 additions & 2 deletions geometry/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ drake_cc_package_library(
":internal_frame",
":internal_geometry",
":meshcat",
":meshcat_visualizer",
":proximity_engine",
":proximity_properties",
":rgba",
Expand Down Expand Up @@ -380,9 +381,18 @@ drake_cc_binary(
name = "meshcat_manual_test",
testonly = True,
srcs = ["test/meshcat_manual_test.cc"],
data = ["//systems/sensors:test_models"],
data = [
"//manipulation/models/iiwa_description:models",
"//systems/sensors:test_models",
],
visibility = ["//visibility:private"],
deps = [":meshcat"],
deps = [
":meshcat",
":meshcat_visualizer",
"//multibody/parsing",
"//multibody/plant",
"//systems/analysis:simulator",
],
)

drake_py_binary(
Expand All @@ -407,6 +417,38 @@ drake_cc_googletest(
],
)

drake_cc_library(
name = "meshcat_visualizer",
srcs = ["meshcat_visualizer.cc"],
hdrs = ["meshcat_visualizer.h"],
deps = [
":geometry_roles",
":geometry_version",
":meshcat",
":rgba",
":scene_graph",
"//common:essential",
"//math:geometric_transform",
"//systems/framework:context",
"//systems/framework:leaf_system",
],
)

drake_cc_googletest(
name = "meshcat_visualizer_test",
data = [
"//manipulation/models/iiwa_description:models",
],
deps = [
":meshcat_visualizer",
"//common/test_utilities:expect_throws_message",
"//multibody/parsing",
"//multibody/plant",
"//systems/analysis:simulator",
"//systems/primitives:constant_vector_source",
],
)

# -----------------------------------------------------

filegroup(
Expand Down
18 changes: 17 additions & 1 deletion geometry/meshcat.cc
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "drake/geometry/meshcat.h"

#include <exception>
#include <fstream>
#include <future>
#include <map>
Expand Down Expand Up @@ -67,6 +68,7 @@ struct PerSocketData {
};
using WebSocket = uWS::WebSocket<kSsl, kIsServer, PerSocketData>;
using MsgPackMap = std::map<std::string, msgpack::object>;
constexpr static double kMaxBackPressure{50 * 1024 * 1024};

class SceneTreeElement {
public:
Expand Down Expand Up @@ -187,7 +189,8 @@ class MeshcatShapeReifier : public ShapeReifier {
public:
DRAKE_NO_COPY_NO_MOVE_NO_ASSIGN(MeshcatShapeReifier);

explicit MeshcatShapeReifier(std::string uuid) : uuid_(std::move(uuid)) {}
explicit MeshcatShapeReifier(std::string uuid)
: uuid_(std::move(uuid)) {}

~MeshcatShapeReifier() = default;

Expand Down Expand Up @@ -225,6 +228,8 @@ class MeshcatShapeReifier : public ShapeReifier {
}

void ImplementGeometry(const HalfSpace&, void*) override {
// TODO(russt): Use PlaneGeometry with fields width, height,
// widthSegments, heightSegments
drake::log()->warn("Meshcat does not display HalfSpace geometry (yet).");
}

Expand Down Expand Up @@ -292,6 +297,12 @@ class MeshcatShapeReifier : public ShapeReifier {
// We simply dump the binary contents of the file into the data field of the
// message. The javascript meshcat takes care of the rest.
int size = input.tellg();
if (size > kMaxBackPressure) {
throw std::runtime_error(fmt::format(
"The meshfile at {} is too large for the current websocket setup. "
"Size {} is greater than the max backpressure {}.",
mesh.filename(), size, kMaxBackPressure));
}
input.seekg(0, std::ios::beg);
geometry.data.resize(size);
input.read(geometry.data.data(), size);
Expand Down Expand Up @@ -553,6 +564,11 @@ class Meshcat::WebSocketPublisher {
// Update this new connection with previously published data.
SendTree(ws);
};
// TODO(russt): I could increase this more if necessary (when it was too
// low, some SetObject messages were dropped). But at some point the real
// fix is to actually throttle the sending (e.g. by slowing down the main
// thread).
behavior.maxBackpressure = kMaxBackPressure;

uWS::App app =
uWS::App()
Expand Down
14 changes: 6 additions & 8 deletions geometry/meshcat.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,6 @@ in the visualizer.
- All user objects can easily be cleared by a single, parameter-free call to
Delete(). You are welcome to use absolute paths to organize your data, but the
burden on tracking and cleaning them up lie on you.
*/
class Meshcat {
public:
Expand All @@ -97,7 +95,7 @@ class Meshcat {
@ref meshcat_path. Any objects previously set at this `path` will be
replaced.
@param path a "/"-delimited string indicating the path in the scene tree.
See @ref meshcat_path for the semantics.
See @ref meshcat_path "Meshcat paths" for the semantics.
@param shape a Shape that specifies the geometry of the object.
@param rgba an Rgba that specifies the (solid) color of the object.
*/
Expand All @@ -111,7 +109,7 @@ class Meshcat {
the transform of "/foo" will move the objects at "/foo/box1" and
"/foo/robots/HAL9000".
@param path a "/"-delimited string indicating the path in the scene tree.
See @ref meshcat_path for the semantics.
See @ref meshcat_path "Meshcat paths" for the semantics.
@param X_ParentPath the relative transform from the path to its immediate
parent.
*/
Expand All @@ -126,8 +124,8 @@ class Meshcat {
@verbatim
meshcat.SetProperty("/Background", "visible", false);
@endverbatim
will turn off the background. See @ref meshcat_path for more details about
these properties and how to address them.
will turn off the background. See @ref meshcat_path "Meshcat paths" for more
details about these properties and how to address them.
@param path a "/"-delimited string indicating the path in the scene tree.
See @ref meshcat_path for the semantics.
Expand All @@ -141,8 +139,8 @@ class Meshcat {
meshcat.SetProperty("/Cameras/default/rotated/<object>", "zoom", 2.0);
meshcat.SetProperty("/Lights/DirectionalLight/<object>", "intensity", 1.0);
@endverbatim
See @ref meshcat_path for more details about these properties and how to
address them.
See @ref meshcat_path "Meshcat paths" for more details about these properties
and how to address them.
@param path a "/"-delimited string indicating the path in the scene tree.
See @ref meshcat_path for the semantics.
Expand Down
178 changes: 178 additions & 0 deletions geometry/meshcat_visualizer.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
#include "drake/geometry/meshcat_visualizer.h"

#include <memory>
#include <string>
#include <utility>

#include <fmt/format.h>

#include "drake/geometry/utilities.h"

namespace drake {
namespace geometry {

template <typename T>
MeshcatVisualizer<T>::MeshcatVisualizer(
Meshcat* meshcat, const std::shared_ptr<Meshcat>& owned_meshcat,
bool scalar_conversion, MeshcatVisualizerParams params)
: systems::LeafSystem<T>(systems::SystemTypeTag<MeshcatVisualizer>{}),
meshcat_(owned_meshcat ? owned_meshcat.get() : meshcat),
owned_meshcat_(owned_meshcat),
params_(std::move(params)) {
if (scalar_conversion && meshcat_ == nullptr) {
throw std::runtime_error(
"MeshcatVisualizer can only be scalar converted if it owns its "
"Meshcat instance. Construct MeshcatVisualizer using the shared_ptr "
"constructor instead.");
}
DRAKE_DEMAND(meshcat_ != nullptr);
DRAKE_DEMAND(params_.publish_period >= 0.0);
if (params_.role == Role::kUnassigned) {
throw std::runtime_error(
"MeshcatVisualizer cannot be used for geometries with the "
"Role::kUnassigned value. Please choose proximity, perception, or "
"illustration");
}

std::cout << "period = " << params_.publish_period << std::endl;
this->DeclarePeriodicPublishEvent(params_.publish_period, 0.0,
&MeshcatVisualizer<T>::UpdateMeshcat);
this->DeclareForcedPublishEvent(&MeshcatVisualizer<T>::UpdateMeshcat);

if (params_.delete_prefix_on_initialization_event) {
this->DeclareInitializationPublishEvent(
&MeshcatVisualizer<T>::DeletePrefix);
}

query_object_input_port_ =
this->DeclareAbstractInputPort("query_object", Value<QueryObject<T>>())
.get_index();
}

template <typename T>
MeshcatVisualizer<T>::MeshcatVisualizer(Meshcat* meshcat,
MeshcatVisualizerParams params)
: MeshcatVisualizer(meshcat, nullptr, false, std::move(params)) {}

template <typename T>
MeshcatVisualizer<T>::MeshcatVisualizer(const std::shared_ptr<Meshcat>& meshcat,
MeshcatVisualizerParams params)
: MeshcatVisualizer(nullptr, meshcat, false, std::move(params)) {}

template <typename T>
template <typename U>
MeshcatVisualizer<T>::MeshcatVisualizer(const MeshcatVisualizer<U>& other)
: MeshcatVisualizer(nullptr, other.owned_meshcat_, true, other.params_) {}

template <typename T>
void MeshcatVisualizer<T>::DeletePrefix() const {
meshcat_->Delete(params_.prefix);
version_ = GeometryVersion();
}

template <typename T>
const MeshcatVisualizer<T>& MeshcatVisualizer<T>::AddToBuilder(
systems::DiagramBuilder<T>* builder, const SceneGraph<T>& scene_graph,
Meshcat* meshcat, MeshcatVisualizerParams params) {
return AddToBuilder(builder, scene_graph.get_query_output_port(), meshcat,
std::move(params));
}

template <typename T>
const MeshcatVisualizer<T>& MeshcatVisualizer<T>::AddToBuilder(
systems::DiagramBuilder<T>* builder,
const systems::OutputPort<T>& query_object_port, Meshcat* meshcat,
MeshcatVisualizerParams params) {
auto& visualizer = *builder->template AddSystem<MeshcatVisualizer<T>>(
meshcat, std::move(params));
builder->Connect(query_object_port, visualizer.query_object_input_port());
return visualizer;
}

template <typename T>
systems::EventStatus MeshcatVisualizer<T>::UpdateMeshcat(
const systems::Context<T>& context) const {
const auto& query_object =
query_object_input_port().template Eval<QueryObject<T>>(context);
const GeometryVersion& current_version =
query_object.inspector().geometry_version();

bool set_objects = false;
if (!version_.IsSameAs(current_version, params_.role)) {
set_objects = true;
version_ = current_version;
}

if (set_objects) {
SetObjects(query_object.inspector());
}
SetTransforms(query_object);

return systems::EventStatus::Succeeded();
}

template <typename T>
void MeshcatVisualizer<T>::SetObjects(
const SceneGraphInspector<T>& inspector) const {
// Recreate the dynamic frames from scratch in case existing frames have been
// removed.
dynamic_frames_.clear();

for (FrameId frame_id : inspector.GetAllFrameIds()) {
std::string frame_path =
frame_id == inspector.world_frame_id()
? params_.prefix
: fmt::format("{}/{}", params_.prefix, inspector.GetName(frame_id));
// MultibodyPlant declares frames with SceneGraph using "::". We replace
// those with `/` here to expose the full tree to Meshcat.
size_t pos = 0;
while ((pos = frame_path.find("::", pos)) != std::string::npos) {
frame_path.replace(pos++, 2, "/");
}
if (frame_id != inspector.world_frame_id() &&
inspector.NumGeometriesForFrameWithRole(frame_id, params_.role) > 0) {
dynamic_frames_[frame_id] = frame_path;
}

for (GeometryId geom_id : inspector.GetGeometries(frame_id, params_.role)) {
// Note: We use the frame_path/id instead of instance.GetName(geom_id),
// which is a garbled mess of :: and _ and a memory address by default
// when coming from MultibodyPlant.
const std::string path =
fmt::format("{}/{}", frame_path, geom_id.get_value());
Rgba rgba = params_.default_color;
if (params_.role == Role::kIllustration) {
const IllustrationProperties* props =
inspector.GetIllustrationProperties(geom_id);
if (props && props->HasProperty("phong", "diffuse")) {
rgba = props->GetProperty<Rgba>("phong", "diffuse");
}
}

meshcat_->SetObject(path, inspector.GetShape(geom_id), rgba);
meshcat_->SetTransform(path, inspector.GetPoseInFrame(geom_id));
}
}
}

template <typename T>
void MeshcatVisualizer<T>::SetTransforms(
const QueryObject<T>& query_object) const {
for (const auto& [frame_id, path] : dynamic_frames_) {
meshcat_->SetTransform(path, internal::convert_to_double(
query_object.GetPoseInWorld(frame_id)));
}
}

template <typename T>
systems::EventStatus MeshcatVisualizer<T>::DeletePrefix(
const systems::Context<T>&) const {
DeletePrefix();
return systems::EventStatus::Succeeded();
}

} // namespace geometry
} // namespace drake

DRAKE_DEFINE_CLASS_TEMPLATE_INSTANTIATIONS_ON_DEFAULT_NONSYMBOLIC_SCALARS(
class ::drake::geometry::MeshcatVisualizer)
Loading

0 comments on commit 7b6cc4c

Please sign in to comment.