Skip to content

Commit

Permalink
[geometry] Add python bindings for Meshcat/MeshcatVisualizerCpp
Browse files Browse the repository at this point in the history
Follow-up to RobotLocomotion#13038.
Also makes the meshcat_visualizer_test.py more robust (test_warnings_and_errors could fail if the default zmq_url was already consumed)
  • Loading branch information
RussTedrake committed Aug 30, 2021
1 parent 705d6da commit c3a15e0
Show file tree
Hide file tree
Showing 9 changed files with 159 additions and 12 deletions.
92 changes: 92 additions & 0 deletions bindings/pydrake/geometry_py.cc
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
#include "drake/geometry/geometry_instance.h"
#include "drake/geometry/geometry_properties.h"
#include "drake/geometry/geometry_roles.h"
#include "drake/geometry/meshcat.h"
#include "drake/geometry/meshcat_visualizer.h"
#include "drake/geometry/optimization/hpolyhedron.h"
#include "drake/geometry/optimization/hyperellipsoid.h"
#include "drake/geometry/optimization/iris.h"
Expand Down Expand Up @@ -952,6 +954,48 @@ void DoScalarDependentDefinitions(py::module m, T) {
py::arg("lcm"), py::arg("params") = DrakeVisualizerParams{},
cls_doc.DispatchLoadMessage.doc);
}

// MeshcatVisualizer
{
using Class = MeshcatVisualizer<T>;
constexpr auto& cls_doc = doc.MeshcatVisualizer;
// Note that we are temporarily re-mapping MeshcatVisualizer =>
// MeshcatVisualizerCpp to avoid collisions with the python
// MeshcatVisualizer. See #13038.
auto cls = DefineTemplateClassWithDefault<Class, LeafSystem<T>>(
m, "MeshcatVisualizerCpp", param, cls_doc.doc);
cls // BR
.def(py::init<std::shared_ptr<Meshcat>, MeshcatVisualizerParams>(),
py::arg("meshcat"), py::arg("params") = MeshcatVisualizerParams{},
// `meshcat` is a shared_ptr, so does not need a keep_alive.
cls_doc.ctor.doc)
.def("Delete", &Class::Delete, cls_doc.Delete.doc)
.def("query_object_input_port", &Class::query_object_input_port,
py_rvp::reference_internal, cls_doc.query_object_input_port.doc)
.def_static("AddToBuilder",
py::overload_cast<systems::DiagramBuilder<T>*, const SceneGraph<T>&,
std::shared_ptr<Meshcat>, MeshcatVisualizerParams>(
&MeshcatVisualizer<T>::AddToBuilder),
py::arg("builder"), py::arg("scene_graph"), py::arg("meshcat"),
py::arg("params") = MeshcatVisualizerParams{},
// Keep alive, ownership: `return` keeps `builder` alive.
py::keep_alive<0, 1>(),
// `meshcat` is a shared_ptr, so does not need a keep_alive.
py_rvp::reference,
cls_doc.AddToBuilder.doc_4args_builder_scene_graph_meshcat_params)
.def_static("AddToBuilder",
py::overload_cast<systems::DiagramBuilder<T>*,
const systems::OutputPort<T>&, std::shared_ptr<Meshcat>,
MeshcatVisualizerParams>(&MeshcatVisualizer<T>::AddToBuilder),
py::arg("builder"), py::arg("query_object_port"),
py::arg("meshcat"), py::arg("params") = MeshcatVisualizerParams{},
// Keep alive, ownership: `return` keeps `builder` alive.
py::keep_alive<0, 1>(),
// `meshcat` is a shared_ptr, so does not need a keep_alive.
py_rvp::reference,
cls_doc.AddToBuilder
.doc_4args_builder_query_object_port_meshcat_params);
}
} // NOLINT(readability/fn_size)

