RobotLocomotion / drake

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

STL support #19408

Open SeanCurtis-TRI opened 1 year ago

SeanCurtis-TRI commented 1 year ago

What happened?

In a recent slack coversation a user tried instantiating a model using STL files. They went through the following unhelpful process.

  1. Simply reference the STL and receive the following errror:
    • RuntimeError: ProximityEngine: expect an Obj file for non-hydroelastics but get .stl file /workspaces/bdai/ws/install/spot_description/share/spot_description/meshes/base/collision/body_collision.stl) instead.
  2. Try adding the tag <drake:compliant_hydroelastic>
    • RuntimeError: A mesh must contain at least one tetrahedron
  3. Try switching to <drake:rigid_hydroelastic>:
    • RuntimeError: hydroelastic::MakeRigidRepresentation(): unsupported mesh file: /workspaces/bdai/ws/install/spot_description/share/spot_description/meshes/base/collision/body_collision.stl

The real problem is:

  1. Drake doesn't consume STL files. Period. And, more generally, supports a limited domain of mesh files.
  2. Users should be given direct feedback on this subject with clear directions on guidance for converting.

Proposal:

  1. SceneGraph should take responsibility for assessing Mesh types upon registration.
    • If it's an unsupported type, SceneGraph should make that clear and direct the user to a drake webpage discussing how to get supported geometry.
    • For the legacy behavior of, "you've specified an STL but have an identically named OBJ next to it", we should emit a warning right then (that we are swapping the STL for the OBJ) and then swap it internally so no downstream geometry consumers have to worry about it.

Some nuances:

  1. We support more than OBJ. We also support VTK, but for limited purposes (e.g., proximity with compliant hydorelastic.). The principle of "the mesh may depend on the application" is a principle that will grow (e.g., gltf for perception roles). So, we may need to defer some of the final validation until we see what role is applied. But this would still lie within SceneGraph's purview (see AssignRole()).
  2. This lies outside the domain of parsing (which would be a natural home). Do we want this validation to be in stand-alone functions that parsing and SceneGraph can use?
  3. The page to which we refer the reader may not yet exist and may need to be created.

Version

No response

What operating system are you using?

No response

What installation option are you using?

No response

Relevant log output

No response

jwnimmer-tri commented 1 year ago

See also #19055.

markisus commented 1 year ago

Just got bit by this. Until the issue is resolved, do you have a recommended workflow for doing the conversion? I see there is the stl2obj https://github.com/RobotLocomotion/drake/blob/316b8201810801cb537c9c0a5f0a05d2c6cecae7/manipulation/util/BUILD.bazel#L146 tool. Is this tool available if I installed the python version of drake using pip?

markisus commented 1 year ago

I found your converter script using trimesh and adapted it to work for stl as well. Converting all stls and daes to obj fixed the error for me.

import argparse            
import pathlib
from trimesh import load_mesh
from trimesh.exchange.obj import export_obj

def convert(root, extension):
    for mesh_file in pathlib.Path(root).glob(f'*/**/*.{extension}'):
        print(f"Converting {mesh_file}")
        mesh = load_mesh(mesh_file)
        obj, data = export_obj(
                    mesh, return_texture=True, mtl_name=mesh_file.with_suffix('.mtl'))
        obj_file = mesh_file.with_suffix('.obj')
        with open(obj_file, "w") as f:
            f.write(obj)
            print(f"Wrote {obj_file}")
        # save the MTL and images                                
        for k, v in data.items():
            with open(k, 'wb') as f:
                f.write(v)
                print(f"Wrote {k}")

parser = argparse.ArgumentParser("Convert meshes to obj")
parser.add_argument("root", type=str, help="The root directory to glob for stl files")
parser.add_argument("--extension", type=str, default="stl", help="The type of mesh to convert. You may specify dae. Other extensions may work as well.")

args = parser.parse_args()

convert(args.root, extension=args.extension)

There is a bug in the current version of Trimesh that I had to resolve locally to fix meshes that had no associated materials. See https://github.com/mikedh/trimesh/issues/1970

jwnimmer-tri commented 11 months ago

FYI I think the points above about giving good, early feedback in case of file type problems are right on point.

However, for the particular case of STL files, now that we have VTK packaged well I think it would be a good idea to add direct support for STL files. Possibly that rebalances the priorities here somewhat.

jwnimmer-tri commented 2 months ago

Given my prior comment, I'm repurposing this issue:

The new call to action is either:

(1) When given an STL mesh, throw an error with a hyperlink to a troubleshooting guide that convincingly explains how to convert the model file to use a different supported file format -- either OBJ or glTF. Our MuJoCo parser has an epsilon version of this, but we need it for SDFormat / URDF parsers as well, and we still need a guide document for how to convert.

(2) Actually parse and support the STL meshes, for all geometry endpoints. (This means Meshcat, Meldis, all RenderEngines, implicit inertia calculations, etc.)

Obviously (2) is better if we can swing it. I anticipate it will not be that difficult.

RussTedrake commented 2 months ago

I also like the idea of a single prepare_model_for_drake.py. https://github.com/RobotLocomotion/drake/issues/19109#issuecomment-2219287999