Skip to content
Merged
Show file tree
Hide file tree
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
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,12 @@ Read/write meshes and point clouds from some common formats.
- `read_mesh(filename)` Reads a mesh from file. Returns numpy matrices `V, F`, a Nx3 real numpy array of vertices and a Mx3 integer numpy array of 0-based face indices (or Mx4 for a quad mesh, etc).
- `filename` the path to read the file from. Currently supports the same file types as [geometry-central](http://geometry-central.net/surface/utilities/io/#supported-file-types). The file type is inferred automatically from the path extension.

- `write_mesh(V, F, filename)` Write a mesh to file.
- `write_mesh(V, F, filename, UV_coords=None, UV_type=None)` Write a mesh to file, optionally with UV coords.
- `V` a Nx3 real numpy array of vertices
- `F` a Mx3 integer numpy array of faces, with 0-based vertex indices (or Mx4 for a quad mesh, etc).
- `filename` the path to write the file to. Currently supports the same file types as [geometry-central](http://geometry-central.net/surface/utilities/io/#supported-file-types). The file type is inferred automatically from the path extension.
- `UV_coords` (optional) a Ux2 numpy array of UV coords, interpreted based on UV_type. *Warning:* this function does not currently preserve shared UV indices when writing, each written coordinate is independent
- `UV_type` (optional) string, one of `'per-vertex'`, `'per-face'`, or `'per-corner'`. The size of `U` should be `N`, `M`, or `M*3/4`, respectively

- `read_point_cloud(filename)` Reads a point cloud from file. Returns numpy matrix `V`, a Nx3 real numpy array of vertices. Really, this just reads a mesh file and ignores the face entries.
- `filename` the path to read the file from. Currently supports the same file types as [geometry-central](http://geometry-central.net/surface/utilities/io/#supported-file-types)'s mesh reader. The file type is inferred automatically from the path extension.
Expand Down
82 changes: 77 additions & 5 deletions src/cpp/io.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,9 @@ std::tuple<DenseMatrix<double>, DenseMatrix<int64_t>> read_mesh(std::string file
return std::make_tuple(V, F);
}

void write_mesh(DenseMatrix<double> verts, DenseMatrix<int64_t> faces, std::string filename) {

// Copy in to the mesh object
namespace { // anonymous helers
SimplePolygonMesh buildMesh(const DenseMatrix<double>& verts, const DenseMatrix<int64_t>& faces,
const DenseMatrix<double>& corner_UVs) {
std::vector<Vector3> coords(verts.rows());
for (size_t i = 0; i < verts.rows(); i++) {
for (size_t j = 0; j < 3; j++) {
Expand All @@ -73,10 +73,78 @@ void write_mesh(DenseMatrix<double> verts, DenseMatrix<int64_t> faces, std::stri
polys[i][j] = faces(i, j);
}
}
std::vector<std::vector<Vector2>> corner_params;
if (corner_UVs.size() > 0) {
corner_params.resize(faces.rows());
for (size_t i = 0; i < faces.rows(); i++) {
corner_params[i].resize(faces.cols());
for (size_t j = 0; j < faces.cols(); j++) {
size_t ind = i * faces.cols() + j;
for (size_t k = 0; k < 2; k++) {
corner_params[i][j][k] = corner_UVs(ind, k);
}
}
}
}

return SimplePolygonMesh(polys, coords, corner_params);
}
SimplePolygonMesh buildMesh(const DenseMatrix<double>& verts, const DenseMatrix<int64_t>& faces) {
DenseMatrix<double> empty_UVs = DenseMatrix<double>::Zero(0, 2);
return buildMesh(verts, faces, empty_UVs);
}
} // namespace

SimplePolygonMesh pmesh(polys, coords);
void write_mesh(DenseMatrix<double> verts, DenseMatrix<int64_t> faces, std::string filename) {
SimplePolygonMesh pmesh = buildMesh(verts, faces);
pmesh.writeMesh(filename);
}

void write_mesh_pervertex_uv(DenseMatrix<double> verts, DenseMatrix<int64_t> faces, DenseMatrix<double> UVs,
std::string filename) {
size_t V = verts.rows();
size_t F = faces.rows();
size_t D = faces.cols();

// expand out to per-corner UVs
DenseMatrix<double> face_UVs = DenseMatrix<double>::Zero(F * D, 2);
for (size_t i = 0; i < F; i++) {
for (size_t j = 0; j < D; j++) {
size_t vInd = faces(i, j);
for (size_t k = 0; k < 2; k++) {
face_UVs(i * D + j, k) = UVs(vInd, k);
}
}
}

// Call the mesh writer
SimplePolygonMesh pmesh = buildMesh(verts, faces, face_UVs);
pmesh.writeMesh(filename);
}

void write_mesh_perface_uv(DenseMatrix<double> verts, DenseMatrix<int64_t> faces, DenseMatrix<double> UVs,
std::string filename) {

size_t V = verts.rows();
size_t F = faces.rows();
size_t D = faces.cols();

// expand out to per-corner UVs
DenseMatrix<double> face_UVs = DenseMatrix<double>::Zero(F * D, 2);
for (size_t i = 0; i < F; i++) {
for (size_t j = 0; j < D; j++) {
for (size_t k = 0; k < 2; k++) {
face_UVs(i * D + j, k) = UVs(i, k);
}
}
}

SimplePolygonMesh pmesh = buildMesh(verts, faces, face_UVs);
pmesh.writeMesh(filename);
}

void write_mesh_percorner_uv(DenseMatrix<double> verts, DenseMatrix<int64_t> faces, DenseMatrix<double> UVs,
std::string filename) {
SimplePolygonMesh pmesh = buildMesh(verts, faces, UVs);
pmesh.writeMesh(filename);
}

Expand Down Expand Up @@ -110,7 +178,11 @@ void write_point_cloud(DenseMatrix<double> points, std::string filename) {
void bind_io(py::module& m) {

m.def("read_mesh", &read_mesh, "Read a mesh from file.", py::arg("filename"));

m.def("write_mesh", &write_mesh, "Write a mesh to file.", py::arg("verts"), py::arg("faces"), py::arg("filename"));
m.def("write_mesh_pervertex_uv", &write_mesh_pervertex_uv, "Write a mesh to file.", py::arg("verts"), py::arg("faces"), py::arg("UVs"), py::arg("filename"));
m.def("write_mesh_perface_uv", &write_mesh_perface_uv, "Write a mesh to file.", py::arg("verts"), py::arg("faces"), py::arg("UVs"), py::arg("filename"));
m.def("write_mesh_percorner_uv", &write_mesh_percorner_uv, "Write a mesh to file.", py::arg("verts"), py::arg("faces"), py::arg("UVs"), py::arg("filename"));

m.def("read_point_cloud", &read_point_cloud, "Read a point cloud from file.", py::arg("filename"));
m.def("write_point_cloud", &write_point_cloud, "Write a point cloud to file.", py::arg("points"), py::arg("filename"));
Expand Down
35 changes: 33 additions & 2 deletions src/potpourri3d/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,40 @@ def read_mesh(filename):
F = np.ascontiguousarray(F)
return V, F

def write_mesh(V, F, filename):
def write_mesh(V, F, filename, UV_coords=None, UV_type=None):
# TODO generalize this to take indexed UVs
# (the underlying geometry-central writer needs to support it first)

validate_mesh(V, F, test_indices=True)
pp3db.write_mesh(V, F, filename)

if UV_type is None:

pp3db.write_mesh(V, F, filename)

elif UV_type == 'per-vertex':

if len(UV_coords.shape) != 2 or UV_coords.shape[0] != V.shape[0] or UV_coords.shape[1] != 2:
raise ValueError("UV_coords should be a 2d Vx2 numpy array")

pp3db.write_mesh_pervertex_uv(V, F, UV_coords, filename)

elif UV_type == 'per-face':

if len(UV_coords.shape) != 2 or UV_coords.shape[0] != F.shape[0] or UV_coords.shape[1] != 2:
raise ValueError("UV_coords should be a 2d Fx2 numpy array")

pp3db.write_mesh_perface_uv(V, F, UV_coords, filename)

elif UV_type == 'per-corner':

if len(UV_coords.shape) != 2 or UV_coords.shape[0] != F.shape[0]*F.shape[1] or UV_coords.shape[1] != 2:
raise ValueError("UV_coords should be a 2d Fx2 numpy array")

pp3db.write_mesh_percorner_uv(V, F, UV_coords, filename)

else:
raise ValueError(f"unrecognized value for UV_type: {UV_type}. Should be one of: [None, 'per-vertex', 'per-face', 'per-corner']")


def read_point_cloud(filename):
V = pp3db.read_point_cloud(filename)
Expand Down
11 changes: 11 additions & 0 deletions test/potpourri3d_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,17 @@ def test_write_read_mesh(self):

self.assertLess(np.amax(np.abs(V-Vnew)), 1e-6)
self.assertTrue((F==Fnew).all())


# smoke test various UV writers
UV_vert = V[:,:2]
pp3d.write_mesh(V,F,fname,UV_coords=UV_vert, UV_type='per-vertex')

UV_face = F[:,:2] * .3
pp3d.write_mesh(V,F,fname,UV_coords=UV_face, UV_type='per-face')

UV_corner = np.zeros((F.shape[0]*F.shape[1],2))
pp3d.write_mesh(V,F,fname,UV_coords=UV_corner, UV_type='per-corner')

def test_write_read_point_cloud(self):

Expand Down