Skip to content

Commit a2d540b

Browse files
committed
Add ResolveConstraints logic to JointSliders (=> ModelVisualizer)
Updates JointSliders to have discrete state (previously it was stateless). Additionally, if the new ResolveConstraints logic is active, we publish the (rounded) resolved constraints back to Meshcat, so that the other sliders are updated. Since ModelVisualizer uses JointSliders, now ModelVisualizer also satisfies the multibody constraints. Resolves #18917. Deprecates `JointSliders::SetPositions(q)`, which has been replaced with `JointSliders::SetPositions(context, q)`.
1 parent 73be646 commit a2d540b

File tree

10 files changed

+549
-82
lines changed

10 files changed

+549
-82
lines changed

bindings/pydrake/multibody/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ drake_pybind_library(
167167
"//bindings/pydrake:documentation_pybind",
168168
"//bindings/pydrake/common:cpp_template_pybind",
169169
"//bindings/pydrake/common:default_scalars_pybind",
170+
"//bindings/pydrake/common:deprecation_pybind",
170171
"//bindings/pydrake/common:serialize_pybind",
171172
"//bindings/pydrake/common:type_pack",
172173
],
@@ -446,6 +447,7 @@ drake_py_unittest(
446447
deps = [
447448
":meshcat_py",
448449
":parsing_py",
450+
"//bindings/pydrake/common/test_utilities:deprecation_py",
449451
"//bindings/pydrake/common/test_utilities:numpy_compare_py",
450452
],
451453
)

bindings/pydrake/multibody/meshcat_py.cc

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "drake/bindings/pydrake/common/cpp_template_pybind.h"
22
#include "drake/bindings/pydrake/common/default_scalars_pybind.h"
3+
#include "drake/bindings/pydrake/common/deprecation_pybind.h"
34
#include "drake/bindings/pydrake/common/serialize_pybind.h"
45
#include "drake/bindings/pydrake/common/type_pack.h"
56
#include "drake/bindings/pydrake/documentation_pybind.h"
@@ -167,20 +168,22 @@ void DoScalarDependentDefinitions(py::module m, T) {
167168
{
168169
using Class = JointSliders<T>;
169170
constexpr auto& cls_doc = doc.JointSliders;
170-
DefineTemplateClassWithDefault<JointSliders<T>, LeafSystem<T>>(
171-
m, "JointSliders", param, doc.JointSliders.doc)
171+
auto cls = DefineTemplateClassWithDefault<JointSliders<T>, LeafSystem<T>>(
172+
m, "JointSliders", param, doc.JointSliders.doc);
173+
cls // BR
172174
.def(py::init<std::shared_ptr<geometry::Meshcat>,
173175
const MultibodyPlant<T>*, std::optional<Eigen::VectorXd>,
174176
std::variant<std::monostate, double, Eigen::VectorXd>,
175177
std::variant<std::monostate, double, Eigen::VectorXd>,
176178
std::variant<std::monostate, double, Eigen::VectorXd>,
177-
std::vector<std::string>, std::vector<std::string>>(),
179+
std::vector<std::string>, std::vector<std::string>, double>(),
178180
py::arg("meshcat"), py::arg("plant"),
179181
py::arg("initial_value") = py::none(),
180182
py::arg("lower_limit") = py::none(),
181183
py::arg("upper_limit") = py::none(), py::arg("step") = py::none(),
182184
py::arg("decrement_keycodes") = std::vector<std::string>(),
183185
py::arg("increment_keycodes") = std::vector<std::string>(),
186+
py::arg("time_step") = 1.0 / 32.0,
184187
// Keep alive, reference: `self` keeps `plant` alive.
185188
py::keep_alive<1, 3>(), // BR
186189
cls_doc.ctor.doc)
@@ -191,8 +194,18 @@ void DoScalarDependentDefinitions(py::module m, T) {
191194
// This is a long-running function that sleeps; for both reasons, we
192195
// must release the GIL.
193196
py::call_guard<py::gil_scoped_release>(), cls_doc.Run.doc)
194-
.def("SetPositions", &Class::SetPositions, py::arg("q"),
195-
cls_doc.SetPositions.doc);
197+
.def("SetPositions",
198+
py::overload_cast<systems::Context<T>*, const Eigen::VectorXd&>(
199+
&Class::SetPositions),
200+
py::arg("context"), py::arg("q"), cls_doc.SetPositions.doc);
201+
202+
#pragma GCC diagnostic push
203+
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
204+
cls.def("SetPositions",
205+
WrapDeprecated(doc.JointSliders.SetPositions.doc_deprecated,
206+
py::overload_cast<const Eigen::VectorXd&>(&Class::SetPositions)),
207+
py::arg("q"), doc.JointSliders.SetPositions.doc_deprecated);
208+
#pragma GCC diagnostic pop
196209
}
197210
}
198211
} // namespace

bindings/pydrake/multibody/test/meshcat_test.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from pydrake.common.test_utilities import (
1818
numpy_compare,
1919
)
20+
from pydrake.common.test_utilities.deprecation import catch_drake_warnings
2021
from pydrake.geometry import (
2122
Meshcat,
2223
Rgba,
@@ -93,6 +94,7 @@ def test_joint_sliders(self):
9394
step=[0.25, 0.50],
9495
decrement_keycodes=["ArrowLeft", "ArrowDown"],
9596
increment_keycodes=["ArrowRight", "ArrowUp"],
97+
time_step=0.01,
9698
)
9799

98100
# Various methods should not crash.
@@ -134,7 +136,11 @@ def test_joint_sliders(self):
134136
np.testing.assert_equal(q, [0, 0])
135137

