Skip to content

Commit befe2d8

Browse files
authored
Add binary STL support. (#802)
Includes support for binary STL and some basic tests.
1 parent 7d62f1f commit befe2d8

13 files changed

+1992
-2
lines changed

CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,10 @@ list(APPEND draco_io_sources
396396
"${draco_src_root}/io/ply_property_writer.h"
397397
"${draco_src_root}/io/ply_reader.cc"
398398
"${draco_src_root}/io/ply_reader.h"
399+
"${draco_src_root}/io/stl_decoder.cc"
400+
"${draco_src_root}/io/stl_decoder.h"
401+
"${draco_src_root}/io/stl_encoder.cc"
402+
"${draco_src_root}/io/stl_encoder.h"
399403
"${draco_src_root}/io/point_cloud_io.cc"
400404
"${draco_src_root}/io/point_cloud_io.h"
401405
"${draco_src_root}/io/stdio_file_reader.cc"

cmake/draco_tests.cmake

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ list(
6060
"${draco_src_root}/io/obj_encoder_test.cc"
6161
"${draco_src_root}/io/ply_decoder_test.cc"
6262
"${draco_src_root}/io/ply_reader_test.cc"
63+
"${draco_src_root}/io/stl_decoder_test.cc"
64+
"${draco_src_root}/io/stl_encoder_test.cc"
6365
"${draco_src_root}/io/point_cloud_io_test.cc"
6466
"${draco_src_root}/mesh/mesh_are_equivalent_test.cc"
6567
"${draco_src_root}/mesh/mesh_cleanup_test.cc"

src/draco/io/mesh_io.cc

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "draco/io/file_utils.h"
2121
#include "draco/io/obj_decoder.h"
2222
#include "draco/io/ply_decoder.h"
23+
#include "draco/io/stl_decoder.h"
2324
#ifdef DRACO_TRANSCODER_SUPPORTED
2425
#include "draco/compression/draco_compression_options.h"
2526
#include "draco/compression/encode.h"
@@ -71,11 +72,16 @@ StatusOr<std::unique_ptr<Mesh>> ReadMeshFromFile(
7172
return std::move(mesh);
7273
}
7374
if (extension == "ply") {
74-
// Wavefront PLY file format.
75+
// Stanford PLY file format.
7576
PlyDecoder ply_decoder;
7677
DRACO_RETURN_IF_ERROR(ply_decoder.DecodeFromFile(file_name, mesh.get()));
7778
return std::move(mesh);
7879
}
80+
if (extension == "stl") {
81+
// STL file format.
82+
StlDecoder stl_decoder;
83+
return stl_decoder.DecodeFromFile(file_name);
84+
}
7985
#ifdef DRACO_TRANSCODER_SUPPORTED
8086
if (extension == "gltf" || extension == "glb") {
8187
GltfDecoder gltf_decoder;

src/draco/io/stl_decoder.cc

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Copyright 2022 The Draco Authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
//
15+
#include "draco/io/stl_decoder.h"
16+
17+
#include "draco/core/macros.h"
18+
#include "draco/core/status.h"
19+
#include "draco/core/status_or.h"
20+
#include "draco/io/file_utils.h"
21+
#include "draco/mesh/triangle_soup_mesh_builder.h"
22+
23+
namespace draco {
24+
25+
StatusOr<std::unique_ptr<Mesh>> StlDecoder::DecodeFromFile(
26+
const std::string &file_name) {
27+
std::vector<char> data;
28+
if (!ReadFileToBuffer(file_name, &data)) {
29+
return Status(Status::IO_ERROR, "Unable to read input file.");
30+
}
31+
DecoderBuffer buffer;
32+
buffer.Init(data.data(), data.size());
33+
return DecodeFromBuffer(&buffer);
34+
}
35+
36+
StatusOr<std::unique_ptr<Mesh>> StlDecoder::DecodeFromBuffer(
37+
DecoderBuffer *buffer) {
38+
if (!strncmp(buffer->data_head() , "solid ", 6)) {
39+
return Status(Status::IO_ERROR,
40+
"Currently only binary STL files are supported.");
41+
}
42+
buffer->Advance(80);
43+
uint32_t face_count;
44+
buffer->Decode(&face_count, 4);
45+
46+
TriangleSoupMeshBuilder builder;
47+
builder.Start(face_count);
48+
49+
const int32_t pos_att_id = builder.AddAttribute(GeometryAttribute::POSITION,
50+
3, DT_FLOAT32);
51+
const int32_t norm_att_id = builder.AddAttribute(GeometryAttribute::NORMAL,
52+
3, DT_FLOAT32);
53+
54+
for (uint32_t i = 0; i < face_count; i++) {
55+
float data[48];
56+
buffer->Decode(data, 48);
57+
uint16_t unused;
58+
buffer->Decode(&unused, 2);
59+
60+
builder.SetPerFaceAttributeValueForFace(norm_att_id, draco::FaceIndex(i),
61+
draco::Vector3f(data[0], data[1], data[2]).data());
62+
63+
builder.SetAttributeValuesForFace(pos_att_id, draco::FaceIndex(i),
64+
draco::Vector3f(data[3], data[4], data[5]).data(),
65+
draco::Vector3f(data[6], data[7], data[8]).data(),
66+
draco::Vector3f(data[9], data[10], data[11]).data());
67+
68+
}
69+
70+
std::unique_ptr<Mesh> mesh = builder.Finalize();
71+
return mesh;
72+
}
73+
74+
} // namespace draco

src/draco/io/stl_decoder.h

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright 2022 The Draco Authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
//
15+
#ifndef DRACO_IO_STL_DECODER_H_
16+
#define DRACO_IO_STL_DECODER_H_
17+
18+
#include <string>
19+
20+
#include "draco/core/decoder_buffer.h"
21+
#include "draco/core/status.h"
22+
#include "draco/core/status_or.h"
23+
#include "draco/draco_features.h"
24+
#include "draco/mesh/mesh.h"
25+
26+
namespace draco {
27+
28+
// Decodes an STL file into draco::Mesh (or draco::PointCloud if the
29+
// connectivity data is not needed).
30+
class StlDecoder {
31+
public:
32+
StatusOr<std::unique_ptr<Mesh>> DecodeFromFile(const std::string &file_name);
33+
StatusOr<std::unique_ptr<Mesh>> DecodeFromBuffer(DecoderBuffer *buffer);
34+
};
35+
36+
} // namespace draco
37+
38+
#endif // DRACO_IO_STL_DECODER_H_

src/draco/io/stl_decoder_test.cc

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright 2022 The Draco Authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
//
15+
#include "draco/io/stl_decoder.h"
16+
#include "draco/core/draco_test_base.h"
17+
#include "draco/core/draco_test_utils.h"
18+
19+
namespace draco {
20+
21+
class StlDecoderTest : public ::testing::Test {
22+
protected:
23+
24+
void test_decoding(const std::string &file_name) {
25+
const std::string path = GetTestFileFullPath(file_name);
26+
StlDecoder decoder;
27+
DRACO_ASSIGN_OR_ASSERT(std::unique_ptr<Mesh> mesh,
28+
decoder.DecodeFromFile(path));
29+
ASSERT_GT(mesh->num_faces(), 0);
30+
ASSERT_GT(mesh->num_points(), 0);
31+
}
32+
33+
void test_decoding_should_fail(const std::string &file_name) {
34+
StlDecoder decoder;
35+
StatusOr<std::unique_ptr<Mesh>> statusOrMesh =
36+
decoder.DecodeFromFile(GetTestFileFullPath(file_name));
37+
ASSERT_FALSE(statusOrMesh.ok());
38+
}
39+
};
40+
41+
TEST_F(StlDecoderTest, TestStlDecoding) {
42+
test_decoding("STL/bunny.stl");
43+
test_decoding("STL/test_sphere.stl");
44+
test_decoding_should_fail("STL/test_sphere_ascii.stl");
45+
}
46+
47+
} // namespace draco

src/draco/io/stl_encoder.cc

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// Copyright 2022 The Draco Authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
//
15+
#include "draco/io/stl_encoder.h"
16+
17+
#include <memory>
18+
#include <iomanip>
19+
#include <sstream>
20+
21+
#include "draco/io/file_writer_factory.h"
22+
#include "draco/io/file_writer_interface.h"
23+
24+
namespace draco {
25+
26+
StlEncoder::StlEncoder()
27+
: out_buffer_(nullptr), in_point_cloud_(nullptr), in_mesh_(nullptr) {}
28+
29+
Status StlEncoder::EncodeToFile(const Mesh &mesh, const std::string &file_name) {
30+
in_mesh_ = &mesh;
31+
std::unique_ptr<FileWriterInterface> file =
32+
FileWriterFactory::OpenWriter(file_name);
33+
if (!file) {
34+
return Status(Status::IO_ERROR, "File couldn't be opened");
35+
}
36+
// Encode the mesh into a buffer.
37+
EncoderBuffer buffer;
38+
DRACO_RETURN_IF_ERROR(EncodeToBuffer(mesh, &buffer));
39+
// Write the buffer into the file.
40+
file->Write(buffer.data(), buffer.size());
41+
return OkStatus();
42+
}
43+
44+
Status StlEncoder::EncodeToBuffer(const Mesh &mesh, EncoderBuffer *out_buffer) {
45+
in_mesh_ = &mesh;
46+
out_buffer_ = out_buffer;
47+
Status s = EncodeInternal();
48+
in_mesh_ = nullptr; // cleanup
49+
in_point_cloud_ = nullptr;
50+
out_buffer_ = nullptr;
51+
return s;
52+
}
53+
54+
Status StlEncoder::EncodeInternal() {
55+
// Write STL header.
56+
std::stringstream out;
57+
out << std::left << std::setw(80) << "generated using Draco"; // header is 80 bytes fixed size.
58+
const std::string header_str = out.str();
59+
buffer()->Encode(header_str.data(), header_str.length());
60+
61+
uint32_t num_faces = in_mesh_->num_faces();
62+
buffer()->Encode(&num_faces, 4);
63+
64+
std::vector<uint8_t> stl_face;
65+
66+
const int pos_att_id =
67+
in_mesh_->GetNamedAttributeId(GeometryAttribute::POSITION);
68+
69+
if (pos_att_id < 0) {
70+
return ErrorStatus("Mesh is missing the position attribute.");
71+
}
72+
73+
if (in_mesh_->attribute(pos_att_id)->data_type() !=
74+
DT_FLOAT32) {
75+
return ErrorStatus("Mesh position attribute is not of type float32.");
76+
}
77+
78+
uint16_t unused = 0;
79+
80+
if (in_mesh_) {
81+
for (FaceIndex i(0); i < in_mesh_->num_faces(); ++i) {
82+
83+
const auto &f = in_mesh_->face(i);
84+
const auto *const pos_att = in_mesh_->attribute(pos_att_id);
85+
86+
// The normal attribute can contain arbitrary normals that may not
87+
// correspond to the winding of the face.
88+
// Therefor we simply always calculate them
89+
// using the points of the triangle face: norm(cross(p2-p1, p3-p1))
90+
91+
Vector3f pos[3];
92+
pos_att->GetMappedValue(f[0], &pos[0][0]);
93+
pos_att->GetMappedValue(f[1], &pos[1][0]);
94+
pos_att->GetMappedValue(f[2], &pos[2][0]);
95+
Vector3f norm = CrossProduct(pos[1] - pos[0], pos[2] - pos[0]);
96+
norm.Normalize();
97+
buffer()->Encode(norm.data(), sizeof(float) * 3);
98+
99+
for (int c = 0; c < 3; ++c) {
100+
buffer()->Encode(pos_att->GetAddress(pos_att->mapped_index(f[c])),
101+
pos_att->byte_stride());
102+
}
103+
104+
buffer()->Encode(&unused, 2);
105+
106+
}
107+
}
108+
return OkStatus();
109+
}
110+
111+
} // namespace draco

src/draco/io/stl_encoder.h

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Copyright 2022 The Draco Authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
//
15+
#ifndef DRACO_IO_STL_ENCODER_H_
16+
#define DRACO_IO_STL_ENCODER_H_
17+
18+
#include "draco/core/encoder_buffer.h"
19+
#include "draco/mesh/mesh.h"
20+
21+
namespace draco {
22+
23+
// Class for encoding draco::Mesh into the STL file format.
24+
class StlEncoder {
25+
public:
26+
StlEncoder();
27+
28+
// Encodes the mesh and saves it into a file.
29+
// Returns false when either the encoding failed or when the file couldn't be
30+
// opened.
31+
Status EncodeToFile(const Mesh &mesh, const std::string &file_name);
32+
33+
// Encodes the mesh into a buffer.
34+
Status EncodeToBuffer(const Mesh &mesh, EncoderBuffer *out_buffer);
35+
36+
protected:
37+
Status EncodeInternal();
38+
EncoderBuffer *buffer() const { return out_buffer_; }
39+
40+
private:
41+
42+
EncoderBuffer *out_buffer_;
43+
44+
const PointCloud *in_point_cloud_;
45+
const Mesh *in_mesh_;
46+
};
47+
48+
} // namespace draco
49+
50+
#endif // DRACO_IO_STL_ENCODER_H_

0 commit comments

Comments
 (0)