Skip to content

Commit

Permalink
proximity: Don't warn when parsing meshes with mtl files (#14974)
Browse files Browse the repository at this point in the history
  • Loading branch information
jwnimmer-tri authored May 3, 2021
1 parent 80e8941 commit cc31cf3
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 33 deletions.
11 changes: 7 additions & 4 deletions bindings/pydrake/geometry_py.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1283,11 +1283,14 @@ void DoScalarIndependentDefinitions(py::module m) {
py_rvp::reference_internal, py::arg("diffuse"),
doc.MakePhongIllustrationProperties.doc);

m.def("ReadObjToSurfaceMesh",
py::overload_cast<const std::string&, double>(
&geometry::ReadObjToSurfaceMesh),
m.def(
"ReadObjToSurfaceMesh",
[](const std::string& filename, double scale) {
return geometry::ReadObjToSurfaceMesh(filename, scale);
},
py::arg("filename"), py::arg("scale") = 1.0,
doc.ReadObjToSurfaceMesh.doc_2args_filename_scale);
// N.B. We have not bound the optional "on_warning" argument.
doc.ReadObjToSurfaceMesh.doc_3args_filename_scale_on_warning);
}

void def_geometry(py::module m) {
Expand Down
72 changes: 47 additions & 25 deletions geometry/proximity/obj_to_surface_mesh.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

#include <fstream>
#include <istream>
#include <memory>
#include <numeric>
#include <optional>
#include <stdexcept>
#include <string>
#include <utility>
Expand All @@ -12,6 +14,7 @@
#include <tiny_obj_loader.h>

#include "drake/common/drake_assert.h"
#include "drake/common/filesystem.h"
#include "drake/common/text_logging.h"
#include "drake/geometry/proximity/surface_mesh.h"

Expand All @@ -23,12 +26,6 @@ namespace {
// TODO(DamrongGuoy): Refactor the tinyobj usage between here and
// ProximityEngine.

// TODO(DamrongGuoy): Remove the guard DRAKE_DOXYGEN_CXX when we fixed
// issue#11130 "doxygen: Do not emit for `*.cc` files, also ignore
// `internal` namespace when appropriate".

#ifndef DRAKE_DOXYGEN_CXX

/*
Converts vertices of tinyobj to vertices of SurfaceMesh.
@param tinyobj_vertices
Expand Down Expand Up @@ -111,39 +108,39 @@ void TinyObjToSurfaceFaces(const tinyobj::mesh_t& mesh,
}
}

#endif // #ifndef DRAKE_DOXYGEN_CXX

} // namespace

SurfaceMesh<double> ReadObjToSurfaceMesh(const std::string& filename,
const double scale) {
std::ifstream input_stream(filename);
if (!input_stream.is_open()) {
throw std::runtime_error("Cannot open file '" + filename +"'");
}
return ReadObjToSurfaceMesh(&input_stream, scale);
}

SurfaceMesh<double> ReadObjToSurfaceMesh(std::istream* input_stream,
const double scale) {
SurfaceMesh<double> DoReadObjToSurfaceMesh(
std::istream* input_stream,
const double scale,
const std::optional<std::string>& mtl_basedir,
const std::function<void(std::string_view)> on_warning) {
tinyobj::attrib_t attrib; // Used for vertices.
std::vector<tinyobj::shape_t> shapes; // Used for triangles.
std::vector<tinyobj::material_t> materials; // Not used.
std::string warn;
std::string err;
// Ignore material-library file.
tinyobj::MaterialReader* readMatFn = nullptr;
std::unique_ptr<tinyobj::MaterialReader> readMatFn;
if (mtl_basedir) {
readMatFn = std::make_unique<tinyobj::MaterialFileReader>(*mtl_basedir);
}
// triangulate non-triangle faces.
bool triangulate = true;

bool ret = tinyobj::LoadObj(
&attrib, &shapes, &materials, &warn, &err, input_stream, readMatFn,
&attrib, &shapes, &materials, &warn, &err, input_stream, readMatFn.get(),
triangulate);
if (!ret || !err.empty()) {
throw std::runtime_error("Error parsing Wavefront obj file : " + err);
}
if (!warn.empty()) {
drake::log()->warn("Warning parsing Wavefront obj file : {}", warn);
warn = "Warning parsing Wavefront obj file : " + warn;
if (warn.back() == '\n') {
warn.pop_back();
}
if (on_warning) {
on_warning(warn);
} else {
drake::log()->warn(warn);
}
}
if (shapes.size() == 0) {
throw std::runtime_error("The Wavefront obj file has no faces.");
Expand All @@ -169,5 +166,30 @@ SurfaceMesh<double> ReadObjToSurfaceMesh(std::istream* input_stream,
return SurfaceMesh<double>(std::move(faces), std::move(vertices));
}

} // namespace

SurfaceMesh<double> ReadObjToSurfaceMesh(
const std::string& filename,
const double scale,
std::function<void(std::string_view)> on_warning) {
std::ifstream input_stream(filename);
if (!input_stream.is_open()) {
throw std::runtime_error("Cannot open file '" + filename +"'");
}
const std::string mtl_basedir =
filesystem::path(filename).parent_path().string() + "/";
return DoReadObjToSurfaceMesh(&input_stream, scale, mtl_basedir,
std::move(on_warning));
}

SurfaceMesh<double> ReadObjToSurfaceMesh(
std::istream* input_stream,
const double scale,
std::function<void(std::string_view)> on_warning) {
DRAKE_THROW_UNLESS(input_stream != nullptr);
return DoReadObjToSurfaceMesh(input_stream, scale,
std::nullopt /* mtl_basedir */, std::move(on_warning));
}

} // namespace geometry
} // namespace drake
18 changes: 14 additions & 4 deletions geometry/proximity/obj_to_surface_mesh.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
#pragma once