void DoScalarIndependentDefinitions(py::module m) {
Expand Down Expand Up @@ -1359,6 +1403,54 @@ void DoScalarIndependentDefinitions(py::module m) {
py::arg("filename"), py::arg("scale") = 1.0,
// N.B. We have not bound the optional "on_warning" argument.
doc.ReadObjToSurfaceMesh.doc_3args_filename_scale_on_warning);

// Meshcat
{
using Class = Meshcat;
constexpr auto& cls_doc = doc.Meshcat;
py::class_<Class, std::shared_ptr<Class>> cls(m, "Meshcat", cls_doc.doc);
cls // BR
.def(py::init<>(), cls_doc.ctor.doc)
.def("web_url", &Class::web_url, cls_doc.web_url.doc)
.def("ws_url", &Class::ws_url, cls_doc.ws_url.doc)
.def("SetObject", &Class::SetObject, py::arg("path"), py::arg("shape"),
py::arg("rgba") = Rgba(.9, .9, .9, 1.), cls_doc.SetObject.doc)
.def("SetTransform", &Class::SetTransform, py::arg("path"),
py::arg("X_ParentPath"), cls_doc.SetTransform.doc)
.def("Delete", &Class::Delete, py::arg("path") = "", cls_doc.Delete.doc)
.def("SetProperty",
py::overload_cast<std::string_view, std::string, bool>(
&Class::SetProperty),
py::arg("path"), py::arg("property"), py::arg("value"),
cls_doc.SetProperty.doc_bool)
.def("SetProperty",
py::overload_cast<std::string_view, std::string, double>(
&Class::SetProperty),
py::arg("path"), py::arg("property"), py::arg("value"),
cls_doc.SetProperty.doc_double);
// Note: we intentionally do not bind the advanced methods (HasProperty and
// GetPacked*) which were intended primarily for testing in C++.
}

// MeshcatVisualizerParams
{
using Class = MeshcatVisualizerParams;
constexpr auto& cls_doc = doc.MeshcatVisualizerParams;
py::class_<Class>(
m, "MeshcatVisualizerParams", py::dynamic_attr(), cls_doc.doc)
.def(ParamInit<Class>())
.def_readwrite("publish_period",
&MeshcatVisualizerParams::publish_period,
cls_doc.publish_period.doc)
.def_readwrite("role", &MeshcatVisualizerParams::role, cls_doc.role.doc)
.def_readwrite("default_color", &MeshcatVisualizerParams::default_color,
cls_doc.default_color.doc)
.def_readwrite(
"prefix", &MeshcatVisualizerParams::prefix, cls_doc.prefix.doc)
.def_readwrite("delete_on_intialization_event",
&MeshcatVisualizerParams::delete_on_intialization_event,
cls_doc.delete_on_intialization_event.doc);
}
}

void def_geometry(py::module m) {
Expand Down
16 changes: 10 additions & 6 deletions bindings/pydrake/systems/test/meshcat_visualizer_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -468,30 +468,34 @@ def test_warnings_and_errors(self):
builder = DiagramBuilder()
sg = builder.AddSystem(SceneGraph())

v2 = builder.AddSystem(MeshcatVisualizer(scene_graph=sg))
v2 = builder.AddSystem(
MeshcatVisualizer(scene_graph=sg, zmq_url=ZMQ_URL))
builder.Connect(sg.get_query_output_port(),
v2.get_geometry_query_input_port())
v2.set_name("v2")

v4 = builder.AddSystem(MeshcatVisualizer(scene_graph=None))
v4 = builder.AddSystem(
MeshcatVisualizer(scene_graph=None, zmq_url=ZMQ_URL))
builder.Connect(sg.get_query_output_port(),
v4.get_geometry_query_input_port())
v4.set_name("v4")

v5 = ConnectMeshcatVisualizer(builder, scene_graph=sg)
v5 = ConnectMeshcatVisualizer(builder, scene_graph=sg, zmq_url=ZMQ_URL)
v5.set_name("v5")

v7 = ConnectMeshcatVisualizer(
builder, scene_graph=sg, output_port=sg.get_query_output_port())
builder, scene_graph=sg, output_port=sg.get_query_output_port(),
zmq_url=ZMQ_URL)
v7.set_name("v7")

with self.assertRaises(AssertionError):
v8 = ConnectMeshcatVisualizer(builder, scene_graph=None,
output_port=None)
output_port=None, zmq_url=ZMQ_URL)
v8.set_name("v8")

v10 = ConnectMeshcatVisualizer(
builder, scene_graph=None, output_port=sg.get_query_output_port())
builder, scene_graph=None, output_port=sg.get_query_output_port(),
zmq_url=ZMQ_URL)
v10.set_name("v10")

diagram = builder.Build()
Expand Down
47 changes: 47 additions & 0 deletions bindings/pydrake/test/geometry_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import numpy as np

from drake import lcmt_viewer_load_robot, lcmt_viewer_draw
from pydrake.autodiffutils import AutoDiffXd
from pydrake.common import FindResourceOrThrow
from pydrake.common.test_utilities import numpy_compare
from pydrake.common.test_utilities.deprecation import catch_drake_warnings
Expand Down Expand Up @@ -364,6 +365,52 @@ def auto_connect_to_port(builder, scene_graph, params):
load_subscriber.clear()
draw_subscriber.clear()

def test_meshcat(self):
meshcat = mut.Meshcat()
self.assertIn("http", meshcat.web_url())
self.assertIn("ws", meshcat.ws_url())
meshcat.SetObject(path="/test/box",
shape=mut.Box(1, 1, 1),
rgba=mut.Rgba(.5, .5, .5))
meshcat.SetTransform(path="/test/box", X_ParentPath=RigidTransform())
meshcat.SetProperty(path="/Background",
property="visible",
value=True)
meshcat.SetProperty(path="/Lights/DirectionalLight/<object>",
property="intensity", value=1.0)

@numpy_compare.check_nonsymbolic_types
def test_meshcat_visualizer(self, T):
meshcat = mut.Meshcat()
params = mut.MeshcatVisualizerParams()
params.publish_period = 0.123
params.role = mut.Role.kIllustration
params.default_color = mut.Rgba(0.5, 0.5, 0.5)
params.prefix = "py_visualizer"
params.delete_on_intialization_event = False
vis = mut.MeshcatVisualizerCpp_[T](meshcat=meshcat, params=params)
vis.Delete()
self.assertIsInstance(vis.query_object_input_port(), InputPort_[T])

