Skip to content
4 changes: 4 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 2 additions & 0 deletions cmake/draco_tests.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
8 changes: 7 additions & 1 deletion src/draco/io/mesh_io.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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;
Expand Down
74 changes: 74 additions & 0 deletions src/draco/io/stl_decoder.cc
Original file line number Diff line number Diff line change
@@ -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);
Comment thread
rhulha marked this conversation as resolved.
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 changes: 38 additions & 0 deletions src/draco/io/stl_decoder.h
Original file line number Diff line number Diff line change
@@ -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 changes: 47 additions & 0 deletions src/draco/io/stl_decoder_test.cc
Original file line number Diff line number Diff line change
@@ -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 =
Comment thread
rhulha marked this conversation as resolved.
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 changes: 111 additions & 0 deletions src/draco/io/stl_encoder.cc
Original file line number Diff line number Diff line change
@@ -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());
Comment thread
rhulha marked this conversation as resolved.

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 changes: 50 additions & 0 deletions src/draco/io/stl_encoder.h
Original file line number Diff line number Diff line change
@@ -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_
Loading