Skip to content

[multibody] Add surface velocity to MultibodyPlant#24725

Open
SeanCurtis-TRI wants to merge 2 commits into
RobotLocomotion:masterfrom
SeanCurtis-TRI:PR_mbp_surface_velocity
Open

[multibody] Add surface velocity to MultibodyPlant#24725
SeanCurtis-TRI wants to merge 2 commits into
RobotLocomotion:masterfrom
SeanCurtis-TRI:PR_mbp_surface_velocity

Conversation

@SeanCurtis-TRI

@SeanCurtis-TRI SeanCurtis-TRI commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

This includes:

  • The APIs on MultibodyPlant to declare and introspect surface velocities.
  • The requisite ports.
  • The parsing infrastructure to declare surface velocity in the model files.

It does not actually make use of the surface velocity in contact yet.

Relates #19599.


This change is Reviewable

This includes:
 - The APIs on MultibodyPlant to declare and introspect surface velocities.
 - The requisite ports.
 - The parsing infrastructure to declare surface velocity in the model
   files.

It does *not* actually make use of the surface velocity in contact yet.
@SeanCurtis-TRI SeanCurtis-TRI added the release notes: none This pull request should not be mentioned in the release notes label Jul 14, 2026
@SeanCurtis-TRI

Copy link
Copy Markdown
Contributor Author

+(release notes: none) +a:@jwnimmer-tri for feature review please

@SeanCurtis-TRI SeanCurtis-TRI left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: I've tagged this as none. I'm open to changing it to feature although it won't be meaningful until the next two PRs merge (implementing the contact responses).

@jwnimmer-tri I particularly undid something you suggested in MbP::SetDefaultState() in the previous PR. Look there and in SetRandomState(). Generally, there are some system level weirdnesses due to MbP's overriding of LeafSystem implementations.

@SeanCurtis-TRI made 1 comment.
Reviewable status: LGTM missing from assignee jwnimmer-tri(platform), needs at least two assigned reviewers (waiting on jwnimmer-tri).

@jwnimmer-tri jwnimmer-tri left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First pass complete. This seems like a reasonable slice of the feature branch.

I particularly undid something you suggested in MbP::SetDefaultState() in the previous PR. Look there and in SetRandomState(). Generally, there are some system level weirdnesses due to MbP's overriding of LeafSystem implementations.

See discussions inline. I agree that we don't need to support a random distribution of initial displacement conditions, so the only question is the most maintainable way to implement what we need.

@jwnimmer-tri reviewed 13 files and all commit messages, and made 50 comments.
Reviewable status: 49 unresolved discussions, LGTM missing from assignee jwnimmer-tri(platform), needs at least two assigned reviewers (waiting on SeanCurtis-TRI).


multibody/plant/multibody_plant.h line 289 at r1 (raw file):

surface_displacements

In various places this port name is plural (displacements) vs not (displacement). Everywhere should use the same spelling.

(Given that the input is uniformly plural, I imagine the goal is to use plural everywhere for output, too.)