#include <functional>
#include <istream>
#include <string>
#include <string_view>
#include <vector>

#include "drake/geometry/proximity/surface_mesh.h"
Expand All @@ -19,18 +21,26 @@ namespace geometry {
A valid file name with absolute path or relative path.
@param scale
An optional scale to coordinates.
@param on_warning
An optional callback that will receive warning message(s) encountered
while reading the mesh. When not provided, drake::log() will be used.
@throws std::runtime_error if `filename` doesn't have a valid file path, or the
file has no faces.
@return surface mesh
*/
SurfaceMesh<double> ReadObjToSurfaceMesh(const std::string& filename,
double scale = 1.0);
SurfaceMesh<double> ReadObjToSurfaceMesh(
const std::string& filename,
double scale = 1.0,
std::function<void(std::string_view)> on_warning = {});

/**
Overload of @ref ReadObjToSurfaceMesh(const std::string&, double) with the
Wavefront .obj file given in std::istream.
*/
SurfaceMesh<double> ReadObjToSurfaceMesh(std::istream* input_stream,
double scale = 1.0);
SurfaceMesh<double> ReadObjToSurfaceMesh(
std::istream* input_stream,
double scale = 1.0,
std::function<void(std::string_view)> on_warning = {});

} // namespace geometry
} // namespace drake
34 changes: 34 additions & 0 deletions geometry/proximity/test/obj_to_surface_mesh_test.cc
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "drake/geometry/proximity/obj_to_surface_mesh.h"

#include <fstream>
#include <sstream>
#include <string>
#include <tuple>
Expand Down Expand Up @@ -159,6 +160,39 @@ GTEST_TEST(ObjToSurfaceMeshTest, ThrowExceptionForEmptyFile) {
"The Wavefront obj file has no faces.");
}

void FailOnWarning(std::string_view message) {
throw std::runtime_error(fmt::format("FailOnWarning: {}", message));
}

GTEST_TEST(ObjToSurfaceMeshTest, WarningCallback) {
// This *.obj file refers to a separate *.mtl file. In various cases below,
// this may cause warnings from the parser.
const std::string filename =
FindResourceOrThrow("drake/geometry/test/quad_cube.obj");

// When loaded as a stream (such that the *.mtl file is missing) with
// a defaulted callback, we will drake::log() but not throw.
{
std::ifstream input(filename);
EXPECT_NO_THROW(ReadObjToSurfaceMesh(&input, 1.0));
}

// When loaded as a stream (such that the *.mtl file is missing), the user-
// provided callback may choose to throw, and our test stub callback does so.
{
std::ifstream input(filename);
DRAKE_EXPECT_THROWS_MESSAGE(
ReadObjToSurfaceMesh(&input, 1.0, &FailOnWarning),
std::exception,
"FailOnWarning: Warning parsing Wavefront obj file : "
".*CubeMaterial.*not found.*");
}

// When parsing using a filename, we are able to locate the *.mtl file with
// no warnings.
EXPECT_NO_THROW(ReadObjToSurfaceMesh(filename, 1.0, &FailOnWarning));
}

GTEST_TEST(ObjToSurfaceMeshTest, ThrowExceptionFileHasNoFaces) {
std::istringstream no_faces{R"(
v 1.0 0.0 0.0
Expand Down

0 comments on commit cc31cf3

Please sign in to comment.