136138
# The SetPositions function doesn't crash (Acrobot has two positions).
137-
dut.SetPositions(q=[1, 2])
139+
context = dut.CreateDefaultContext()
140+
dut.SetPositions(context=context, q=[1, 2])
141+
with catch_drake_warnings(expected_count=1) as w:
142+
dut.SetPositions(q=[1, 2])
143+
self.assertIn("SetPositions(context", str(w[0].message))
138144

139145
def test_internal_point_contact_visualizer(self):
140146
"""A very basic existence test, since this class is internal use only.

bindings/pydrake/visualization/_model_visualizer.py

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,8 @@ def Finalize(self, position=None):
406406
self._diagram.plant().SetPositions(
407407
self._diagram.plant().GetMyContextFromRoot(self._context),
408408
position)
409-
self._sliders.SetPositions(position)
409+
self._sliders.SetPositions(
410+
self._sliders.GetMyContextFromRoot(self._context), position)
410411

411412
# Use Simulator to dispatch initialization events.
412413
# TODO(eric.cousineau): Simplify as part of #13776 (was #10015).
@@ -523,9 +524,15 @@ def Run(self, position=None, loop_once=False):
523524
self._diagram.plant().SetPositions(
524525
self._diagram.plant().GetMyContextFromRoot(self._context),
525526
position)
526-
self._sliders.SetPositions(position)
527+
self._sliders.SetPositions(
528+
self._sliders.GetMyContextFromRoot(self._context),
529+
position)
527530
self._diagram.ForcedPublish(self._context)
528531

532+
sliders_context = self._sliders.GetMyContextFromRoot(self._context)
533+
plant_context = self._diagram.plant().GetMyContextFromRoot(
534+
self._context)
535+
529536
# Everything is finally fully configured. We can open the window now.
530537
# TODO(jwnimmer-tri) The browser_new config knob would probably make
531538
# more sense as an argument to Run() vs an argument to our constructor.
@@ -564,13 +571,17 @@ def has_clicks(button_name):
564571
self._meshcat.DeleteButton(stop_button_name)
565572
slider_values = self._get_slider_values()
566573
self._reload()
574+
sliders_context = self._sliders.GetMyContextFromRoot(
575+
self._context)
576+
plant_context = self._diagram.plant().GetMyContextFromRoot(
577+
self._context)
567578
self._set_slider_values(slider_values)
568579
self._meshcat.AddButton(stop_button_name, "Escape")
569-
q = self._sliders.get_output_port().Eval(
570-
self._sliders.GetMyContextFromRoot(self._context))
571-
self._diagram.plant().SetPositions(
572-
self._diagram.plant().GetMyContextFromRoot(self._context),
573-
q)
580+
updated = self._sliders.EvalUniquePeriodicDiscreteUpdate(
581+
sliders_context)
582+
sliders_context.SetDiscreteState(updated)
583+
q = self._sliders.get_output_port().Eval(sliders_context)
584+
self._diagram.plant().SetPositions(plant_context, q)
574585
self._diagram.ForcedPublish(self._context)
575586
if loop_once or has_clicks(stop_button_name):
576587
break

multibody/inverse_kinematics/add_multibody_plant_constraints.h

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,12 @@ namespace multibody {
1111

1212
/** For all kinematic constraints associated with `plant` adds a corresponding
1313
solver::Constraint to `prog`, using decision variables `q` to represent the
14-
generalized positions of the plant.
14+
generalized positions of the plant. The `plant` must stay alive for the
15+
lifetime of the added constraints.
1516
16-
Adds joint limits constraints, unit quaternion constraints, and constraints for
17-
any locked joints (via Joint::Lock()). Note that you must pass a valid
18-
`plant_context` to use joint locking.
17+
Adds joint limits constraints (always), unit quaternion constraints, and
18+
constraints for any locked joints (via Joint::Lock()). Note that you must pass
19+
a valid `plant_context` to use joint locking.
1920
2021
Adds constraints for coupler, distance, ball, and weld constraints. The
2122
distance constraint is implemented here as a hard kinematic constraint (i.e.,

multibody/inverse_kinematics/inverse_kinematics.h

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,12 +71,12 @@ class InverseKinematics {
7171
* auto plant_context = &(diagram->GetMutableSubsystemContext(items.plant,
7272
* diagram_context.get()));
7373
* ```
74-
* This context will be modified during calling ik.prog.Solve(...). When
75-
* Solve() returns `result`, context will store the optimized posture, namely
76-
* plant.GetPositions(*context) will be the same as in
77-
* result.GetSolution(ik.q()). The user could then use this context to perform
78-
* kinematic computation (like computing the position of the end-effector
79-
* etc.).
74+
* This context will be modified during calling solvers::Solve(ik.prog, ...).
75+
* When Solve() returns `result`, context will store the optimized posture,
76+
* namely plant.GetPositions(*context) will be the same as in
77+
* result.GetSolution(ik.q()). The user could then use this context to
78+
* perform kinematic computation (like computing the position of the
79+
* end-effector etc.).
8080
* @param with_joint_limits If set to true, then the constructor imposes the
8181
* joint limits (obtained from plant.GetPositionLowerLimits() and
8282
* plant.GetPositionUpperLimits(), and from any body/joint locks set in

multibody/meshcat/BUILD.bazel

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,10 @@ drake_cc_library(
8888
"//common:scope_exit",
8989
"//geometry:meshcat",
9090
"//geometry:meshcat_graphviz",
91+
"//multibody/inverse_kinematics:add_multibody_plant_constraints",
9192
"//multibody/plant",
93+
"//solvers:choose_best_solver",
94+
"//solvers:solve",
9295
],
9396
)
9497

0 commit comments

Comments
 (0)