forked from pz4kybsvg/Conception
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
180 lines
6.5 KiB
180 lines
6.5 KiB
#include "drake/multibody/parsing/detail_mesh_parser.h"
|
|
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <istream>
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include <fmt/format.h>
|
|
#include <tiny_obj_loader.h>
|
|
|
|
#include "drake/common/diagnostic_policy.h"
|
|
#include "drake/geometry/proximity/obj_to_surface_mesh.h"
|
|
#include "drake/geometry/proximity/triangle_surface_mesh.h"
|
|
#include "drake/multibody/parsing/detail_make_model_name.h"
|
|
#include "drake/multibody/tree/geometry_spatial_inertia.h"
|
|
|
|
namespace drake {
|
|
namespace multibody {
|
|
namespace internal {
|
|
namespace {
|
|
|
|
using drake::geometry::TriangleSurfaceMesh;
|
|
using drake::internal::DiagnosticPolicy;
|
|
|
|
// The parse result of attempting to read a single object from OBJ data.
|
|
struct NamedMesh {
|
|
std::string name;
|
|
std::unique_ptr<TriangleSurfaceMesh<double>> mesh;
|
|
};
|
|
|
|
// Callback function for capturing the parsed object name; required by tinyobj's
|
|
// API. `storage` is provided by the caller.
|
|
void CaptureName(void* storage, const char* parsed_name) {
|
|
*static_cast<std::string*>(storage) = parsed_name;
|
|
}
|
|
|
|
// Attempts to parse a single mesh out of the input stream returning the
|
|
// resulting triangle mesh and its name (if any is defined). If there is a
|
|
// parsing failure, the return value contains a null mesh.
|
|
NamedMesh DoGetObjMesh(const DiagnosticPolicy& diagnostic,
|
|
std::istream* input_stream,
|
|
const std::string& mtl_basedir) {
|
|
std::optional<TriangleSurfaceMesh<double>> mesh =
|
|
geometry::internal::DoReadObjToSurfaceMesh(input_stream, 1.0,
|
|
mtl_basedir,
|
|
{.diagnostic = diagnostic,
|
|
.allowed_shape_count = 1});
|
|
|
|
if (!mesh.has_value()) {
|
|
return {};
|
|
}
|
|
|
|
// A mesh exists, let's see if it had a name. We'll rewind the input stream
|
|
// and parse explicitly listening for the "o some_name" data.
|
|
std::string object_name;
|
|
tinyobj::callback_t callback;
|
|
callback.object_cb = CaptureName;
|
|
|
|
input_stream->seekg(0);
|
|
const bool result =
|
|
tinyobj::LoadObjWithCallback(*input_stream, callback, &object_name);
|
|
// If we have a mesh, then no error should be encountered in attempting to
|
|
// find the name.
|
|
DRAKE_DEMAND(result);
|
|
// Note: the name may still be *empty*. This simply means the obj didn't
|
|
// define an object name.
|
|
return {object_name,
|
|
std::make_unique<TriangleSurfaceMesh<double>>(std::move(*mesh))};
|
|
}
|
|
|
|
// N.B. This layer of abstraction leaves the door open for implementing
|
|
// GetMeshFromString() once we can support handling SceneGraph geometry from
|
|
// an in-memory source.
|
|
NamedMesh GetMeshFromFile(const DiagnosticPolicy& diagnostic,
|
|
const std::string filename) {
|
|
std::ifstream input_stream(filename);
|
|
if (!input_stream.is_open()) {
|
|
diagnostic.Error(fmt::format("Cannot open file '{}'", filename));
|
|
return {};
|
|
}
|
|
// Failure to provide the directory of the obj file as the base material
|
|
// library directory will cause OBJs with MTL files referenced by relative
|
|
// paths to spew warnings.
|
|
const std::string mtl_basedir =
|
|
std::filesystem::path(filename).parent_path().string() + "/";
|
|
return DoGetObjMesh(diagnostic, &input_stream, mtl_basedir);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
std::optional<ModelInstanceIndex> AddModelFromMesh(
|
|
const DataSource& data_source, const std::string& model_name,
|
|
const std::optional<std::string>& parent_model_name,
|
|
const ParsingWorkspace& workspace) {
|
|
if (!data_source.IsFilename()) {
|
|
throw std::runtime_error(
|
|
"Parser does not yet support adding models using in-memory OBJ data. "
|
|
"The OBJ data must be referenced as a file.");
|
|
}
|
|
DRAKE_THROW_UNLESS(!workspace.plant->is_finalized());
|
|
|
|
const std::string filename = data_source.GetAbsolutePath();
|
|
|
|
NamedMesh named_mesh = GetMeshFromFile(workspace.diagnostic, filename);
|
|
|
|
if (named_mesh.mesh == nullptr) {
|
|
// Reasons for there being no mesh should already have been dispatched to
|
|
// the diagnostic policy.
|
|
return std::nullopt;
|
|
}
|
|
|
|
// Resolve the naming protocol - see Parser documentation.
|
|
std::string candidate_name = model_name;
|
|
if (candidate_name.empty()) {
|
|
candidate_name = named_mesh.name;
|
|
}
|
|
if (candidate_name.empty()) {
|
|
candidate_name = std::filesystem::path(filename).stem();
|
|
}
|
|
|
|
// Register model instance, body, and collision and visual geometries.
|
|
MultibodyPlant<double>& plant = *workspace.plant;
|
|
std::string model_instance_name =
|
|
MakeModelName(candidate_name, parent_model_name, workspace);
|
|
const ModelInstanceIndex model_instance =
|
|
plant.AddModelInstance(model_instance_name);
|
|
|
|
const SpatialInertia<double> M_BBo_B =
|
|
CalcSpatialInertia(*named_mesh.mesh, 1000.0 /* kg/m3 */);
|
|
const RigidBody<double>& body =
|
|
plant.AddRigidBody(candidate_name, model_instance, M_BBo_B);
|
|
|
|
if (plant.geometry_source_is_registered()) {
|
|
// TODO(SeanCurtis-TRI): Consider using named_mesh.name as part of the
|
|
// geometry names.
|
|
// TODO(SeanCurtis-TRI): It would be better to use a single geometry and
|
|
// assign all three roles to it. This would require access to the SceneGraph
|
|
// and some shenanigans to placate MbP.
|
|
const auto X_BG = math::RigidTransformd::Identity();
|
|
const geometry::Mesh mesh(filename);
|
|
plant.RegisterCollisionGeometry(body, X_BG, mesh, "collision",
|
|
CoulombFriction<double>());
|
|
// TODO(SeanCurtis-TRI): If there's a material applied to the object, use
|
|
// the specified color.
|
|
plant.RegisterVisualGeometry(body, X_BG, mesh, "visual");
|
|
}
|
|
|
|
return model_instance;
|
|
}
|
|
|
|
MeshParserWrapper::MeshParserWrapper() = default;
|
|
MeshParserWrapper::~MeshParserWrapper() = default;
|
|
|
|
std::optional<ModelInstanceIndex> MeshParserWrapper::AddModel(
|
|
const DataSource& data_source, const std::string& model_name,
|
|
const std::optional<std::string>& parent_model_name,
|
|
const ParsingWorkspace& workspace) {
|
|
return AddModelFromMesh(data_source, model_name, parent_model_name,
|
|
workspace);
|
|
}
|
|
|
|
std::vector<ModelInstanceIndex> MeshParserWrapper::AddAllModels(
|
|
const DataSource& data_source,
|
|
const std::optional<std::string>& parent_model_name,
|
|
const ParsingWorkspace& workspace) {
|
|
std::optional<ModelInstanceIndex> maybe_index =
|
|
AddModelFromMesh(data_source, "", parent_model_name, workspace);
|
|
if (maybe_index.has_value()) {
|
|
return {*maybe_index};
|
|
}
|
|
return {};
|
|
}
|
|
|
|
} // namespace internal
|
|
} // namespace multibody
|
|
} // namespace drake
|