builder = DiagramBuilder_[T]()
scene_graph = builder.AddSystem(mut.SceneGraph_[T]())
mut.MeshcatVisualizerCpp_[T].AddToBuilder(builder=builder,
scene_graph=scene_graph,
meshcat=meshcat,
params=params)
mut.MeshcatVisualizerCpp_[T].AddToBuilder(
builder=builder,
query_object_port=scene_graph.get_query_output_port(),
meshcat=meshcat,
params=params)

def test_meshcat_visualizer_scalar_conversion(self):
meshcat = mut.Meshcat()
vis = mut.MeshcatVisualizerCpp(meshcat)
vis_autodiff = vis.ToAutoDiffXd()
self.assertIsInstance(vis_autodiff,
mut.MeshcatVisualizerCpp_[AutoDiffXd])

@numpy_compare.check_nonsymbolic_types
def test_frame_pose_vector_api(self, T):
FramePoseVector = mut.FramePoseVector_[T]
Expand Down
4 changes: 4 additions & 0 deletions geometry/meshcat.h
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ class Meshcat {
See @ref meshcat_path for the semantics.
@param property the string name of the property to set
@param value the new value.
@pydrake_mkdoc_identifier{bool}
*/
void SetProperty(std::string_view path, std::string property, bool value);

Expand All @@ -146,6 +148,8 @@ class Meshcat {
See @ref meshcat_path for the semantics.
@param property the string name of the property to set
@param value the new value.
@pydrake_mkdoc_identifier{double}
*/
void SetProperty(std::string_view path, std::string property, double value);

Expand Down
2 changes: 1 addition & 1 deletion geometry/meshcat_visualizer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ MeshcatVisualizer<T>::MeshcatVisualizer(std::shared_ptr<Meshcat> meshcat,
&MeshcatVisualizer<T>::UpdateMeshcat);
this->DeclareForcedPublishEvent(&MeshcatVisualizer<T>::UpdateMeshcat);

if (params_.delete_prefix_on_initialization_event) {
if (params_.delete_on_intialization_event) {
this->DeclareInitializationPublishEvent(
&MeshcatVisualizer<T>::OnInitialization);
}
Expand Down
2 changes: 1 addition & 1 deletion geometry/meshcat_visualizer.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ class MeshcatVisualizer final : public systems::LeafSystem<T> {
MeshcatVisualizerParams::prefix. Since this visualizer will only ever add
geometry under this prefix, this will remove all geometry/transforms added
by the visualizer, or by a previous instance of this visualizer using the
same prefix. Use MeshcatVisualizer::delete_prefix_on_initialization_event
same prefix. Use MeshcatVisualizer::delete_on_intialization_event
to determine whether this should be called on initialization. */
void Delete() const;

Expand Down
2 changes: 1 addition & 1 deletion geometry/meshcat_visualizer_params.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ struct MeshcatVisualizerParams {
initialization event to remove any visualizations e.g. from a previous
simulation. See @ref declare_initialization_events "Declare initialization
events" for more information. */
bool delete_prefix_on_initialization_event{true};
bool delete_on_intialization_event{true};
};

} // namespace geometry
Expand Down
2 changes: 1 addition & 1 deletion geometry/test/meshcat_manual_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ Open up your browser to the URL above.

builder.ExportInput(plant.get_actuation_input_port(), "actuation_input");
MeshcatVisualizerParams params;
params.delete_prefix_on_initialization_event = false;
params.delete_on_intialization_event = false;
MeshcatVisualizerd::AddToBuilder(&builder, scene_graph, meshcat, params);

auto diagram = builder.Build();
Expand Down
4 changes: 2 additions & 2 deletions geometry/test/meshcat_visualizer_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ TEST_F(MeshcatVisualizerWithIiwaTest, Prefix) {

TEST_F(MeshcatVisualizerWithIiwaTest, DeletePrefixOnInitialization) {
MeshcatVisualizerParams params;
params.delete_prefix_on_initialization_event = true;
params.delete_on_intialization_event = true;
SetUpDiagram(params);
// Scribble a transform onto the scene tree beneath the visualizer prefix.
meshcat_->SetTransform("/drake/visualizer/my_random_path",
Expand All @@ -163,7 +163,7 @@ TEST_F(MeshcatVisualizerWithIiwaTest, DeletePrefixOnInitialization) {
EXPECT_FALSE(meshcat_->HasPath("/drake/visualizer/my_random_path"));

// Repeat, but this time with delete prefix disabled.
params.delete_prefix_on_initialization_event = false;
params.delete_on_intialization_event = false;
SetUpDiagram(params);
meshcat_->SetTransform("/drake/visualizer/my_random_path",
math::RigidTransformd());
Expand Down

0 comments on commit c3a15e0

Please sign in to comment.