Add binary STL support. (#802)

Includes support for binary STL and some basic tests.
This commit is contained in:
Raymond Hulha 2022-03-25 22:14:33 +01:00 committed by GitHub
parent 7d62f1f26f
commit befe2d8809
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 1992 additions and 2 deletions

View File

@ -396,6 +396,10 @@ list(APPEND draco_io_sources
"${draco_src_root}/io/ply_property_writer.h"
"${draco_src_root}/io/ply_reader.cc"
"${draco_src_root}/io/ply_reader.h"
"${draco_src_root}/io/stl_decoder.cc"
"${draco_src_root}/io/stl_decoder.h"
"${draco_src_root}/io/stl_encoder.cc"
"${draco_src_root}/io/stl_encoder.h"
"${draco_src_root}/io/point_cloud_io.cc"
"${draco_src_root}/io/point_cloud_io.h"
"${draco_src_root}/io/stdio_file_reader.cc"

View File

@ -60,6 +60,8 @@ list(
"${draco_src_root}/io/obj_encoder_test.cc"
"${draco_src_root}/io/ply_decoder_test.cc"
"${draco_src_root}/io/ply_reader_test.cc"
"${draco_src_root}/io/stl_decoder_test.cc"
"${draco_src_root}/io/stl_encoder_test.cc"
"${draco_src_root}/io/point_cloud_io_test.cc"
"${draco_src_root}/mesh/mesh_are_equivalent_test.cc"
"${draco_src_root}/mesh/mesh_cleanup_test.cc"

View File

@ -20,6 +20,7 @@
#include "draco/io/file_utils.h"
#include "draco/io/obj_decoder.h"
#include "draco/io/ply_decoder.h"
#include "draco/io/stl_decoder.h"
#ifdef DRACO_TRANSCODER_SUPPORTED
#include "draco/compression/draco_compression_options.h"
#include "draco/compression/encode.h"
@ -71,11 +72,16 @@ StatusOr<std::unique_ptr<Mesh>> ReadMeshFromFile(
return std::move(mesh);
}
if (extension == "ply") {
// Wavefront PLY file format.
// Stanford PLY file format.
PlyDecoder ply_decoder;
DRACO_RETURN_IF_ERROR(ply_decoder.DecodeFromFile(file_name, mesh.get()));
return std::move(mesh);
}
if (extension == "stl") {
// STL file format.
StlDecoder stl_decoder;
return stl_decoder.DecodeFromFile(file_name);
}
#ifdef DRACO_TRANSCODER_SUPPORTED
if (extension == "gltf" || extension == "glb") {
GltfDecoder gltf_decoder;

View File

@ -0,0 +1,74 @@
// Copyright 2022 The Draco Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#include "draco/io/stl_decoder.h"
#include "draco/core/macros.h"
#include "draco/core/status.h"
#include "draco/core/status_or.h"
#include "draco/io/file_utils.h"
#include "draco/mesh/triangle_soup_mesh_builder.h"
namespace draco {
StatusOr<std::unique_ptr<Mesh>> StlDecoder::DecodeFromFile(
const std::string &file_name) {
std::vector<char> data;
if (!ReadFileToBuffer(file_name, &data)) {
return Status(Status::IO_ERROR, "Unable to read input file.");
}
DecoderBuffer buffer;
buffer.Init(data.data(), data.size());
return DecodeFromBuffer(&buffer);
}
StatusOr<std::unique_ptr<Mesh>> StlDecoder::DecodeFromBuffer(
DecoderBuffer *buffer) {
if (!strncmp(buffer->data_head() , "solid ", 6)) {
return Status(Status::IO_ERROR,
"Currently only binary STL files are supported.");
}
buffer->Advance(80);
uint32_t face_count;
buffer->Decode(&face_count, 4);
TriangleSoupMeshBuilder builder;
builder.Start(face_count);
const int32_t pos_att_id = builder.AddAttribute(GeometryAttribute::POSITION,
3, DT_FLOAT32);
const int32_t norm_att_id = builder.AddAttribute(GeometryAttribute::NORMAL,
3, DT_FLOAT32);
for (uint32_t i = 0; i < face_count; i++) {
float data[48];
buffer->Decode(data, 48);
uint16_t unused;
buffer->Decode(&unused, 2);
builder.SetPerFaceAttributeValueForFace(norm_att_id, draco::FaceIndex(i),
draco::Vector3f(data[0], data[1], data[2]).data());
builder.SetAttributeValuesForFace(pos_att_id, draco::FaceIndex(i),
draco::Vector3f(data[3], data[4], data[5]).data(),
draco::Vector3f(data[6], data[7], data[8]).data(),
draco::Vector3f(data[9], data[10], data[11]).data());
}
std::unique_ptr<Mesh> mesh = builder.Finalize();
return mesh;
}
} // namespace draco

View File

@ -0,0 +1,38 @@
// Copyright 2022 The Draco Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#ifndef DRACO_IO_STL_DECODER_H_
#define DRACO_IO_STL_DECODER_H_
#include <string>
#include "draco/core/decoder_buffer.h"
#include "draco/core/status.h"
#include "draco/core/status_or.h"
#include "draco/draco_features.h"
#include "draco/mesh/mesh.h"
namespace draco {
// Decodes an STL file into draco::Mesh (or draco::PointCloud if the
// connectivity data is not needed).
class StlDecoder {
public:
StatusOr<std::unique_ptr<Mesh>> DecodeFromFile(const std::string &file_name);
StatusOr<std::unique_ptr<Mesh>> DecodeFromBuffer(DecoderBuffer *buffer);
};
} // namespace draco
#endif // DRACO_IO_STL_DECODER_H_

View File

@ -0,0 +1,47 @@
// Copyright 2022 The Draco Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#include "draco/io/stl_decoder.h"
#include "draco/core/draco_test_base.h"
#include "draco/core/draco_test_utils.h"
namespace draco {
class StlDecoderTest : public ::testing::Test {
protected:
void test_decoding(const std::string &file_name) {
const std::string path = GetTestFileFullPath(file_name);
StlDecoder decoder;
DRACO_ASSIGN_OR_ASSERT(std::unique_ptr<Mesh> mesh,
decoder.DecodeFromFile(path));
ASSERT_GT(mesh->num_faces(), 0);
ASSERT_GT(mesh->num_points(), 0);
}
void test_decoding_should_fail(const std::string &file_name) {
StlDecoder decoder;
StatusOr<std::unique_ptr<Mesh>> statusOrMesh =
decoder.DecodeFromFile(GetTestFileFullPath(file_name));
ASSERT_FALSE(statusOrMesh.ok());
}
};
TEST_F(StlDecoderTest, TestStlDecoding) {
test_decoding("STL/bunny.stl");
test_decoding("STL/test_sphere.stl");
test_decoding_should_fail("STL/test_sphere_ascii.stl");
}
} // namespace draco

111
src/draco/io/stl_encoder.cc Normal file
View File

@ -0,0 +1,111 @@
// Copyright 2022 The Draco Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#include "draco/io/stl_encoder.h"
#include <memory>
#include <iomanip>
#include <sstream>
#include "draco/io/file_writer_factory.h"
#include "draco/io/file_writer_interface.h"
namespace draco {
StlEncoder::StlEncoder()
: out_buffer_(nullptr), in_point_cloud_(nullptr), in_mesh_(nullptr) {}
Status StlEncoder::EncodeToFile(const Mesh &mesh, const std::string &file_name) {
in_mesh_ = &mesh;
std::unique_ptr<FileWriterInterface> file =
FileWriterFactory::OpenWriter(file_name);
if (!file) {
return Status(Status::IO_ERROR, "File couldn't be opened");
}
// Encode the mesh into a buffer.
EncoderBuffer buffer;
DRACO_RETURN_IF_ERROR(EncodeToBuffer(mesh, &buffer));
// Write the buffer into the file.
file->Write(buffer.data(), buffer.size());
return OkStatus();
}
Status StlEncoder::EncodeToBuffer(const Mesh &mesh, EncoderBuffer *out_buffer) {
in_mesh_ = &mesh;
out_buffer_ = out_buffer;
Status s = EncodeInternal();
in_mesh_ = nullptr; // cleanup
in_point_cloud_ = nullptr;
out_buffer_ = nullptr;
return s;
}
Status StlEncoder::EncodeInternal() {
// Write STL header.
std::stringstream out;
out << std::left << std::setw(80) << "generated using Draco"; // header is 80 bytes fixed size.
const std::string header_str = out.str();
buffer()->Encode(header_str.data(), header_str.length());
uint32_t num_faces = in_mesh_->num_faces();
buffer()->Encode(&num_faces, 4);
std::vector<uint8_t> stl_face;
const int pos_att_id =
in_mesh_->GetNamedAttributeId(GeometryAttribute::POSITION);
if (pos_att_id < 0) {
return ErrorStatus("Mesh is missing the position attribute.");
}
if (in_mesh_->attribute(pos_att_id)->data_type() !=
DT_FLOAT32) {
return ErrorStatus("Mesh position attribute is not of type float32.");
}
uint16_t unused = 0;
if (in_mesh_) {
for (FaceIndex i(0); i < in_mesh_->num_faces(); ++i) {
const auto &f = in_mesh_->face(i);
const auto *const pos_att = in_mesh_->attribute(pos_att_id);
// The normal attribute can contain arbitrary normals that may not
// correspond to the winding of the face.
// Therefor we simply always calculate them
// using the points of the triangle face: norm(cross(p2-p1, p3-p1))
Vector3f pos[3];
pos_att->GetMappedValue(f[0], &pos[0][0]);
pos_att->GetMappedValue(f[1], &pos[1][0]);
pos_att->GetMappedValue(f[2], &pos[2][0]);
Vector3f norm = CrossProduct(pos[1] - pos[0], pos[2] - pos[0]);
norm.Normalize();
buffer()->Encode(norm.data(), sizeof(float) * 3);
for (int c = 0; c < 3; ++c) {
buffer()->Encode(pos_att->GetAddress(pos_att->mapped_index(f[c])),
pos_att->byte_stride());
}
buffer()->Encode(&unused, 2);
}
}
return OkStatus();
}
} // namespace draco

View File

@ -0,0 +1,50 @@
// Copyright 2022 The Draco Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#ifndef DRACO_IO_STL_ENCODER_H_
#define DRACO_IO_STL_ENCODER_H_
#include "draco/core/encoder_buffer.h"
#include "draco/mesh/mesh.h"
namespace draco {
// Class for encoding draco::Mesh into the STL file format.
class StlEncoder {
public:
StlEncoder();
// Encodes the mesh and saves it into a file.
// Returns false when either the encoding failed or when the file couldn't be
// opened.
Status EncodeToFile(const Mesh &mesh, const std::string &file_name);
// Encodes the mesh into a buffer.
Status EncodeToBuffer(const Mesh &mesh, EncoderBuffer *out_buffer);
protected:
Status EncodeInternal();
EncoderBuffer *buffer() const { return out_buffer_; }
private:
EncoderBuffer *out_buffer_;
const PointCloud *in_point_cloud_;
const Mesh *in_mesh_;
};
} // namespace draco
#endif // DRACO_IO_STL_ENCODER_H_

View File

@ -0,0 +1,75 @@
// Copyright 2022 The Draco Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#include <sstream>
#include "draco/core/draco_test_base.h"
#include "draco/core/draco_test_utils.h"
#include "draco/io/file_reader_factory.h"
#include "draco/io/file_reader_interface.h"
#include "draco/io/stl_decoder.h"
#include "draco/io/stl_encoder.h"
namespace draco {
class StlEncoderTest : public ::testing::Test {
protected:
void CompareMeshes(const Mesh *mesh0, const Mesh *mesh1) {
ASSERT_EQ(mesh0->num_faces(), mesh1->num_faces());
ASSERT_EQ(mesh0->num_attributes(), mesh1->num_attributes());
for (size_t att_id = 0; att_id < mesh0->num_attributes(); ++att_id) {
ASSERT_EQ(mesh0->attribute(att_id)->size(),
mesh1->attribute(att_id)->size());
}
}
// Encode a mesh using the StlEncoder and then decode to verify the encoding.
std::unique_ptr<Mesh> EncodeAndDecodeMesh(const Mesh *mesh) {
EncoderBuffer encoder_buffer;
StlEncoder encoder;
Status status = encoder.EncodeToBuffer(*mesh, &encoder_buffer);
if (!status.ok()) {
return nullptr;
}
DecoderBuffer decoder_buffer;
decoder_buffer.Init(encoder_buffer.data(), encoder_buffer.size());
StlDecoder decoder;
StatusOr<std::unique_ptr<Mesh>> status_or_mesh =
decoder.DecodeFromBuffer(&decoder_buffer);
if (!status_or_mesh.ok()) {
return nullptr;
}
std::unique_ptr<Mesh> decoded_mesh = std::move(status_or_mesh).value();
return decoded_mesh;
}
void test_encoding(const std::string &file_name) {
const std::unique_ptr<Mesh> mesh(ReadMeshFromTestFile(file_name, true));
ASSERT_NE(mesh, nullptr) << "Failed to load test model " << file_name;
ASSERT_GT(mesh->num_faces(), 0);
const std::unique_ptr<Mesh> decoded_mesh = EncodeAndDecodeMesh(mesh.get());
CompareMeshes(mesh.get(), decoded_mesh.get());
}
};
TEST_F(StlEncoderTest, TestStlEncoding) {
// Test decoded mesh from encoded stl file stays the same.
test_encoding("STL/bunny.stl");
test_encoding("STL/test_sphere.stl");
}
} // namespace draco

View File

@ -20,6 +20,7 @@
#include "draco/io/obj_encoder.h"
#include "draco/io/parser_utils.h"
#include "draco/io/ply_encoder.h"
#include "draco/io/stl_encoder.h"
namespace {
@ -157,8 +158,20 @@ int main(int argc, char **argv) {
return -1;
}
}
} else if (extension == ".stl") {
draco::StlEncoder stl_encoder;
if (mesh) {
draco::Status s = stl_encoder.EncodeToFile(*mesh, options.output);
if (s.code() != draco::Status::OK) {
printf("Failed to store the decoded mesh as STL.\n");
return -1;
}
} else {
printf("Can't store a point cloud as STL.\n");
return -1;
}
} else {
printf("Invalid extension of the output file. Use either .ply or .obj.\n");
printf("Invalid extension of the output file. Use either .ply, .stl or .obj.\n");
return -1;
}
printf("Decoded geometry saved to %s (%" PRId64 " ms to decode)\n",

BIN
testdata/STL/bunny.stl vendored Normal file

Binary file not shown.

BIN
testdata/STL/test_sphere.stl vendored Normal file

Binary file not shown.

1570
testdata/STL/test_sphere_ascii.stl vendored Normal file

File diff suppressed because it is too large Load Diff