multibody/parsing/detail_sdf_parser.cc line 1392 at r1 (raw file):

  const std::set<std::string> supported_link_elements{
      "drake:surface_velocity_axis",

The custom SDFormat and URDF elements need to be documented in parsing_doxygen.h.


multibody/parsing/detail_sdf_parser.cc line 1505 at r1 (raw file):

  // Parse link-level surface velocity axis (if present) and register.
  if (link_element->HasElement("drake:surface_velocity_axis")) {
    const Vector3d a = ToVector3(

nit It's not really defective since I was able to understand it basically immediately, but I do think we can afford to spell it out for absolute clarity.

Suggestion:

axis

multibody/parsing/detail_urdf_parser.cc line 262 at r1 (raw file):

  // Parse link-level surface velocity axis (if present) and register.
  // World body is excluded since it cannot have surface velocity.

If the user sets a surface axis on the world in the XML file, silently doing nothing is not a great outcome. IMO letting the plant throw is superior to that, but of course we could also use the Error() reporting for it, too.


multibody/parsing/detail_urdf_parser.cc line 267 at r1 (raw file):

        node->FirstChildElement("drake:surface_velocity_axis");
    if (sv_node != nullptr) {
      Eigen::Vector3d a;

nit Ditto

Suggestion:

axis

multibody/plant/BUILD.bazel line 1311 at r1 (raw file):

drake_cc_googletest(
    name = "surface_velocity_test",
    srcs = ["test/surface_velocity_test.cc"],

nit Don't list redundant filename -- it's already the default. (I thought our bzl code failed-fast on this mistake? Maybe that was only for Python tests.)


multibody/plant/BUILD.bazel line 1313 at r1 (raw file):

    srcs = ["test/surface_velocity_test.cc"],
    deps = [
        ":multibody_plant_config_functions",

Many of the deps here are not actually used by the test.


multibody/plant/multibody_plant.h line 1202 at r1 (raw file):

  /// carries a systems::BusValue whose signals set the surface speed for each
  /// body registered via SetSurfaceVelocityAxis(). Each signal's name is the
  /// fully qualified body name and its value is a finite `double` speed in m/s.

nit It's not totally obvious (even to me) exactly what this means. Is there a specific function that returns the exact necessary value which we could cite? Is it the body.scoped_name().get_full()? (Reading the implementation now, it seems like yes.)

Ditto on the output port.

Code quote:

fully qualified body name

multibody/plant/multibody_plant.h line 1210 at r1 (raw file):

  /// Returns the `"surface_displacements"` output port, which carries a
  /// systems::BusValue whose signals report the cumulative surface displacement
  /// (in metres) for each body registered via SetSurfaceVelocityAxis(). Each

nit freedom spelling

Suggestion:

meters

multibody/plant/multibody_plant.h line 2330 at r1 (raw file):

  ///    surface velocity registrations, but they can be connected by weld
  ///    joints to function as a single rigid body.
  ///  - **No random distribution for surface displacement.** The integrated

BTW This final bullet is not super important. If there is a way to un-bold it and/or trim down the verbiage, that would be helpful.


multibody/plant/multibody_plant.h line 2356 at r1 (raw file):

  /// Returns the surface-velocity axis for `body` expressed in the body frame
  /// B, or `std::nullopt` if `body` has not been registered. Works both
  /// before and after Finalize().

BTW It might be worth a reminder that because Set normalizes its argument, the return value might be different that what was passed to Set?


multibody/plant/multibody_plant.h line 3252 at r1 (raw file):

    SetDefaultMiscState(state);
    deformable_model().SetDefaultState(context, state);
  }

I believe a much simpler implementation is to just delegate all of this to the base class? Having to write out what it would have done longhand seems like a maintenance nightmare.

With this change (and the same idea in SetRandomState), I think SetDefaultMiscState can be removed.

Suggestion:

  void SetDefaultState(const systems::Context<T>& context,
                       systems::State<T>* state) const override {
    DRAKE_MBP_THROW_IF_NOT_FINALIZED();
    this->ValidateContext(context);
    this->ValidateCreatedForThisSystem(state);
    internal::MultibodyTreeSystem<T>::SetDefaultState(context, state);
    deformable_model().SetDefaultState(context, state);
  }

multibody/plant/multibody_plant.h line 3272 at r1 (raw file):

    // value.
    SetDefaultMiscState(state);
  }

BTW Ditto per SetDefaultState.

The difference here is that MbTS doesn't override the method, so we need to still call the MbT manually. (Or, I suppose, fix MbTS to also override this one.)

Suggestion:

  void SetRandomState(const systems::Context<T>& context,
                      systems::State<T>* state,
                      RandomGenerator* generator) const override {
    DRAKE_MBP_THROW_IF_NOT_FINALIZED();
    this->ValidateContext(context);
    this->ValidateCreatedForThisSystem(state);
    internal::MultibodyTreeSystem<T>::SetRandomState(context, state, generator);
    // TODO(...) This should be handled by MultibodyTreeSystem.
    internal_tree().SetRandomState(context, state, generator);
  }

multibody/plant/multibody_plant.h line 6036 at r1 (raw file):

  }

  // Computes the surface velocity for the body identified by `body_index`.

This new method is unusual. It is in the public section, but it doesn't have Doxygen and isn't bound in pydrake. (I think it was it supposed to be private, given the test fixture access pattern.)


multibody/plant/multibody_plant.h line 6115 at r1 (raw file):

    std::vector<Instance> instance;
    systems::OutputPortIndex geometry_pose;  // Declared in ctor, not Finalize.
    systems::OutputPortIndex surface_displacements;

We must document this as // Declared in ctor, not Finalize. (unless the code changes to not do that anymore).


multibody/plant/multibody_plant.h line 6917 at r1 (raw file):

  struct SurfaceVelocityEntry {
    std::string scoped_name;

Model instances can be renamed (pre-Finalize). We can't cache the scoped_name here unless we also invalidate/refresh the cache when a model instance gets renamed.

IMO trying to cache it is a premature optimization, and if we do need the scoped names to be cached, then the MbT should do that for us (post-Finalize), not the surface velocity axis table.


multibody/plant/multibody_plant.h line 6923 at r1 (raw file):

  // iteration order (ascending BodyIndex) defines displacement state indices.
  // Frozen at Finalize().
  std::map<BodyIndex, SurfaceVelocityEntry> surface_velocity_bodies_;

It's probably worth documenting our invariant that the axes stored here are always unit-length vectors.


multibody/plant/multibody_plant.h line 6928 at r1 (raw file):

  // integration (continuous mode only). Only valid when
  // surface_velocity_bodies_ is non-empty.
  int surface_displacement_continuous_state_start_{};

I'm not convinced that storing this as a runtime member field has utility that matches the implementation complexity. There are several places in the MbP code that already hard-code the assumption that surface displacement is the only z state. A halfway implementation of allowing other species of z state seems more confusing than helpful?

Instead, we could amend the comment in DeclareMiscContinuousStates() to advise future developers that they will need to start tracking z indices if/when they add more kinds of z state.


multibody/plant/multibody_plant.cc line 341 at r1 (raw file):

          systems::SystemTypeTag<MultibodyPlant>{},
          other.internal_tree().template CloneToScalar<T>(),
          other.is_discrete(), other.CalcNumRequiredMiscContinuousStates()) {

Re-deriving this number from first principles is overly brittle. We can just ask other what the right answer is.

Suggestion:

other.internal_tree().num_misc_continuous_states()

multibody/plant/multibody_plant.cc line 403 at r1 (raw file):

      surface_velocity_bodies_.emplace(
          body_index, SurfaceVelocityEntry{entry.scoped_name, entry.axis});
    }

The looping here seems gratuitous.

The only reason this loop can't be a simple assignment like all of the other modeling-parameter fields is because the SurfaceVelocityEntry is a nested class within the T-dependent plant.

If SurfaceVelocityEntry disappears entirely (because caching the scoped_name is unsound), then that will resolve it anyway.

If an entry struct sticks around, then move its declaration to the (internal) namespace instead of being plant-scoped, and then the simple assignment here will work.

Suggestion:

    surface_velocity_bodies_ = other.surface_velocity_bodies_;

multibody/plant/multibody_plant.cc line 846 at r1 (raw file):

template <typename T>
void MultibodyPlant<T>::SetSurfaceVelocityAxis(

BTW See #17734.

It would be plausible to check that the body is ours before computing with it. This is even more important if we're going to cache its scoped_name.


multibody/plant/multibody_plant.cc line 908 at r1 (raw file):

int MultibodyPlant<T>::num_misc_continuous_states() const {
  DRAKE_MBP_THROW_IF_NOT_FINALIZED();
  return CalcNumRequiredMiscContinuousStates();

Why isn't this simply calling the MbTS::num_misc_continuous_states()?


multibody/plant/multibody_plant.cc line 981 at r1 (raw file):

  return result;
}

In general for several of the new plant methods, there is a moderate level of code duplication for reading from the surface speed input port.

Consider a refactoring to add a CacheEntry for surface_speeds_vector which contains a vector of size ssize(surface_velocity_bodies_) with the speeds transcribed from the input port into state-order (or zero when absent).

Then all of the internal places that need the speed input would eval the cache entry and immediately be able to index exactly what they need.


multibody/plant/multibody_plant.cc line 989 at r1 (raw file):

  if (it == surface_velocity_bodies_.end()) {
    return Vector3<T>::Zero();
  }

BTW For readability, consider placing a named const-ref-alias here for it->second with the data type spelled out.


multibody/plant/multibody_plant.cc line 993 at r1 (raw file):

  // Read speed from the bus port. Default to zero if unconnected or absent.
  double speed = 0.0;
  const auto& port = get_surface_speeds_input_port();

nit This is the only call site that uses the public API sugar for this port (vs calling this->get_input_port(input_port_indices_.surface_speeds)). Be consistent.

Code quote:

get_surface_speeds_input_port()

multibody/plant/multibody_plant.cc line 1516 at r1 (raw file):

template <typename T>
int MultibodyPlant<T>::CalcNumRequiredMiscContinuousStates() const {

I anticipate that once all other defects are fixed, this function has no more callers and should be removed.


multibody/plant/multibody_plant.cc line 2495 at r1 (raw file):

    const T fn_AC = k * x * (1.0 + d * vn);

    if (fn_AC > 0) {

The changes to this function (including the TODO above) do not seem topical for this pull request.


multibody/plant/multibody_plant.cc line 3642 at r1 (raw file):

  }

  // Input "surface_speeds": one BusValue signal per registered body.

This comment is not accurate. (At least, if I take "registered body" to mean a body added with "AddBody".) Even if it means SetSurfaceVelocityAxis-non-null bodies, it's still imprecise because we allow bodies to be omitted, while this implies that all bodies must always be present.

Anyway, the whole thing seems spurious. The pattern in this function is to just name the port. The data type in the declaration speaks for itself, and for anything beyond that we have the Doxygen details.

Suggestion:

// Input "surface_speeds".

multibody/plant/multibody_plant.cc line 3650 at r1 (raw file):

template <typename T>
void MultibodyPlant<T>::DoCalcMiscDerivatives(

Unless I'm missing something, none of the state-update dynamics (zdot for continuous mode, abstract event handling for discrete mode) are unit tested at all.


multibody/plant/multibody_plant.cc line 3652 at r1 (raw file):

void MultibodyPlant<T>::DoCalcMiscDerivatives(
    const systems::Context<T>& context, systems::VectorBase<T>* zdot) const {
  if (CalcNumRequiredMiscContinuousStates() == 0) return;

This check is spurious now, I think? The MbTS promises not to call us when |z| = 0.


multibody/plant/multibody_plant.cc line 3654 at r1 (raw file):

  if (CalcNumRequiredMiscContinuousStates() == 0) return;

  zdot->SetZero();

BTW Plausibly the vector-wide "set zero" could be part of the early-return branch when the port is disconnected, instead of doing it unconditionally. When the port is connected, the loop already overwrites every value.


multibody/plant/multibody_plant.cc line 3662 at r1 (raw file):

  for (const auto& [_, entry] : surface_velocity_bodies_) {
    const AbstractValue* v = bus.Find(entry.scoped_name);
    zdot->SetAtIndex(i++, v != nullptr ? v->get_value<double>() : T(0));

I'm surprised that this actually compiled, since the operands to the ternary conditional have different types. Anyway, I think it would be helpful to unpack the nesting to help guide the reader with some names.

Suggestion:

const double speed = v != nullptr ? v->get_value<double>() : 0.0;
zdot->SetAtIndex(i++, speed);

multibody/plant/multibody_plant.cc line 3672 at r1 (raw file):

      state->template get_mutable_abstract_state<std::vector<double>>(
          surface_displacement_abstract_state_index_);
  // Initialize from the current context (not accumulated from next_state).

typo (I think this is trying to refer to the output argument, which is named state.)

Suggestion:

state

multibody/plant/multibody_plant.cc line 3717 at r1 (raw file):

      this->DeclarePeriodicUnrestrictedUpdateEvent(
          time_step_, 0.0, &MultibodyPlant<T>::CalcSurfaceDisplacementUpdate);
    }

The rationale for not declaring a Forced update event (to align with how q,v updates happen) needs to be explained in a comment.


multibody/plant/multibody_plant.cc line 4256 at r1 (raw file):

  // Output "surface_displacements": cumulative surface displacement per body.
  {

Hmm. If this is going to be part of DeclareSceneGraphPorts, should we color it green in the Doxgen overview?

I'm not sure, just thinking about what would be clearest for both users and maintainers.

(When I first looked at the rendered Doxygen, I wondered if it should be green, but then saw that it was a Finalize-time port so I figured maybe it's fine to keep it black. But if we're declaring it with the SG ports, maybe green is correct?)


multibody/plant/test/multibody_plant_test.cc line 2286 at r1 (raw file):

      {"geometry_pose", false},
      {"deformable_body_configuration", false},
      // Surface velocity group.

nit I buy the desire to put a comment here to separate it from the green group, but I'm not sure that calling the surface velocity a "group" is accurate.

The best I can come up with as an alternative is "Miscellaneous". Or something like "Continuing with the uncolored ports".

image.png

Suggestion:

// Miscellaneous.

multibody/plant/test/surface_velocity_test.cc line 2 at r1 (raw file):

#include <memory>
#include <optional>

nit Unused optional.


multibody/plant/test/surface_velocity_test.cc line 43 at r1 (raw file):

      auto& z = context->get_mutable_continuous_state()
                    .get_mutable_misc_continuous_state();
      for (int i = 0; i < z.size(); ++i) {

BTW I believe z.SetFromVector(VectorXd::Constant(z.size(), value)) would let us avoid writing out the loop by hand.


multibody/plant/test/surface_velocity_test.cc line 50 at r1 (raw file):

};

namespace {

Meta: In general, the test case organization here is not terribly smooth.

The tests that check ComputeSurfaceVelocity are reasonably well organized, but could perhaps be better grouped to help indicate "these are the tests of ComputeSurfaceVelocity". (The method name under test isn't part of either the test suite name or the test case name.)

The tests that check Get/Set of the axis are a lot more scattered and not organized in a way that tells a story about what/why/how we're checking.


multibody/plant/test/surface_velocity_test.cc line 76 at r1 (raw file):

}

// Fixture: finalized standalone plant, standalone context

It's important to document that this fixture provides a continuous-time plant. (A bunch of the plant logic branches on continuous vs discrete, so anything tested with this fixture only tests the continuous branches.)

Plausibly the test fixture name could reflect that distinction, as well?


multibody/plant/test/surface_velocity_test.cc line 78 at r1 (raw file):

// Fixture: finalized standalone plant, standalone context
//
// "belt" is registered with non-unit axis (2,0,0) to exercise normalization.

nit Clarity

Suggestion:

The "belt" body

multibody/plant/test/surface_velocity_test.cc line 80 at r1 (raw file):

// "belt" is registered with non-unit axis (2,0,0) to exercise normalization.
// "other" is intentionally left without surface velocity. Tests that need a
// speed wired to the surface_speeds port call FixValue().

typo

Suggestion:

FixBeltSpeed

multibody/plant/test/surface_velocity_test.cc line 118 at r1 (raw file):

// SetSurfaceVelocityAxis is pre-Finalize only.
TEST_F(SurfaceVelocityTest, SetAxisThrowsAfterFinalize) {

This test isn't 100% redundant with SetSurfaceVelocityAxisErrors, but it's close.

This tests setting an axis that was already set, and confirms it's a still error post-Finalize versus the other test case which is on a body without an existing axis.

It would be most clear if these were part of the same test case (to clarify the point), but if not then at least we need some docs explaining the difference.


multibody/plant/test/surface_velocity_test.cc line 125 at r1 (raw file):

// SetSurfaceVelocityAxis normalizes its input, can overwrite an existing
// registration, and the value survives Finalize().
GTEST_TEST(MultibodyPlantTest, SetSurfaceVelocityAxisNormalizesAndPersists) {

I find it weird that the test name is MultibodyPlantTest in a file not named multibody_plant_test.cc. We have two one-off instances of this pattern, but it's not typical.

Ditto throughout.

Code quote:

MultibodyPlantTest

multibody/plant/test/surface_velocity_test.cc line 188 at r1 (raw file):

}

TEST_F(SurfaceVelocityTest, SurfaceSpeedsPortName) {

We should also check the output port name.


multibody/plant/test/surface_velocity_test.cc line 256 at r1 (raw file):

  const Vector3d n_C_B(0, 0, 1);
  double speed = 0.75;
  const Vector3d v_ss_B_expected(0, -speed, 0);

nit Why is this value established so far away from its first use?


bindings/pydrake/multibody/plant_py.cc line 254 at r1 (raw file):

            py::arg("model_instance"), cls_doc.num_actuated_dofs.doc_1args);
    // Plant-owned state variables - miscellaneous continuous state.
    cls  // BR

This method should be bound in the same order as it is declared in the header file. So, this shouldn't have a new section break (unless we also re-jigger the header file, but that is probably not a good idea).

Yes, the "Forwarded methods from MultibodyTree" introduction is not accurate for this method, but it was already an fuzzy approximation when we added bindings for num_constraints() and such. (Edited to add: This MbP method actually should be forwarded from MbTS as its implementation inside MbP, so in fact the section title will be accurate.)


bindings/pydrake/multibody/plant_py.cc line 1197 at r1 (raw file):

            py_rvp::reference_internal,
            cls_doc.get_surface_displacement_output_port.doc)
        .def("SetSurfaceVelocityAxis", &Class::SetSurfaceVelocityAxis,

For better or worse, putting the GetAxis/SetAxis into the "ports" section is inconsistent with existing practice. Only ports should live here.

I'd say "// Property accessors" is a probably good home for the Get/Set, or maybe there's somewhere even better.


bindings/pydrake/multibody/test/plant_test.py line 1858 at r1 (raw file):

        body = plant.AddRigidBody("body")

        # confirm configuration of surface velocity axis.

nit Capitalize (GSG).

@SeanCurtis-TRI

Copy link
Copy Markdown
Contributor Author

multibody/plant/multibody_plant.h line 1202 at r1 (raw file):

Previously, jwnimmer-tri (Jeremy Nimmer) wrote…

nit It's not totally obvious (even to me) exactly what this means. Is there a specific function that returns the exact necessary value which we could cite? Is it the body.scoped_name().get_full()? (Reading the implementation now, it seems like yes.)

Ditto on the output port.

This is problematic on two levels:

  1. MultibodyPlant doesn't register geometry::Frames with the scoped name of the corresponding body. Instead, it uses the bespoke GetScopedName() in multibody_plant.cc. This is an approximation of ScopedName -- it elides model instance names for the world and default model instances. That one-off function introduces the term "fully qualified" (which, in turn, is appears in the documentation of multiple multibody element types).
  2. MeshcatVisualizer ultimately needs to map geometry::Frame back to RigidBody so that it can look up the surface velocity axis. I've just noted in my implementation, there's a defect there that isn't covered by test. MeshcatVisualizer is using RigidBody::scoped_name() and is assuming that the geometry::Frame::name() will match (which it does in many cases...but not all).

This is related to the name mangling referenced in #9128. I don't think any of us has actually figured out what the obstacle is to resolving that issue.

So, I have two action items:

  1. Augment the meschat visualizer test to confirm the bug that I now perceive is real or not.
  2. Possibly poke into resolving geometry: More SceneGraph introspection - get shapes and poses for geometries #9128 prior to this PR so that the geometry frame's name is simply the body's scoped name.

Thoughts?

@jwnimmer-tri jwnimmer-tri left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jwnimmer-tri made 1 comment.
Reviewable status: 49 unresolved discussions, LGTM missing from assignee jwnimmer-tri(platform), needs at least two assigned reviewers (waiting on SeanCurtis-TRI).


multibody/plant/multibody_plant.h line 1202 at r1 (raw file):

Previously, SeanCurtis-TRI (Sean Curtis) wrote…

This is problematic on two levels:

  1. MultibodyPlant doesn't register geometry::Frames with the scoped name of the corresponding body. Instead, it uses the bespoke GetScopedName() in multibody_plant.cc. This is an approximation of ScopedName -- it elides model instance names for the world and default model instances. That one-off function introduces the term "fully qualified" (which, in turn, is appears in the documentation of multiple multibody element types).
  2. MeshcatVisualizer ultimately needs to map geometry::Frame back to RigidBody so that it can look up the surface velocity axis. I've just noted in my implementation, there's a defect there that isn't covered by test. MeshcatVisualizer is using RigidBody::scoped_name() and is assuming that the geometry::Frame::name() will match (which it does in many cases...but not all).

This is related to the name mangling referenced in #9128. I don't think any of us has actually figured out what the obstacle is to resolving that issue.

So, I have two action items:

  1. Augment the meschat visualizer test to confirm the bug that I now perceive is real or not.
  2. Possibly poke into resolving #9128 prior to this PR so that the geometry frame's name is simply the body's scoped name.

Thoughts?

I don't think I have much to add. The explorations seem good.

One way to move forward would be to document and treat the Body's scoped_name() as authoritative (like we already do here) and leave the visualization problem for another day.

If the only gap is really down to the world and default model instances, another solution could be to forbid setting surface velocities on bodies in those instances (throwing in the plant). We could relax that restriction later once the naming is sorted out. Possibly this makes unit testing too annoying, though.

@jwnimmer-tri jwnimmer-tri left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jwnimmer-tri made 1 comment.
Reviewable status: 49 unresolved discussions, LGTM missing from assignee jwnimmer-tri(platform), needs at least two assigned reviewers (waiting on SeanCurtis-TRI).


multibody/plant/multibody_plant.h line 1202 at r1 (raw file):

MeshcatVisualizer is using RigidBody::scoped_name() and is assuming that the geometry::Frame::name() will match (which it does in many cases...but not all).

Actually, can't this solve by just fixing the (draft) code in MeshcatVisualizer?

Right now, it just copies the axes dict with the body names (making the assumption that body name matches frame name, as you note). However, it that loop I believe it has access to all the data it needs to convert from body name to frame name while transcribing the dictionary?

@jwnimmer-tri jwnimmer-tri left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jwnimmer-tri made 1 comment.
Reviewable status: 49 unresolved discussions, LGTM missing from assignee jwnimmer-tri(platform), needs at least two assigned reviewers (waiting on SeanCurtis-TRI).


multibody/plant/multibody_plant.h line 1202 at r1 (raw file):

Previously, jwnimmer-tri (Jeremy Nimmer) wrote…

MeshcatVisualizer is using RigidBody::scoped_name() and is assuming that the geometry::Frame::name() will match (which it does in many cases...but not all).

Actually, can't this solve by just fixing the (draft) code in MeshcatVisualizer?

Right now, it just copies the axes dict with the body names (making the assumption that body name matches frame name, as you note). However, it that loop I believe it has access to all the data it needs to convert from body name to frame name while transcribing the dictionary?

(It could even key its dict on geometry::FrameId instead of name, for even faster lookups.)

@jwnimmer-tri jwnimmer-tri left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jwnimmer-tri made 1 comment.
Reviewable status: 50 unresolved discussions, LGTM missing from assignee jwnimmer-tri(platform), needs at least two assigned reviewers, commits need curation (https://drake.mit.edu/reviewable.html#curated-commits) (waiting on SeanCurtis-TRI).


multibody/plant/multibody_plant.h line 2274 at r2 (raw file):

  /// @anchor mbp_surface_velocity
  /// @name               Surface velocity
  ///

Somewhere in the MbP doxygen, we need a @warning that the contact dynamics are not actually implemented yet. (And we'll yank out that comment when we add the dynamics.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release notes: none This pull request should not be mentioned in the release notes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants