mirror of
https://git.mirrors.martin98.com/https://github.com/google/draco
synced 2025-04-20 12:49:54 +08:00
Add binary STL support. (#802)
Includes support for binary STL and some basic tests.
This commit is contained in:
parent
7d62f1f26f
commit
befe2d8809
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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;
|
||||
|
74
src/draco/io/stl_decoder.cc
Normal file
74
src/draco/io/stl_decoder.cc
Normal 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
|
38
src/draco/io/stl_decoder.h
Normal file
38
src/draco/io/stl_decoder.h
Normal 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_
|
47
src/draco/io/stl_decoder_test.cc
Normal file
47
src/draco/io/stl_decoder_test.cc
Normal 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
111
src/draco/io/stl_encoder.cc
Normal 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
|
50
src/draco/io/stl_encoder.h
Normal file
50
src/draco/io/stl_encoder.h
Normal 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_
|
75
src/draco/io/stl_encoder_test.cc
Normal file
75
src/draco/io/stl_encoder_test.cc
Normal 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
|
@ -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
BIN
testdata/STL/bunny.stl
vendored
Normal file
Binary file not shown.
BIN
testdata/STL/test_sphere.stl
vendored
Normal file
BIN
testdata/STL/test_sphere.stl
vendored
Normal file
Binary file not shown.
1570
testdata/STL/test_sphere_ascii.stl
vendored
Normal file
1570
testdata/STL/test_sphere_ascii.stl
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user