Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v0.0.18 | Supports Texture Coordinates and Normals #17

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .DS_Store
Binary file not shown.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
DracoPy.egg-info/
MANIFEST
_skbuild/
venv/*
.eggs/*
*.so
.idea/
.vscode/
dist/
bunny_test.drc
*secret.json
__pycache__/
__pycache__/
10 changes: 6 additions & 4 deletions example.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import os
import DracoPy

with open('bunny.drc', 'rb') as draco_file:
testdata_directory = "testdata_files"

with open(os.path.join(testdata_directory, "Avocado.bin"), "rb") as draco_file:
file_content = draco_file.read()
mesh_object = DracoPy.decode_buffer_to_mesh(file_content)
print('number of points in original file: {0}'.format(len(mesh_object.points)))
print('number of faces in original file: {0}'.format(len(mesh_object.faces)))
encoding_test = DracoPy.encode_mesh_to_buffer(mesh_object.points, mesh_object.faces)
print('number of faces in original file: {0}'.format(len(mesh_object.tex_coord)))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be the number of texture coordinates?

print('number of normal in original file: {0}'.format(len(mesh_object.normals)))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

normal -> normals

encoding_test = DracoPy.encode_mesh_to_buffer(mesh_object.points, mesh_object.face)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

faces?

with open('bunny_test.drc', 'wb') as test_file:
test_file.write(encoding_test)

11 changes: 5 additions & 6 deletions setup.py
Original file line number Diff line number Diff line change
@@ -15,10 +15,10 @@ def read(fname):
setup_requires = []
try:
cmake_version = LegacyVersion(get_cmake_version())
if cmake_version < LegacyVersion("3.5") or cmake_version >= LegacyVersion("3.15"):
setup_requires.append('cmake<3.15')
if cmake_version < LegacyVersion("3.5") or cmake_version >= LegacyVersion("3.20"):
setup_requires.append('cmake<3.20')
except SKBuildError:
setup_requires.append('cmake<3.15')
setup_requires.append('cmake<3.20')

# If you want to re-build the cython cpp file (DracoPy.cpp), run:
# cython --cplus -3 -I./_skbuild/linux-x86_64-3.6/cmake-install/include/draco/ ./src/DracoPy.pyx
@@ -50,12 +50,11 @@ def read(fname):
'-std=c++11','-O3'
]


setup(
name='DracoPy',
version='0.0.19',
description = 'Python wrapper for Google\'s Draco Mesh Compression Library',
author = 'Manuel Castro',
author = 'Manuel Castro :: Contributors :: Fatih Erol, Faru Nuri Sonmez',
author_email = 'macastro@princeton.edu',
url = 'https://github.com/seung-lab/DracoPy',
long_description=read('README.md'),
@@ -69,7 +68,7 @@ def read(fname):
setuptools.Extension(
'DracoPy',
sources=[ os.path.join(src_dir, 'DracoPy.cpp') ],
depends=[ os.path.join(src_dir, 'DracoPy.h') ],
depends=[ os.path.join(src_dir, 'DracoPy.h'), os.path.join(src_dir, 'DracoPy.pyx')],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is right. The pyx is only necessary for generating the DracoPy.cpp files

language='c++',
include_dirs = [ os.path.join(CMAKE_INSTALL_DIR(), 'include/')],
extra_compile_args=extra_compile_args,
1,172 changes: 654 additions & 518 deletions src/DracoPy.cpp

Large diffs are not rendered by default.

71 changes: 64 additions & 7 deletions src/DracoPy.h
Original file line number Diff line number Diff line change
@@ -8,8 +8,7 @@
#include "draco/point_cloud/point_cloud_builder.h"

namespace DracoFunctions {

enum decoding_status { successful, not_draco_encoded, no_position_attribute, failed_during_decoding };
enum decoding_status { successful, not_draco_encoded, no_position_attribute, failed_during_decoding, no_tex_coord_attribute, no_normal_coord_attribute };
enum encoding_status { successful_encoding, failed_during_encoding };

struct PointCloudObject {
@@ -27,6 +26,7 @@ namespace DracoFunctions {
struct MeshObject : PointCloudObject {
std::vector<float> normals;
std::vector<unsigned int> faces;
std::vector<float> tex_coord;
};

struct EncodedObject {
@@ -35,11 +35,18 @@ namespace DracoFunctions {
};

MeshObject decode_buffer(const char *buffer, std::size_t buffer_len) {

std::cout << "Decode Buffer" << "Begin" << std::endl;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please either hide the std::couts behind a verbose flag or remove them. This library is often used in large deployments of thousands of CPUs.


MeshObject meshObject;
draco::DecoderBuffer decoderBuffer;
decoderBuffer.Init(buffer, buffer_len);
draco::Decoder decoder;

auto statusor = decoder.DecodeMeshFromBuffer(&decoderBuffer);

std::cout << "Decode Buffer " << "Status : "<< std::boolalpha << statusor.ok() << std::endl;

if (!statusor.ok()) {
std::string status_string = statusor.status().error_msg_string();
if (status_string.compare("Not a Draco file.") || status_string.compare("Failed to parse Draco header.")) {
@@ -50,15 +57,27 @@ namespace DracoFunctions {
}
return meshObject;
}

std::unique_ptr<draco::Mesh> in_mesh = std::move(statusor).value();
draco::Mesh *mesh = in_mesh.get();
//Position Attribute ID
const int pos_att_id = mesh->GetNamedAttributeId(draco::GeometryAttribute::POSITION);
std::cout << "Decode Buffer " << "Attribute Position : " << pos_att_id << std::endl;



if (pos_att_id < 0) {
meshObject.decode_status = no_position_attribute;
return meshObject;
}

std::cout << "Decode Buffer " << "Mesh Number Points : " << 3 * mesh->num_points() << std::endl;
meshObject.points.reserve(3 * mesh->num_points());
std::cout << "Decode Buffer " << "Mesh Number Faces : " << 3 * mesh->num_faces() << std::endl;
meshObject.faces.reserve(3 * mesh->num_faces());



const auto *const pos_att = mesh->attribute(pos_att_id);
std::array<float, 3> pos_val;
for (draco::PointIndex v(0); v < mesh->num_points(); ++v) {
@@ -76,6 +95,40 @@ namespace DracoFunctions {
meshObject.faces.push_back(*(reinterpret_cast<const uint32_t *>(&(f[1]))));
meshObject.faces.push_back(*(reinterpret_cast<const uint32_t *>(&(f[2]))));
}

const int tex_att_id = mesh->GetNamedAttributeId(draco::GeometryAttribute::TEX_COORD);
if(tex_att_id >= 0) {

const auto *const tex_att = mesh->attribute(tex_att_id);
meshObject.tex_coord.reserve(tex_att->size());
std::array<float, 2> tex_val;
std::cout << "Decode Buffer " << "Attribute Tex Coord : " << tex_att->size() << std::endl;

for (draco::PointIndex v(0); v < tex_att->size(); ++v) {
if (!tex_att->ConvertValue<float, 2>(tex_att->mapped_index(v), &tex_val[0])) {
std::cout << "Convert Error" << std::endl;
meshObject.decode_status = no_tex_coord_attribute;
}
meshObject.tex_coord.push_back(tex_val[0]);
meshObject.tex_coord.push_back(tex_val[1]);
}
}

const int normal_att_id = mesh->GetNamedAttributeId(draco::GeometryAttribute::NORMAL);
const auto *const normal_att = mesh->attribute(normal_att_id);
meshObject.normals.reserve(normal_att->size());
std::array<float, 3> normal_val;
std::cout << "Decode Buffer" << "Attribute Normal Coord : " << normal_att->size() << std::endl;
for (draco::PointIndex v(0); v < normal_att->size(); ++v){
if (!normal_att->ConvertValue<float, 3>(normal_att->mapped_index(v), &normal_val[0])){
std::cout << "Convert Error" << std::endl;
meshObject.decode_status = no_normal_coord_attribute;
}
meshObject.normals.push_back(normal_val[0]);
meshObject.normals.push_back(normal_val[1]);
meshObject.normals.push_back(normal_val[3]);
}

const draco::GeometryMetadata *metadata = mesh->GetMetadata();
meshObject.encoding_options_set = false;
if (metadata) {
@@ -161,12 +214,13 @@ namespace DracoFunctions {
}
}

EncodedObject encode_mesh(const std::vector<float> &points, const std::vector<unsigned int> &faces,
int quantization_bits, int compression_level, float quantization_range, const float *quantization_origin, bool create_metadata) {
EncodedObject encode_mesh(const std::vector<float> &points, const std::vector<unsigned int> &faces, const std::vector<float> &normals, int quantization_bits,
int compression_level, float quantization_range, const float *quantization_origin, bool create_metadata) {
draco::TriangleSoupMeshBuilder mb;
mb.Start(faces.size());
const int pos_att_id =
mb.AddAttribute(draco::GeometryAttribute::POSITION, 3, draco::DataType::DT_FLOAT32);

const int pos_att_id = mb.AddAttribute(draco::GeometryAttribute::POSITION, 3, draco::DataType::DT_FLOAT32); //attribute_type, num_components, data_type
std::cout << "POSITION :" << pos_att_id << std::endl;

for (std::size_t i = 0; i <= faces.size() - 3; i += 3) {
auto point1Index = faces[i]*3;
@@ -175,6 +229,9 @@ namespace DracoFunctions {
mb.SetAttributeValuesForFace(pos_att_id, draco::FaceIndex(i), draco::Vector3f(points[point1Index], points[point1Index+1], points[point1Index+2]).data(), draco::Vector3f(points[point2Index], points[point2Index+1], points[point2Index+2]).data(), draco::Vector3f(points[point3Index], points[point3Index+1], points[point3Index+2]).data());
}

const int tex_att_id = mb.AddAttribute(draco::GeometryAttribute::TEX_COORD, 2, draco::DataType::DT_FLOAT32);
std::cout << "TEX_COORD :" << tex_att_id << std::endl;

std::unique_ptr<draco::Mesh> ptr_mesh = mb.Finalize();
draco::Mesh *mesh = ptr_mesh.get();
draco::Encoder encoder;
@@ -183,6 +240,7 @@ namespace DracoFunctions {
const draco::Status status = encoder.EncodeMeshToBuffer(*mesh, &buffer);
EncodedObject encodedMeshObject;
encodedMeshObject.buffer = *((std::vector<unsigned char> *)buffer.buffer());

if (status.ok()) {
encodedMeshObject.encode_status = successful_encoding;
} else {
@@ -199,7 +257,6 @@ namespace DracoFunctions {
pcb.Start(num_points);
const int pos_att_id =
pcb.AddAttribute(draco::GeometryAttribute::POSITION, 3, draco::DataType::DT_FLOAT32);

for (draco::PointIndex i(0); i < num_points; i++) {
pcb.SetAttributeValueForPoint(pos_att_id, i, points.data() + 3 * i.value());
}
7 changes: 3 additions & 4 deletions src/DracoPy.pxd
Original file line number Diff line number Diff line change
@@ -26,9 +26,8 @@ cdef extern from "DracoPy.h" namespace "DracoFunctions":
cdef struct MeshObject:
vector[float] points
vector[unsigned int] faces

# TODO: add support for normals, which are not currently supported.
vector[float] normals
vector[float] tex_coord

# Encoding options
bool encoding_options_set
@@ -47,8 +46,8 @@ cdef extern from "DracoPy.h" namespace "DracoFunctions":

PointCloudObject decode_buffer_to_point_cloud(const char *buffer, size_t buffer_len) except +

EncodedObject encode_mesh(vector[float] points, vector[uint32_t] faces, int quantization_bits,
EncodedObject encode_mesh(vector[float] points, vector[uint32_t] faces, vector[float] normals, int quantization_bits,
int compression_level, float quantization_range, const float *quantization_origin, bool create_metadata) except +

EncodedObject encode_point_cloud(vector[float] points, int quantization_bits,
int compression_level, float quantization_range, const float *quantization_origin, bool create_metadata) except +
8 changes: 6 additions & 2 deletions src/DracoPy.pyx
Original file line number Diff line number Diff line change
@@ -40,6 +40,10 @@ class DracoMesh(DracoPointCloud):
def normals(self):
return self.data_struct['normals']

@property
def tex_coord(self):
return self.data_struct['tex_coord']

class EncodingOptions(object):
def __init__(self, quantization_bits, quantization_range, quantization_origin):
self.quantization_bits = quantization_bits
@@ -70,7 +74,7 @@ class FileTypeException(Exception):
class EncodingFailedException(Exception):
pass

def encode_mesh_to_buffer(points, faces, quantization_bits=14, compression_level=1, quantization_range=-1, quantization_origin=None, create_metadata=False):
def encode_mesh_to_buffer(points, faces, normals, quantization_bits=14, compression_level=1, quantization_range=-1, quantization_origin=None, create_metadata=False):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a breaking change. Can normals be given a default value that is handled gracefully?

"""
Encode a list or numpy array of points/vertices (float) and faces (unsigned int) to a draco buffer.
Quantization bits should be an integer between 0 and 31
@@ -87,7 +91,7 @@ def encode_mesh_to_buffer(points, faces, quantization_bits=14, compression_level
quant_origin = <float *>PyMem_Malloc(sizeof(float) * num_dims)
for dim in range(num_dims):
quant_origin[dim] = quantization_origin[dim]
encoded_mesh = DracoPy.encode_mesh(points, faces, quantization_bits, compression_level, quantization_range, quant_origin, create_metadata)
encoded_mesh = DracoPy.encode_mesh(points, faces, normals, quantization_bits, compression_level, quantization_range, quant_origin, create_metadata)
if quant_origin != NULL:
PyMem_Free(quant_origin)
if encoded_mesh.encode_status == DracoPy.encoding_status.successful_encoding: