RobotLocomotion / drake

Model-based design and verification for robotics.
https://drake.mit.edu
Other
3.33k stars 1.26k forks source link

Parser support for obj files directly #17267

Closed jwnimmer-tri closed 1 year ago

jwnimmer-tri commented 2 years ago

When adding manipulands to a scene, the current workflow is to develop a mesh for the object, and then write an SDFormat file that refers to the mesh, then feed that SDFormat file to the parser. Both the mesh and the SDFormat file go into source control.

The content of the SDFormat file is fairly boilerplate, and we often create it using semi-scripted methods. Even with that, it's extra work to write and maintain that boilerplate.

We should update the multibody::Parser to accept *.obj files directly as an input format.

It should add a new model instance, floating body, visual and proximity geometry, and should compute the mass and inertia from the mesh itself, using approximations as necessary (e.g., assume uniform density, possibly even assume the density of water).

One problem will be the units of the mesh. Mesh tools tend to operate on obj files in centimeters (millimeters?) by convention, but we also have meshes that use meters. We might need a way for the user to specify their units, but I'm not sure how to do that.

jwnimmer-tri commented 2 years ago

FYI we already have code that calculates the centroid of the volume enclosed by a triangulated surface mesh. As a by product, it also calculates the volume (6 times of volume to be precise):

https://github.com/RobotLocomotion/drake/blob/f7d728144d4d350fc5fa491a5182d02782bdacbd/geometry/proximity/make_convex_mesh.cc#L51

jwnimmer-tri commented 2 years ago

I think there are basically two ways to offer this API:

(1) Add a new dedicated function.

class Parser {
  ModelInstanceIndex AddMeshModelFromFile(
      const std::string& file_name,
      double scale = 1.0,  // in meters per obj coordinate
      double density = 1.0,  // in kg/L
      const std::string& model_name = {});
  ModelInstanceIndex AddMeshModelFromString(
      const std::string& file_contents,
      const std::string& file_type,
      double scale = 1.0,  // in meters per obj coordinate
      double density = 1.0,  // in kg/L
      const std::string& model_name = {});
};

(2) Use the existing function but separately configure the properties:

class Parser {
  // Existing API
  ModelInstanceIndex AddModelFromFile(const std::string& file_name, const std::string& model_name = {});
  ModelInstanceIndex AddModelFromString(const std::string& file_contents, const std::string& file_type, const std::string& model_name = {});

  // New API
  void set_standalone_mesh_scale(double scale);
  void set_standalone_mesh_density(double density);
};

The user would first set the scale and density (the scale is probably homogeneous for all of their art assets, and the density will not matter if they are only doing geometrical queries), and then can add the mesh using only its filename. Option (2) might be slightly more compatible with lists of model files (e.g., model directives) that pre-suppose a filename is all that's necessary.

jwnimmer-tri commented 2 years ago

The other different-direction answer would be to publish a sdformat-generating script, given an obj.

jwnimmer-tri commented 1 year ago

Another possible tactic here: it seems like the MJCF syntax for loading a mesh as a manipuland is somewhat terse. An example of using that format to easily load an *.obj might be sufficient on its own, without changing the Parser.

rpoyner-tri commented 1 year ago

@SeanCurtis-TRI any chance you would be interested in taking this one?

SeanCurtis-TRI commented 1 year ago

Yep. I'll take this one.

SeanCurtis-TRI commented 1 year ago

@jwnimmer-tri Do you have a link to the MJCF syntax to which you refer? I've skimmed the website but nothing jumped out in this particular context.

jwnimmer-tri commented 1 year ago

See TestMesh in detail_mujoco_parser_test.cc.

<mujoco model="box">
  <asset>
    <mesh name="box" file="box.obj"/>
  </asset>
  <worldbody>
    <geom name="box_geom" type="mesh" mesh="box"/>
  </worldbody>
</mujoco>
jwnimmer-tri commented 1 year ago

From f2f: when loading an *.obj, if it has an embedded name, use that as the model name (and body name). Otherwise, use the file basename as the model name. Otherwise, use a hardcoded placeholder name like "wavefront_string_mesh" or whatever (only relevant when parsing from strings).

SeanCurtis-TRI commented 1 year ago

Slight elaboration on the name discussion.

  1. We have APIs in which the user can request loading a single model and specify the model_name. So, that puts some shades of grey here. There are two axes: naming protocol and model-body name agreement.
    • naming protocol. I'd suggest the following (ever so slightly modified) protocol for (at least) the model instance.
      • if model_name is specified, that wins.
      • else if a single object name is specified in the obj file, that wins.
      • Else if the mesh comes from a file, the file's base name wins.
      • Else, it came from a string, so we give it some generic "from a string" name.
    • model vs body name: should there ever be a parting of the ways?
      • For example, if the OBJ has an object name but a model_name is explicitly passed, should the model be one thing (the user-specified model name) and the body another (the OBJ object name)?
      • In the case of "matching" names, would we consider model_name and model_name_body? Or should the definitely always match?
  2. Presumably loading the same mesh multiple times should have the same semantics as loading the same sdf/urdf multiple times. Model instance name collision will occur organically and will throw. So, the user would be obliged to handle that on the parser-invocation side of things. Yes?
jwnimmer-tri commented 1 year ago

I'd suggest the following (ever so slightly modified) protocol for (at least) the model instance.

SGTM

For example, if the OBJ has an object name but a model_name is explicitly passed, should the model be one thing (the user-specified model name) and the body another (the OBJ object name)?

I think this one is probably a coin toss. Mu gut feeling goes towards model_name == body_name for this feature, to be the most uniform. I could see the embedded obj name being the geometry instance name, if anything.

Presumably loading the same mesh multiple times should have the same semantics ...

Yes.

jwnimmer-tri commented 1 year ago

Re-opening with one other thought. (We might decide to skip but...)

Should we add a little demo of this feature to one of the tutorials? I think possibly users might not know about it otherwise.

Maybe "authoring_multibody_simulation"?

jwnimmer-tri commented 1 year ago

Back to @SeanCurtis-TRI to add sample code to a tutorial.

jwnimmer-tri commented 1 year ago

I'll take over updating the tutorials.

jwnimmer-tri commented 1 year ago

=> https://github.com/RobotLocomotion/drake/pull/19070