diff --git a/bindings/pydrake/geometry_py.cc b/bindings/pydrake/geometry_py.cc index f052c04edd19..007dae89555d 100644 --- a/bindings/pydrake/geometry_py.cc +++ b/bindings/pydrake/geometry_py.cc @@ -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" @@ -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; + 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>( + m, "MeshcatVisualizerCpp", param, cls_doc.doc); + cls // BR + .def(py::init, 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*, const SceneGraph&, + std::shared_ptr, MeshcatVisualizerParams>( + &MeshcatVisualizer::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*, + const systems::OutputPort&, std::shared_ptr, + MeshcatVisualizerParams>(&MeshcatVisualizer::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) { @@ -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_> 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( + &Class::SetProperty), + py::arg("path"), py::arg("property"), py::arg("value"), + cls_doc.SetProperty.doc_bool) + .def("SetProperty", + py::overload_cast( + &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_( + m, "MeshcatVisualizerParams", py::dynamic_attr(), cls_doc.doc) + .def(ParamInit()) + .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) { diff --git a/bindings/pydrake/systems/test/meshcat_visualizer_test.py b/bindings/pydrake/systems/test/meshcat_visualizer_test.py index cb9525f9c932..5498ca04c89c 100644 --- a/bindings/pydrake/systems/test/meshcat_visualizer_test.py +++ b/bindings/pydrake/systems/test/meshcat_visualizer_test.py @@ -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() diff --git a/bindings/pydrake/test/geometry_test.py b/bindings/pydrake/test/geometry_test.py index 4ca1abf69d3b..97600800575b 100644 --- a/bindings/pydrake/test/geometry_test.py +++ b/bindings/pydrake/test/geometry_test.py @@ -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 @@ -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/", + 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] diff --git a/geometry/meshcat.h b/geometry/meshcat.h index 86d31ab86c52..4f8bffcd4a5f 100644 --- a/geometry/meshcat.h +++ b/geometry/meshcat.h @@ -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); @@ -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); diff --git a/geometry/meshcat_visualizer.cc b/geometry/meshcat_visualizer.cc index 0815b6d380a8..ebb5a89a7d6c 100644 --- a/geometry/meshcat_visualizer.cc +++ b/geometry/meshcat_visualizer.cc @@ -30,7 +30,7 @@ MeshcatVisualizer::MeshcatVisualizer(std::shared_ptr meshcat, &MeshcatVisualizer::UpdateMeshcat); this->DeclareForcedPublishEvent(&MeshcatVisualizer::UpdateMeshcat); - if (params_.delete_prefix_on_initialization_event) { + if (params_.delete_on_intialization_event) { this->DeclareInitializationPublishEvent( &MeshcatVisualizer::OnInitialization); } diff --git a/geometry/meshcat_visualizer.h b/geometry/meshcat_visualizer.h index d2d25d65b10d..e02e09b8e5dd 100644 --- a/geometry/meshcat_visualizer.h +++ b/geometry/meshcat_visualizer.h @@ -66,7 +66,7 @@ class MeshcatVisualizer final : public systems::LeafSystem { 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; diff --git a/geometry/meshcat_visualizer_params.h b/geometry/meshcat_visualizer_params.h index a34bbd7ca2d5..79365da3f5ff 100644 --- a/geometry/meshcat_visualizer_params.h +++ b/geometry/meshcat_visualizer_params.h @@ -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 diff --git a/geometry/test/meshcat_manual_test.cc b/geometry/test/meshcat_manual_test.cc index 4ec57663f2a4..f94ee57519b8 100644 --- a/geometry/test/meshcat_manual_test.cc +++ b/geometry/test/meshcat_manual_test.cc @@ -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(); diff --git a/geometry/test/meshcat_visualizer_test.cc b/geometry/test/meshcat_visualizer_test.cc index d2d1d82c9d45..4a3e1b584496 100644 --- a/geometry/test/meshcat_visualizer_test.cc +++ b/geometry/test/meshcat_visualizer_test.cc @@ -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", @@ -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());