From 63cc1528dd78f94c4f5ad8ba1fe4d49b6867e44d Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sun, 2 Apr 2023 15:51:44 -0700 Subject: [PATCH 01/88] update polyscope to latest --- deps/polyscope | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/polyscope b/deps/polyscope index 5b052a6..9d9519e 160000 --- a/deps/polyscope +++ b/deps/polyscope @@ -1 +1 @@ -Subproject commit 5b052a62bdc5fb020d7e1ae00cf834295c607f8c +Subproject commit 9d9519e283da58b72e6dfc9ad632b3d200ee05c3 From de9eee7c72adccbbb6d66d020d3483d7d22010af Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sun, 2 Apr 2023 22:41:59 -0700 Subject: [PATCH 02/88] update to latest --- deps/polyscope | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/polyscope b/deps/polyscope index 9d9519e..3c0b7d4 160000 --- a/deps/polyscope +++ b/deps/polyscope @@ -1 +1 @@ -Subproject commit 9d9519e283da58b72e6dfc9ad632b3d200ee05c3 +Subproject commit 3c0b7d413f4d7b9b09712d270dea08a39b13a772 From 2b95f144aa779b257a63197bd2d3b9abb816a762 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sun, 2 Apr 2023 22:42:24 -0700 Subject: [PATCH 03/88] update tangent vectors and perms --- src/cpp/core.cpp | 2 +- src/cpp/surface_mesh.cpp | 32 +++++--------- src/cpp/utils.h | 2 +- src/polyscope/surface_mesh.py | 55 ++++++------------------ test/polyscope_test.py | 79 +++++++++++++++++++---------------- 5 files changed, 68 insertions(+), 102 deletions(-) diff --git a/src/cpp/core.cpp b/src/cpp/core.cpp index 9446dfd..ebf2888 100644 --- a/src/cpp/core.cpp +++ b/src/cpp/core.cpp @@ -8,11 +8,11 @@ #include "polyscope/affine_remapper.h" #include "polyscope/curve_network.h" +#include "polyscope/messages.h" #include "polyscope/pick.h" #include "polyscope/point_cloud.h" #include "polyscope/polyscope.h" #include "polyscope/surface_mesh.h" -#include "polyscope/surface_parameterization_enums.h" #include "polyscope/view.h" #include "polyscope/volume_mesh.h" diff --git a/src/cpp/surface_mesh.cpp b/src/cpp/surface_mesh.cpp index 79ae9d2..2629d7a 100644 --- a/src/cpp/surface_mesh.cpp +++ b/src/cpp/surface_mesh.cpp @@ -48,12 +48,9 @@ void bind_surface_mesh(py::module& m) { // Vector quantities bindVectorQuantity(m, "SurfaceVertexVectorQuantity"); bindVectorQuantity(m, "SurfaceFaceVectorQuantity"); - bindVectorQuantity(m, "SurfaceVertexIntrinsicVectorQuantity") - .def("set_ribbon_enabled", &ps::SurfaceVertexIntrinsicVectorQuantity::setEnabled, "Set ribbon enabled"); - bindVectorQuantity(m, "SurfaceFaceIntrinsicVectorQuantity") - .def("set_ribbon_enabled", &ps::SurfaceFaceIntrinsicVectorQuantity::setEnabled, "Set ribbon enabled"); - bindVectorQuantity(m, "SurfaceOneFormIntrinsicVectorQuantity") - .def("set_ribbon_enabled", &ps::SurfaceOneFormIntrinsicVectorQuantity::setEnabled, "Set ribbon enabled"); + bindVectorQuantity(m, "SurfaceVertexTangentVectorQuantity"); + bindVectorQuantity(m, "SurfaceFaceTangentVectorQuantity"); + bindVectorQuantity(m, "SurfaceOneFormTangentVectorQuantity"); // == Main class @@ -94,19 +91,10 @@ void bind_surface_mesh(py::module& m) { // permutations & bases - .def("set_vertex_permutation", &ps::SurfaceMesh::setVertexPermutation, "Set vertex permutation") - .def("set_face_permutation", &ps::SurfaceMesh::setFacePermutation, "Set face permutation") .def("set_edge_permutation", &ps::SurfaceMesh::setEdgePermutation, "Set edge permutation") .def("set_halfedge_permutation", &ps::SurfaceMesh::setHalfedgePermutation, "Set halfedge permutation") .def("set_corner_permutation", &ps::SurfaceMesh::setCornerPermutation, "Set corner permutation") - .def("set_vertex_tangent_basisX", &ps::SurfaceMesh::setVertexTangentBasisX, - "Set vertex tangent bases") - .def("set_vertex_tangent_basisX2D", &ps::SurfaceMesh::setVertexTangentBasisX2D, - "Set vertex tangent bases") - .def("set_face_tangent_basisX", &ps::SurfaceMesh::setFaceTangentBasisX, "Set face tangent bases") - .def("set_face_tangent_basisX2D", &ps::SurfaceMesh::setFaceTangentBasisX2D, - "Set face tangent bases") // = quantities @@ -151,13 +139,13 @@ void bind_surface_mesh(py::module& m) { "Add a vertex 2D vector quantity", py::return_value_policy::reference) .def("add_face_vector_quantity2D", &ps::SurfaceMesh::addFaceVectorQuantity2D, "Add a face 2D vector quantity", py::return_value_policy::reference) - .def("add_vertex_intrinsic_vector_quantity", &ps::SurfaceMesh::addVertexIntrinsicVectorQuantity, - "Add a vertex intrinsic vector quantity", py::return_value_policy::reference) - .def("add_face_intrinsic_vector_quantity", &ps::SurfaceMesh::addFaceIntrinsicVectorQuantity, - "Add a face intrinsic vector quantity", py::return_value_policy::reference) - .def("add_one_form_intrinsic_vector_quantity", - &ps::SurfaceMesh::addOneFormIntrinsicVectorQuantity>, - "Add a one form intrinsic vector quantity", py::return_value_policy::reference); + .def("add_vertex_tangent_vector_quantity", &ps::SurfaceMesh::addVertexTangentVectorQuantity, + "Add a vertex tangent vector quantity", py::return_value_policy::reference) + .def("add_face_tangent_vector_quantity", &ps::SurfaceMesh::addFaceTangentVectorQuantity, + "Add a face tangent vector quantity", py::return_value_policy::reference) + .def("add_one_form_tangent_vector_quantity", + &ps::SurfaceMesh::addOneFormTangentVectorQuantity>, + "Add a one form tangent vector quantity", py::return_value_policy::reference); // Static adders and getters diff --git a/src/cpp/utils.h b/src/cpp/utils.h index cf6458c..2e91b3f 100644 --- a/src/cpp/utils.h +++ b/src/cpp/utils.h @@ -63,7 +63,7 @@ py::class_ bindStructure(py::module& m, std::string name) { // quantites .def("remove_all_quantities", &StructureT::removeAllQuantities, "Remove all quantities") - .def("remove_quantity", &StructureT::removeQuantity, "Remove a quantity") + .def("remove_quantity", &StructureT::removeQuantity, py::arg("name"), py::arg("errorIfAbsent") = false, "Remove a quantity") // transform management // clang-format off diff --git a/src/polyscope/surface_mesh.py b/src/polyscope/surface_mesh.py index 173c9ef..3288d28 100644 --- a/src/polyscope/surface_mesh.py +++ b/src/polyscope/surface_mesh.py @@ -180,16 +180,6 @@ def get_back_face_color(self): ## Permutations and bases - def set_vertex_permutation(self, perm, expected_size=None): - if len(perm.shape) != 1 or perm.shape[0] != self.n_vertices(): raise ValueError("'perm' should be an array with one entry per vertex") - if expected_size is None: expected_size = 0 - self.bound_mesh.set_vertex_permutation(perm, expected_size) - - def set_face_permutation(self, perm, expected_size=None): - if len(perm.shape) != 1 or perm.shape[0] != self.n_faces(): raise ValueError("'perm' should be an array with one entry per face") - if expected_size is None: expected_size = 0 - self.bound_mesh.set_face_permutation(perm, expected_size) - def set_edge_permutation(self, perm, expected_size=None): if len(perm.shape) != 1 or perm.shape[0] != self.n_edges(): raise ValueError("'perm' should be an array with one entry per edge") if expected_size is None: expected_size = 0 @@ -206,37 +196,16 @@ def set_halfedge_permutation(self, perm, expected_size=None): self.bound_mesh.set_halfedge_permutation(perm, expected_size) def set_all_permutations(self, - vertex_perm=None, vertex_perm_size=None, - face_perm=None, face_perm_size=None, + vertex_perm=None, vertex_perm_size=None, # now ignored + face_perm=None, face_perm_size=None, # now ignored edge_perm=None, edge_perm_size=None, corner_perm=None, corner_perm_size=None, halfedge_perm=None, halfedge_perm_size=None): - if vertex_perm is not None: self.set_vertex_permutation(vertex_perm, vertex_perm_size) - if face_perm is not None: self.set_face_permutation(face_perm, face_perm_size) if edge_perm is not None: self.set_edge_permutation(edge_perm, edge_perm_size) if corner_perm is not None: self.set_corner_permutation(corner_perm, corner_perm_size) if halfedge_perm is not None: self.set_halfedge_permutation(halfedge_perm, halfedge_perm_size) - def set_vertex_tangent_basisX(self, vectors): - if len(vectors.shape) != 2 or vectors.shape[0] != self.n_vertices() or vectors.shape[1] not in (2,3): - raise ValueError("'vectors' should be an array with one entry per vertex") - - if vectors.shape[1] == 2: - self.bound_mesh.set_vertex_tangent_basisX2D(vectors) - elif vectors.shape[1] == 3: - self.bound_mesh.set_vertex_tangent_basisX(vectors) - - def set_face_tangent_basisX(self, vectors): - if len(vectors.shape) != 2 or vectors.shape[0] != self.n_faces() or vectors.shape[1] not in (2,3): - raise ValueError("'vectors' should be an array with one entry per face") - - if vectors.shape[1] == 2: - self.bound_mesh.set_face_tangent_basisX2D(vectors) - elif vectors.shape[1] == 3: - self.bound_mesh.set_face_tangent_basisX(vectors) - - ## Quantities @@ -385,20 +354,26 @@ def add_vector_quantity(self, name, values, defined_on='vertices', enabled=None, q.set_color(glm3(color)) - def add_intrinsic_vector_quantity(self, name, values, n_sym=1, defined_on='vertices', enabled=None, vectortype="standard", length=None, radius=None, color=None, ribbon=None): + def add_tangent_vector_quantity(self, name, values, basisX, basisY, n_sym=1, defined_on='vertices', enabled=None, vectortype="standard", length=None, radius=None, color=None): if len(values.shape) != 2 or values.shape[1] != 2: raise ValueError("'values' should be an Nx2 array") + if len(basisX.shape) != 2 or basisX.shape[1] != 3: raise ValueError("'basisX' should be an Nx3 array") + if len(basisY.shape) != 2 or basisY.shape[1] != 3: raise ValueError("'basisY' should be an Nx3 array") if defined_on == 'vertices': if values.shape[0] != self.n_vertices(): raise ValueError("'values' should be a length n_vertices array") + if basisX.shape[0] != self.n_vertices(): raise ValueError("'basisX' should be a length n_vertices array") + if basisY.shape[0] != self.n_vertices(): raise ValueError("'basisY' should be a length n_vertices array") - q = self.bound_mesh.add_vertex_intrinsic_vector_quantity(name, values, n_sym, str_to_vectortype(vectortype)) + q = self.bound_mesh.add_vertex_tangent_vector_quantity(name, values, basisX, basisY, n_sym, str_to_vectortype(vectortype)) elif defined_on == 'faces': if values.shape[0] != self.n_faces(): raise ValueError("'values' should be a length n_faces array") + if basisX.shape[0] != self.n_faces(): raise ValueError("'basisX' should be a length n_faces array") + if basisY.shape[0] != self.n_faces(): raise ValueError("'basisY' should be a length n_faces array") - q = self.bound_mesh.add_face_intrinsic_vector_quantity(name, values, n_sym, str_to_vectortype(vectortype)) + q = self.bound_mesh.add_face_tangent_vector_quantity(name, values, basisX, basisY, n_sym, str_to_vectortype(vectortype)) else: raise ValueError("bad `defined_on` value {}, should be one of ['vertices', 'faces']".format(defined_on)) @@ -412,16 +387,14 @@ def add_intrinsic_vector_quantity(self, name, values, n_sym=1, defined_on='verti q.set_radius(radius, True) if color is not None: q.set_color(glm3(color)) - if ribbon is not None: - q.set_ribbon_enabled(ribbon) - def add_one_form_vector_quantity(self, name, values, orientations, enabled=None, length=None, radius=None, color=None, ribbon=None): + def add_one_form_vector_quantity(self, name, values, orientations, enabled=None, length=None, radius=None, color=None): if len(values.shape) != 1 or values.shape[0] != self.n_edges(): raise ValueError("'values' should be length n_edges array") if len(orientations.shape) != 1 or orientations.shape[0] != self.n_edges(): raise ValueError("'orientations' should be length n_edges array") - q = self.bound_mesh.add_one_form_intrinsic_vector_quantity(name, values, orientations) + q = self.bound_mesh.add_one_form_tangent_vector_quantity(name, values, orientations) # Support optional params if enabled is not None: @@ -432,8 +405,6 @@ def add_one_form_vector_quantity(self, name, values, orientations, enabled=None, q.set_radius(radius, True) if color is not None: q.set_color(glm3(color)) - if ribbon is not None: - q.set_ribbon_enabled(ribbon) def register_surface_mesh(name, vertices, faces, enabled=None, color=None, edge_color=None, smooth_shade=None, diff --git a/test/polyscope_test.py b/test/polyscope_test.py index 1d43299..cc1f5f3 100644 --- a/test/polyscope_test.py +++ b/test/polyscope_test.py @@ -1017,28 +1017,18 @@ def test_2D(self): def test_permutation(self): - p = ps.register_surface_mesh("test_mesh", self.generate_verts(), self.generate_faces()) - p.set_vertex_permutation(np.random.permutation(p.n_vertices())) - p.set_vertex_permutation(np.random.permutation(p.n_vertices()), 3*p.n_vertices()) + p = ps.register_surface_mesh("test_mesh", self.generate_verts(), self.generate_faces()) - p.set_face_permutation(np.random.permutation(p.n_faces())) - p.set_face_permutation(np.random.permutation(p.n_faces()), 3*p.n_faces()) - p.set_edge_permutation(np.random.permutation(p.n_edges())) - p.set_edge_permutation(np.random.permutation(p.n_edges()), 3*p.n_edges()) p.set_corner_permutation(np.random.permutation(p.n_corners())) - p.set_corner_permutation(np.random.permutation(p.n_corners()), 3*p.n_corners()) p.set_halfedge_permutation(np.random.permutation(p.n_halfedges())) - p.set_halfedge_permutation(np.random.permutation(p.n_halfedges()), 3*p.n_halfedges()) p = ps.register_surface_mesh("test_mesh2", self.generate_verts(), self.generate_faces()) p.set_all_permutations( - vertex_perm=np.random.permutation(p.n_vertices()), - face_perm=np.random.permutation(p.n_faces()), edge_perm=np.random.permutation(p.n_edges()), corner_perm=np.random.permutation(p.n_corners()), halfedge_perm=np.random.permutation(p.n_halfedges()), @@ -1046,25 +1036,38 @@ def test_permutation(self): ps.show(3) ps.remove_all_structures() + + def test_permutation_with_size(self): - - def test_tangent_basis(self): p = ps.register_surface_mesh("test_mesh", self.generate_verts(), self.generate_faces()) - p.set_vertex_tangent_basisX(np.random.rand(p.n_vertices(), 3)) - p.set_vertex_tangent_basisX(np.random.rand(p.n_vertices(), 2)) + p.set_edge_permutation(np.random.permutation(p.n_edges()), 3*p.n_edges()) + + p.set_corner_permutation(np.random.permutation(p.n_corners()), 3*p.n_corners()) + + p.set_halfedge_permutation(np.random.permutation(p.n_halfedges()), 3*p.n_halfedges()) - p.set_face_tangent_basisX(np.random.rand(p.n_faces(), 3)) - p.set_face_tangent_basisX(np.random.rand(p.n_faces(), 2)) + p = ps.register_surface_mesh("test_mesh2", self.generate_verts(), self.generate_faces()) + + p.set_all_permutations( + edge_perm=np.random.permutation(p.n_edges()), + edge_perm_size=3*p.n_edges(), + corner_perm=np.random.permutation(p.n_corners()), + corner_perm_size=3*p.n_corners(), + halfedge_perm=np.random.permutation(p.n_halfedges()), + halfedge_perm_size=p.n_halfedges(), + ) ps.show(3) ps.remove_all_structures() + def test_scalar(self): - ps.register_surface_mesh("test_mesh", self.generate_verts(), self.generate_faces()) - p = ps.get_surface_mesh("test_mesh") for on in ['vertices', 'faces', 'edges', 'halfedges']: + + ps.register_surface_mesh("test_mesh", self.generate_verts(), self.generate_faces()) + p = ps.get_surface_mesh("test_mesh") if on == 'vertices': vals = np.random.rand(p.n_vertices()) @@ -1072,6 +1075,7 @@ def test_scalar(self): vals = np.random.rand(p.n_faces()) elif on == 'edges': vals = np.random.rand(p.n_edges()) + p.set_edge_permutation(np.random.permutation(p.n_edges())) elif on == 'halfedges': vals = np.random.rand(p.n_halfedges()) @@ -1089,7 +1093,7 @@ def test_scalar(self): p.remove_all_quantities() p.remove_all_quantities() - ps.remove_all_structures() + ps.remove_all_structures() def test_color(self): ps.register_surface_mesh("test_mesh", self.generate_verts(), self.generate_faces()) @@ -1200,7 +1204,7 @@ def test_vector(self): ps.remove_all_structures() - def test_intrinsic_vector(self): + def test_tangent_vector(self): ps.register_surface_mesh("test_mesh", self.generate_verts(), self.generate_faces()) p = ps.get_surface_mesh("test_mesh") @@ -1208,31 +1212,34 @@ def test_intrinsic_vector(self): for on in ['vertices', 'faces']: if on == 'vertices': - vals = np.random.rand(p.n_vertices(),2) - p.set_vertex_tangent_basisX(np.random.rand(p.n_vertices(), 3)); + vals = np.random.rand(p.n_vertices(), 2) + basisX = np.random.rand(p.n_vertices(), 3) + basisY = np.random.rand(p.n_vertices(), 3) elif on == 'faces': vals = np.random.rand(p.n_faces(), 2) - p.set_face_tangent_basisX(np.random.rand(p.n_faces(), 3)); - - p.add_intrinsic_vector_quantity("test_vals1", vals, defined_on=on) - p.add_intrinsic_vector_quantity("test_vals2", vals, defined_on=on, enabled=True) - p.add_intrinsic_vector_quantity("test_vals3", vals, defined_on=on, enabled=True, vectortype='ambient') - p.add_intrinsic_vector_quantity("test_vals4", vals, defined_on=on, enabled=True, length=0.005) - p.add_intrinsic_vector_quantity("test_vals5", vals, defined_on=on, enabled=True, radius=0.001) - p.add_intrinsic_vector_quantity("test_vals6", vals, defined_on=on, enabled=True, color=(0.2, 0.5, 0.5)) - p.add_intrinsic_vector_quantity("test_vals7", vals, defined_on=on, enabled=True, radius=0.001, ribbon=True) - p.add_intrinsic_vector_quantity("test_vals8", vals, n_sym=4, defined_on=on, enabled=True) + basisX = np.random.rand(p.n_faces(), 3) + basisY = np.random.rand(p.n_faces(), 3) + + p.add_tangent_vector_quantity("test_vals1", vals, basisX, basisY, defined_on=on) + p.add_tangent_vector_quantity("test_vals2", vals, basisX, basisY, defined_on=on, enabled=True) + p.add_tangent_vector_quantity("test_vals3", vals, basisX, basisY, defined_on=on, enabled=True, vectortype='ambient') + p.add_tangent_vector_quantity("test_vals4", vals, basisX, basisY, defined_on=on, enabled=True, length=0.005) + p.add_tangent_vector_quantity("test_vals5", vals, basisX, basisY, defined_on=on, enabled=True, radius=0.001) + p.add_tangent_vector_quantity("test_vals6", vals, basisX, basisY, defined_on=on, enabled=True, color=(0.2, 0.5, 0.5)) + p.add_tangent_vector_quantity("test_vals7", vals, basisX, basisY, defined_on=on, enabled=True, radius=0.001) + p.add_tangent_vector_quantity("test_vals8", vals, basisX, basisY, n_sym=4, defined_on=on, enabled=True) ps.show(3) p.remove_all_quantities() ps.remove_all_structures() - def test_one_form_intrinsic_vector(self): + def test_one_form_tangent_vector(self): ps.register_surface_mesh("test_mesh", self.generate_verts(), self.generate_faces()) p = ps.get_surface_mesh("test_mesh") - p.set_vertex_tangent_basisX(np.random.rand(p.n_vertices(), 3)); + + p.set_edge_permutation(np.random.permutation(p.n_edges())) vals = np.random.rand(p.n_edges()) orients = np.random.rand(p.n_edges()) > 0.5 @@ -1243,7 +1250,7 @@ def test_one_form_intrinsic_vector(self): p.add_one_form_vector_quantity("test_vals4", vals, orients, enabled=True, length=0.005) p.add_one_form_vector_quantity("test_vals5", vals, orients, enabled=True, radius=0.001) p.add_one_form_vector_quantity("test_vals6", vals, orients, enabled=True, color=(0.2, 0.5, 0.5)) - p.add_one_form_vector_quantity("test_vals7", vals, orients, enabled=True, radius=0.001, ribbon=True) + p.add_one_form_vector_quantity("test_vals7", vals, orients, enabled=True, radius=0.001) ps.show(3) p.remove_all_quantities() From e9633d92032c13f35b8402a7eb61143b5be1d174 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sun, 9 Apr 2023 22:10:09 -0700 Subject: [PATCH 04/88] add bindings for set_front_dir --- src/cpp/core.cpp | 26 ++++++++++++++++++-------- src/polyscope/core.py | 19 +++++++++++++++++++ test/polyscope_test.py | 7 +++++++ 3 files changed, 44 insertions(+), 8 deletions(-) diff --git a/src/cpp/core.cpp b/src/cpp/core.cpp index ebf2888..00708a3 100644 --- a/src/cpp/core.cpp +++ b/src/cpp/core.cpp @@ -102,7 +102,8 @@ PYBIND11_MODULE(polyscope_bindings, m) { m.def("set_invoke_user_callback_for_nested_show", [](bool x) { ps::options::invokeUserCallbackForNestedShow = x; }); m.def("set_give_focus_on_show", [](bool x) { ps::options::giveFocusOnShow = x; }); m.def("set_navigation_style", [](ps::view::NavigateStyle x) { ps::view::style = x; }); - m.def("set_up_dir", [](ps::view::UpDir x) { ps::view::setUpDir(x); }); + m.def("set_up_dir", [](ps::UpDir x) { ps::view::setUpDir(x); }); + m.def("set_front_dir", [](ps::FrontDir x) { ps::view::setFrontDir(x); }); // === Scene extents m.def("set_automatically_compute_scene_extents", [](bool x) { ps::options::automaticallyComputeSceneExtents = x; }); @@ -226,13 +227,22 @@ PYBIND11_MODULE(polyscope_bindings, m) { .value("orthographic", ps::ProjectionMode::Orthographic) .export_values(); - py::enum_(m, "UpDir") - .value("x_up", ps::view::UpDir::XUp) - .value("y_up", ps::view::UpDir::YUp) - .value("z_up", ps::view::UpDir::ZUp) - .value("neg_x_up", ps::view::UpDir::NegXUp) - .value("neg_y_up", ps::view::UpDir::NegYUp) - .value("neg_z_up", ps::view::UpDir::NegZUp) + py::enum_(m, "UpDir") + .value("x_up", ps::UpDir::XUp) + .value("y_up", ps::UpDir::YUp) + .value("z_up", ps::UpDir::ZUp) + .value("neg_x_up", ps::UpDir::NegXUp) + .value("neg_y_up", ps::UpDir::NegYUp) + .value("neg_z_up", ps::UpDir::NegZUp) + .export_values(); + + py::enum_(m, "FrontDir") + .value("x_front", ps::FrontDir::XFront) + .value("y_front", ps::FrontDir::YFront) + .value("z_front", ps::FrontDir::ZFront) + .value("neg_x_front", ps::FrontDir::NegXFront) + .value("neg_y_front", ps::FrontDir::NegYFront) + .value("neg_z_front", ps::FrontDir::NegZFront) .export_values(); py::enum_(m, "DataType") diff --git a/src/polyscope/core.py b/src/polyscope/core.py index 3200cc8..a44eb51 100644 --- a/src/polyscope/core.py +++ b/src/polyscope/core.py @@ -94,6 +94,9 @@ def set_navigation_style(s): def set_up_dir(d): psb.set_up_dir(str_to_updir(d)) +def set_front_dir(d): + psb.set_front_dir(str_to_frontdir(d)) + ### Scene extents def set_automatically_compute_scene_extents(b): @@ -306,6 +309,22 @@ def str_to_updir(s): return d[s] +def str_to_frontdir(s): + d = { + "x_front" : psb.FrontDir.x_front, + "neg_x_front" : psb.FrontDir.neg_x_front, + "y_front" : psb.FrontDir.y_front, + "neg_y_front" : psb.FrontDir.neg_y_front, + "z_front" : psb.FrontDir.z_front, + "neg_z_front" : psb.FrontDir.neg_z_front, + } + + if s not in d: + raise ValueError("Bad front direction specifier '{}', should be one of [{}]".format(s, + ",".join(["'{}'".format(x) for x in d.keys()]))) + + return d[s] + def str_to_datatype(s): d = { "standard" : psb.DataType.standard, diff --git a/test/polyscope_test.py b/test/polyscope_test.py index cc1f5f3..0d9eb35 100644 --- a/test/polyscope_test.py +++ b/test/polyscope_test.py @@ -88,6 +88,13 @@ def test_view_options(self): ps.set_up_dir("z_up") ps.set_up_dir("neg_z_up") + ps.set_front_dir("x_front") + ps.set_front_dir("neg_x_front") + ps.set_front_dir("y_front") + ps.set_front_dir("neg_y_front") + ps.set_front_dir("z_front") + ps.set_front_dir("neg_z_front") + ps.set_view_projection_mode("orthographic") ps.set_view_projection_mode("perspective") From dd4858a07d8b69541267cd8409359686907a15a8 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sun, 9 Apr 2023 22:59:45 -0700 Subject: [PATCH 05/88] new bindings for up/front/navigate --- deps/polyscope | 2 +- src/cpp/core.cpp | 3 ++ src/polyscope/core.py | 92 ++++++++++++++++++++++++++++-------------- test/polyscope_test.py | 3 ++ 4 files changed, 68 insertions(+), 32 deletions(-) diff --git a/deps/polyscope b/deps/polyscope index 3c0b7d4..71ac08f 160000 --- a/deps/polyscope +++ b/deps/polyscope @@ -1 +1 @@ -Subproject commit 3c0b7d413f4d7b9b09712d270dea08a39b13a772 +Subproject commit 71ac08f2e56bbdcd7861e75c963d3c148a0fce52 diff --git a/src/cpp/core.cpp b/src/cpp/core.cpp index 00708a3..4e32803 100644 --- a/src/cpp/core.cpp +++ b/src/cpp/core.cpp @@ -102,8 +102,11 @@ PYBIND11_MODULE(polyscope_bindings, m) { m.def("set_invoke_user_callback_for_nested_show", [](bool x) { ps::options::invokeUserCallbackForNestedShow = x; }); m.def("set_give_focus_on_show", [](bool x) { ps::options::giveFocusOnShow = x; }); m.def("set_navigation_style", [](ps::view::NavigateStyle x) { ps::view::style = x; }); + m.def("get_navigation_style", ps::view::getNavigateStyle); m.def("set_up_dir", [](ps::UpDir x) { ps::view::setUpDir(x); }); + m.def("get_up_dir", ps::view::getUpDir); m.def("set_front_dir", [](ps::FrontDir x) { ps::view::setFrontDir(x); }); + m.def("get_front_dir", ps::view::getFrontDir); // === Scene extents m.def("set_automatically_compute_scene_extents", [](bool x) { ps::options::automaticallyComputeSceneExtents = x; }); diff --git a/src/polyscope/core.py b/src/polyscope/core.py index a44eb51..44ae447 100644 --- a/src/polyscope/core.py +++ b/src/polyscope/core.py @@ -90,12 +90,18 @@ def set_give_focus_on_show(b): def set_navigation_style(s): psb.set_navigation_style(str_to_navigate_style(s)) +def get_navigation_style(): + return navigate_style_to_str(psb.get_navigation_style()); def set_up_dir(d): psb.set_up_dir(str_to_updir(d)) +def get_up_dir(): + return updir_to_str(psb.get_up_dir()) def set_front_dir(d): psb.set_front_dir(str_to_frontdir(d)) +def get_front_dir(): + return frontdir_to_str(psb.get_front_dir()) ### Scene extents @@ -268,18 +274,26 @@ def load_color_map(cmap_name, filename): ## String-to-enum translation +d_navigate = { + "turntable" : psb.NavigateStyle.turntable, + "free" : psb.NavigateStyle.free, + "planar" : psb.NavigateStyle.planar, +} def str_to_navigate_style(s): - d = { - "turntable" : psb.NavigateStyle.turntable, - "free" : psb.NavigateStyle.free, - "planar" : psb.NavigateStyle.planar, - } - if s not in d: + if s not in d_navigate: raise ValueError("Bad navigate style specifier '{}', should be one of [{}]".format(s, - ",".join(["'{}'".format(x) for x in d.keys()]))) + ",".join(["'{}'".format(x) for x in d_navigate.keys()]))) + + return d_navigate[s] +def navigate_style_to_str(val): + for k,v in d_navigate.items(): + if v == val: + return k + + raise ValueError("Bad navigate style specifier '{}', should be one of [{}]".format(val, + ",".join(["'{}'".format(x) for x in d_navigate.values()]))) - return d[s] def str_to_projection_mode(s): d = { @@ -293,37 +307,53 @@ def str_to_projection_mode(s): return d[s] -def str_to_updir(s): - d = { - "x_up" : psb.UpDir.x_up, - "neg_x_up" : psb.UpDir.neg_x_up, - "y_up" : psb.UpDir.y_up, - "neg_y_up" : psb.UpDir.neg_y_up, - "z_up" : psb.UpDir.z_up, - "neg_z_up" : psb.UpDir.neg_z_up, - } - if s not in d: +d_updir = { + "x_up" : psb.UpDir.x_up, + "neg_x_up" : psb.UpDir.neg_x_up, + "y_up" : psb.UpDir.y_up, + "neg_y_up" : psb.UpDir.neg_y_up, + "z_up" : psb.UpDir.z_up, + "neg_z_up" : psb.UpDir.neg_z_up, +} +def str_to_updir(s): + if s not in d_updir: raise ValueError("Bad up direction specifier '{}', should be one of [{}]".format(s, - ",".join(["'{}'".format(x) for x in d.keys()]))) + ",".join(["'{}'".format(x) for x in d_updir.keys()]))) - return d[s] + return d_updir[s] + +def updir_to_str(val): + for k,v in d_updir.items(): + if v == val: + return k + + raise ValueError("Bad up direction specifier '{}', should be one of [{}]".format(val, + ",".join(["'{}'".format(x) for x in d_updir.values()]))) + +d_frontdir = { + "x_front" : psb.FrontDir.x_front, + "neg_x_front" : psb.FrontDir.neg_x_front, + "y_front" : psb.FrontDir.y_front, + "neg_y_front" : psb.FrontDir.neg_y_front, + "z_front" : psb.FrontDir.z_front, + "neg_z_front" : psb.FrontDir.neg_z_front, +} def str_to_frontdir(s): - d = { - "x_front" : psb.FrontDir.x_front, - "neg_x_front" : psb.FrontDir.neg_x_front, - "y_front" : psb.FrontDir.y_front, - "neg_y_front" : psb.FrontDir.neg_y_front, - "z_front" : psb.FrontDir.z_front, - "neg_z_front" : psb.FrontDir.neg_z_front, - } - if s not in d: + if s not in d_frontdir: raise ValueError("Bad front direction specifier '{}', should be one of [{}]".format(s, - ",".join(["'{}'".format(x) for x in d.keys()]))) + ",".join(["'{}'".format(x) for x in d_frontdir.keys()]))) - return d[s] + return d_frontdir[s] +def frontdir_to_str(val): + for k,v in d_frontdir.items(): + if v == val: + return k + + raise ValueError("Bad front direction specifier '{}', should be one of [{}]".format(val, + ",".join(["'{}'".format(x) for x in d_frontdir.values()]))) def str_to_datatype(s): d = { diff --git a/test/polyscope_test.py b/test/polyscope_test.py index 0d9eb35..255b327 100644 --- a/test/polyscope_test.py +++ b/test/polyscope_test.py @@ -80,6 +80,7 @@ def test_view_options(self): ps.set_navigation_style("turntable") ps.set_navigation_style("free") ps.set_navigation_style("planar") + ps.set_navigation_style(ps.get_navigation_style()) ps.set_up_dir("x_up") ps.set_up_dir("neg_x_up") @@ -87,6 +88,7 @@ def test_view_options(self): ps.set_up_dir("neg_y_up") ps.set_up_dir("z_up") ps.set_up_dir("neg_z_up") + ps.set_up_dir(ps.get_up_dir()) ps.set_front_dir("x_front") ps.set_front_dir("neg_x_front") @@ -94,6 +96,7 @@ def test_view_options(self): ps.set_front_dir("neg_y_front") ps.set_front_dir("z_front") ps.set_front_dir("neg_z_front") + ps.set_front_dir(ps.get_front_dir()) ps.set_view_projection_mode("orthographic") ps.set_view_projection_mode("perspective") From 9a4ef602e668772089be0ac450a3fcc7f6419cf7 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sun, 9 Apr 2023 23:17:04 -0700 Subject: [PATCH 06/88] bindings for json view funcs --- deps/polyscope | 2 +- src/cpp/core.cpp | 2 ++ src/polyscope/core.py | 6 ++++++ test/polyscope_test.py | 3 +++ 4 files changed, 12 insertions(+), 1 deletion(-) diff --git a/deps/polyscope b/deps/polyscope index 71ac08f..ae02c63 160000 --- a/deps/polyscope +++ b/deps/polyscope @@ -1 +1 @@ -Subproject commit 71ac08f2e56bbdcd7861e75c963d3c148a0fce52 +Subproject commit ae02c63996ff14cdf36f8535479ee1dfcf941bc2 diff --git a/src/cpp/core.cpp b/src/cpp/core.cpp index 4e32803..6993f75 100644 --- a/src/cpp/core.cpp +++ b/src/cpp/core.cpp @@ -124,6 +124,8 @@ PYBIND11_MODULE(polyscope_bindings, m) { ps::view::lookAt(location, target, upDir, flyTo); }); m.def("set_view_projection_mode", [](ps::ProjectionMode x) { ps::view::projectionMode = x; }); + m.def("set_view_from_json", ps::view::setViewFromJson); + m.def("get_view_as_json", ps::view::getViewAsJson); // === Messages m.def("info", ps::info, "Send an info message"); diff --git a/src/polyscope/core.py b/src/polyscope/core.py index 44ae447..a1cc711 100644 --- a/src/polyscope/core.py +++ b/src/polyscope/core.py @@ -135,6 +135,12 @@ def look_at_dir(camera_location, target, up_dir, fly_to=False): def set_view_projection_mode(s): psb.set_view_projection_mode(str_to_projection_mode(s)) +def set_view_from_json(s): + psb.view_from_json(s) + +def get_view_as_json(): + return psb.get_view_as_json() + ### Messages def info(message): diff --git a/test/polyscope_test.py b/test/polyscope_test.py index 255b327..6f79df7 100644 --- a/test/polyscope_test.py +++ b/test/polyscope_test.py @@ -117,6 +117,9 @@ def test_camera_movement(self): ps.show(3) + def test_view_json(self): + ps.set_view_from_json(ps.get_view_as_json()) + def test_ground_options(self): ps.set_ground_plane_mode("none") From b03e88deb3302bd80d13497f60b1e7acb1d61dcb Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Mon, 24 Apr 2023 20:56:09 -0700 Subject: [PATCH 07/88] begin adding floating quantities --- CMakeLists.txt | 1 + src/cpp/core.cpp | 2 ++ src/cpp/floating_quantities.cpp | 64 +++++++++++++++++++++++++++++++++ src/cpp/utils.h | 37 +++++++++++++------ 4 files changed, 94 insertions(+), 10 deletions(-) create mode 100644 src/cpp/floating_quantities.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index e4ebd7f..c6e5134 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,6 +17,7 @@ pybind11_add_module(polyscope_bindings src/cpp/point_cloud.cpp src/cpp/curve_network.cpp src/cpp/volume_mesh.cpp + src/cpp/floating_quantities.cpp src/cpp/imgui.cpp ) diff --git a/src/cpp/core.cpp b/src/cpp/core.cpp index 6993f75..46ce572 100644 --- a/src/cpp/core.cpp +++ b/src/cpp/core.cpp @@ -29,6 +29,7 @@ void bind_surface_mesh(py::module& m); void bind_point_cloud(py::module& m); void bind_curve_network(py::module& m); void bind_volume_mesh(py::module& m); +void bind_floating_quantities(py::module& m); void bind_imgui(py::module& m); // Signal handler (makes ctrl-c work, etc) @@ -318,6 +319,7 @@ PYBIND11_MODULE(polyscope_bindings, m) { bind_point_cloud(m); bind_curve_network(m); bind_volume_mesh(m); + bind_floating_quantities(m); bind_imgui(m); } diff --git a/src/cpp/floating_quantities.cpp b/src/cpp/floating_quantities.cpp new file mode 100644 index 0000000..ad30ed4 --- /dev/null +++ b/src/cpp/floating_quantities.cpp @@ -0,0 +1,64 @@ +#include +#include +#include + +#include "Eigen/Dense" + +#include "polyscope/point_cloud.h" +#include "polyscope/polyscope.h" + +#include "utils.h" + +namespace py = pybind11; +namespace ps = polyscope; + +// For overloaded functions, with C++11 compiler only +template +using overload_cast_ = pybind11::detail::overload_cast_impl; + + +// clang-format off +/* +void bind_floating_quantities(py::module& m) { + + // == Helper quantity classes + + // Scalar quantities + bindScalarQuantity(m, "PointCloudScalarQuantity"); + + // Color quantities + bindColorQuantity(m, "PointCloudColorQuantity"); + + // Vector quantities + bindVectorQuantity(m, "PointCloudVectorQuantity"); + + + // == Main class + bindStructure(m, "PointCloud") + + // basics + .def("update_point_positions", &ps::PointCloud::updatePointPositions, "Update point positions") + .def("update_point_positions2D", &ps::PointCloud::updatePointPositions2D, "Update point positions") + .def("n_points", &ps::PointCloud::nPoints, "# points") + + // options + .def("set_radius", &ps::PointCloud::setPointRadius, "Set radius") + .def("get_radius", &ps::PointCloud::getPointRadius, "Get radius") + .def("set_color", &ps::PointCloud::setPointColor, "Set color") + .def("get_color", &ps::PointCloud::getPointColor, "Get color") + .def("set_material", &ps::PointCloud::setMaterial, "Set material") + .def("get_material", &ps::PointCloud::getMaterial, "Get material") + .def("set_point_render_mode", &ps::PointCloud::setPointRenderMode, "Set point render mode") + .def("get_point_render_mode", &ps::PointCloud::getPointRenderMode, "Get point render mode") + + // slice planes + .def("set_ignore_slice_plane", &ps::PointCloud::setIgnoreSlicePlane, "Set ignore slice plane") + .def("get_ignore_slice_plane", &ps::PointCloud::getIgnoreSlicePlane, "Get ignore slice plane") + .def("set_cull_whole_elements", &ps::PointCloud::setCullWholeElements, "Set cull whole elements") + .def("get_cull_whole_elements", &ps::PointCloud::getCullWholeElements, "Get cull whole elements") + + +} +*/ + +// clang-format on diff --git a/src/cpp/utils.h b/src/cpp/utils.h index 2e91b3f..d72856e 100644 --- a/src/cpp/utils.h +++ b/src/cpp/utils.h @@ -8,7 +8,10 @@ #include "Eigen/Dense" +#include "polyscope/image_quantity.h" + namespace py = pybind11; +namespace ps = polyscope; // Some conversion helpers template @@ -63,7 +66,8 @@ py::class_ bindStructure(py::module& m, std::string name) { // quantites .def("remove_all_quantities", &StructureT::removeAllQuantities, "Remove all quantities") - .def("remove_quantity", &StructureT::removeQuantity, py::arg("name"), py::arg("errorIfAbsent") = false, "Remove a quantity") + .def("remove_quantity", &StructureT::removeQuantity, py::arg("name"), py::arg("errorIfAbsent") = false, + "Remove a quantity") // transform management // clang-format off @@ -74,7 +78,18 @@ py::class_ bindStructure(py::module& m, std::string name) { .def("set_position", [](StructureT& s, Eigen::Vector3f T) { s.setPosition(eigen2glm(T)); }, "set the translation component of the transform to the given position") .def("translate", [](StructureT& s, Eigen::Vector3f T) { s.translate(eigen2glm(T)); }, "apply the given translation to the shape, updating its position") .def("get_transform", [](StructureT& s) { return glm2eigen(s.getTransform()); }, "get the current 4x4 transform matrix") - .def("get_position", [](StructureT& s) { return glm2eigen(s.getPosition()); }, "get the position of the shape origin after transform"); + .def("get_position", [](StructureT& s) { return glm2eigen(s.getPosition()); }, "get the position of the shape origin after transform") + + // floating quantites + .def("add_scalar_image_quantity", &StructureT::template addScalarImageQuantity, py::arg("name"), py::arg("dimX"), py::arg("dimY"), py::arg("values"), py::arg("imageOrigin")=ps::ImageOrigin::UpperLeft, py::arg("type")=ps::DataType::STANDARD) + .def("add_color_image_quantity", &StructureT::template addColorImageQuantity, py::arg("name"), py::arg("dimX"), py::arg("dimY"), py::arg("values_rgb"), py::arg("imageOrigin")=ps::ImageOrigin::UpperLeft) + .def("add_color_alpha_image_quantity", &StructureT::template addColorAlphaImageQuantity, py::arg("name"), py::arg("dimX"), py::arg("dimY"), py::arg("values_rgba"), py::arg("imageOrigin")=ps::ImageOrigin::UpperLeft) + .def("add_depth_render_image_quantity", &StructureT::template addDepthRenderImageQuantity, py::arg("name"), py::arg("dimX"), py::arg("dimY"), py::arg("depthData"), py::arg("normalData"), py::arg("imageOrigin")=ps::ImageOrigin::UpperLeft) + .def("add_color_render_image_quantity", &StructureT::template addColorRenderImageQuantity, py::arg("name"), py::arg("dimX"), py::arg("dimY"), py::arg("depthData"), py::arg("normalData"), py::arg("colorData"), py::arg("imageOrigin")=ps::ImageOrigin::UpperLeft) + .def("add_scalar_render_image_quantity", &StructureT::template addScalarRenderImageQuantity, py::arg("name"), py::arg("dimX"), py::arg("dimY"), py::arg("depthData"), py::arg("normalData"), py::arg("scalarData"), py::arg("imageOrigin")=ps::ImageOrigin::UpperLeft, py::arg("type")=ps::DataType::STANDARD) + + ; + // clang-format on } @@ -89,16 +104,18 @@ py::class_ bindScalarQuantity(py::module& m, std::string name) { .def("set_isoline_width", &ScalarQ::setIsolineWidth, "Set isoline width"); } -template +template py::class_ bindVMVScalarQuantity(py::module& m, std::string name) { return py::class_(m, name.c_str()) - .def("set_enabled", &VolumeMeshVertexScalarQuantity::setEnabled, "Set enabled") - .def("set_color_map", &VolumeMeshVertexScalarQuantity::setColorMap, "Set color map") - .def("set_map_range", &VolumeMeshVertexScalarQuantity::setMapRange, "Set map range") - .def("set_isoline_width", &VolumeMeshVertexScalarQuantity::setIsolineWidth, "Set isoline width") - .def("set_level_set_enable", &VolumeMeshVertexScalarQuantity::setEnabledLevelSet, "Set level set rendering enabled") - .def("set_level_set_value", &VolumeMeshVertexScalarQuantity::setLevelSetValue, "Set level set value") - .def("set_level_set_visible_quantity", &VolumeMeshVertexScalarQuantity::setLevelSetVisibleQuantity, "Set quantity to show on level set"); + .def("set_enabled", &VolumeMeshVertexScalarQuantity::setEnabled, "Set enabled") + .def("set_color_map", &VolumeMeshVertexScalarQuantity::setColorMap, "Set color map") + .def("set_map_range", &VolumeMeshVertexScalarQuantity::setMapRange, "Set map range") + .def("set_isoline_width", &VolumeMeshVertexScalarQuantity::setIsolineWidth, "Set isoline width") + .def("set_level_set_enable", &VolumeMeshVertexScalarQuantity::setEnabledLevelSet, + "Set level set rendering enabled") + .def("set_level_set_value", &VolumeMeshVertexScalarQuantity::setLevelSetValue, "Set level set value") + .def("set_level_set_visible_quantity", &VolumeMeshVertexScalarQuantity::setLevelSetVisibleQuantity, + "Set quantity to show on level set"); } // Add common bindings for all color quantities From 1ca0b76548b380abb2c569d26277043205575a0b Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Mon, 24 Apr 2023 21:29:51 -0700 Subject: [PATCH 08/88] factor out common options from quantities --- src/polyscope/common.py | 77 +++++++++++++++++++++++++++++++++ src/polyscope/curve_network.py | 34 +++++---------- src/polyscope/point_cloud.py | 34 +++++---------- src/polyscope/surface_mesh.py | 78 +++++++++------------------------- src/polyscope/volume_mesh.py | 37 ++++++---------- 5 files changed, 130 insertions(+), 130 deletions(-) create mode 100644 src/polyscope/common.py diff --git a/src/polyscope/common.py b/src/polyscope/common.py new file mode 100644 index 0000000..f7377d0 --- /dev/null +++ b/src/polyscope/common.py @@ -0,0 +1,77 @@ +import polyscope_bindings as psb + +from polyscope.core import str_to_datatype, str_to_vectortype, glm3, str_to_point_render_mode, point_render_mode_to_str, str_to_param_viz_style + + +def process_color_args(structure, quantity, color_args): + + for arg,val in color_args.items(): + + if arg == 'enabled' and val is not None: + quantity.set_enabled(val) + + else: + raise ValueError(f"Polyscope: Unrecognized color quantity keyword argument {arg}: {val}") + + +def process_scalar_args(structure, quantity, scalar_args): + + for arg,val in scalar_args.items(): + + if arg == 'enabled' and val is not None: + quantity.set_enabled(val) + + elif arg == 'vminmax' and val is not None: + quantity.set_map_range(val) + + elif arg == 'cmap' and val is not None: + quantity.set_color_map(val) + + else: + raise ValueError(f"Polyscope: Unrecognized scalar quantity keyword argument {arg}: {val}") + +def process_vector_args(structure, quantity, vector_args): + + for arg,val in vector_args.items(): + + if arg == 'enabled' and val is not None: + quantity.set_enabled(val) + + elif arg == 'length' and val is not None: + quantity.set_length(val, True) + + elif arg == 'radius' and val is not None: + quantity.set_radius(val, True) + + elif arg == 'color' and val is not None: + quantity.set_color(glm3(val)) + + else: + raise ValueError(f"Polyscope: Unrecognized vector quantity keyword argument {arg}: {val}") + +def process_parameterization_args(structure, quantity, parameterization_args): + + for arg,val in parameterization_args.items(): + + if arg == 'enabled' and val is not None: + quantity.set_enabled(val) + + elif arg == 'viz_style' and val is not None: + viz_style_enum = str_to_param_viz_style(val) + quantity.set_style(viz_style_enum) + + elif arg == 'grid_colors' and val is not None: + quantity.set_grid_colors((glm3(val[0]), glm3(val[1]))) + + elif arg == 'checker_colors' and val is not None: + quantity.set_checker_colors((glm3(val[0]), glm3(val[1]))) + + elif arg == 'checker_size' and val is not None: + quantity.set_checker_size(val) + + elif arg == 'cmap' and val is not None: + quantity.set_color_map(val) + + else: + raise ValueError(f"Polyscope: Unrecognized parameterization quantity keyword argument {arg}: {val}") + diff --git a/src/polyscope/curve_network.py b/src/polyscope/curve_network.py index ff422f4..6f8333f 100644 --- a/src/polyscope/curve_network.py +++ b/src/polyscope/curve_network.py @@ -2,6 +2,8 @@ from polyscope.core import str_to_datatype, str_to_vectortype, glm3 +from polyscope.common import process_color_args, process_scalar_args, process_vector_args, process_parameterization_args + class CurveNetwork: # This class wraps a _reference_ to the underlying object, whose lifetime is managed by Polyscope @@ -149,7 +151,7 @@ def get_material(self): ## Quantities # Scalar - def add_scalar_quantity(self, name, values, defined_on='nodes', enabled=None, datatype="standard", vminmax=None, cmap=None): + def add_scalar_quantity(self, name, values, defined_on='nodes', datatype="standard", **scalar_args): if len(values.shape) != 1: raise ValueError("'values' should be a length-N array") @@ -161,19 +163,13 @@ def add_scalar_quantity(self, name, values, defined_on='nodes', enabled=None, da q = self.bound_network.add_edge_scalar_quantity(name, values, str_to_datatype(datatype)) else: raise ValueError("bad `defined_on` value {}, should be one of ['nodes', 'edges']".format(defined_on)) - + - # Support optional params - if enabled is not None: - q.set_enabled(enabled) - if vminmax is not None: - q.set_map_range(vminmax) - if cmap is not None: - q.set_color_map(cmap) + process_scalar_args(self, q, scalar_args) # Color - def add_color_quantity(self, name, values, defined_on='nodes', enabled=None): + def add_color_quantity(self, name, values, defined_on='nodes', **color_args): if len(values.shape) != 2 or values.shape[1] != 3: raise ValueError("'values' should be an Nx3 array") @@ -186,13 +182,12 @@ def add_color_quantity(self, name, values, defined_on='nodes', enabled=None): else: raise ValueError("bad `defined_on` value {}, should be one of ['nodes', 'edges']".format(defined_on)) - # Support optional params - if enabled is not None: - q.set_enabled(enabled) + + process_color_args(self, q, color_args) # Vector - def add_vector_quantity(self, name, values, defined_on='nodes', enabled=None, vectortype="standard", length=None, radius=None, color=None): + def add_vector_quantity(self, name, values, defined_on='nodes', vectortype="standard", **vector_args): if len(values.shape) != 2 or values.shape[1] not in [2,3]: raise ValueError("'values' should be an Nx3 array (or Nx2 for 2D)") @@ -215,15 +210,8 @@ def add_vector_quantity(self, name, values, defined_on='nodes', enabled=None, ve else: raise ValueError("bad `defined_on` value {}, should be one of ['nodes', 'edges']".format(defined_on)) - # Support optional params - if enabled is not None: - q.set_enabled(enabled) - if length is not None: - q.set_length(length, True) - if radius is not None: - q.set_radius(radius, True) - if color is not None: - q.set_color(glm3(color)) + + process_vector_args(self, q, vector_args) def register_curve_network(name, nodes, edges, enabled=None, radius=None, color=None, material=None, transparency=None): diff --git a/src/polyscope/point_cloud.py b/src/polyscope/point_cloud.py index 2cd2eb1..9b0392c 100644 --- a/src/polyscope/point_cloud.py +++ b/src/polyscope/point_cloud.py @@ -2,6 +2,8 @@ from polyscope.core import str_to_datatype, str_to_vectortype, glm3, str_to_point_render_mode, point_render_mode_to_str +from polyscope.common import process_color_args, process_scalar_args, process_vector_args, process_parameterization_args + class PointCloud: # This class wraps a _reference_ to the underlying object, whose lifetime is managed by Polyscope @@ -143,49 +145,33 @@ def get_material(self): ## Quantities # Scalar - def add_scalar_quantity(self, name, values, enabled=None, datatype="standard", vminmax=None, cmap=None): + def add_scalar_quantity(self, name, values, datatype="standard", **scalar_args): if len(values.shape) != 1 or values.shape[0] != self.n_points(): raise ValueError("'values' should be a length-N array") q = self.bound_cloud.add_scalar_quantity(name, values, str_to_datatype(datatype)) - # Support optional params - if enabled is not None: - q.set_enabled(enabled) - if vminmax is not None: - q.set_map_range(vminmax) - if cmap is not None: - q.set_color_map(cmap) + process_scalar_args(self, q, scalar_args) # Color - def add_color_quantity(self, name, values, enabled=None): + def add_color_quantity(self, name, values, **color_args): if len(values.shape) != 2 or values.shape[0] != self.n_points() or values.shape[1] != 3: raise ValueError("'values' should be an Nx3 array") q = self.bound_cloud.add_color_quantity(name, values) - - # Support optional params - if enabled is not None: - q.set_enabled(enabled) + + process_color_args(self, q, color_args) # Vector - def add_vector_quantity(self, name, values, enabled=None, vectortype="standard", length=None, radius=None, color=None): + def add_vector_quantity(self, name, values, vectortype="standard", **vector_args): if len(values.shape) != 2 or values.shape[0] != self.n_points() or values.shape[1] not in [2,3]: raise ValueError("'values' should be an Nx3 array (or Nx2 for 2D)") if values.shape[1] == 2: q = self.bound_cloud.add_vector_quantity2D(name, values, str_to_vectortype(vectortype)) elif values.shape[1] == 3: q = self.bound_cloud.add_vector_quantity(name, values, str_to_vectortype(vectortype)) - - # Support optional params - if enabled is not None: - q.set_enabled(enabled) - if length is not None: - q.set_length(length, True) - if radius is not None: - q.set_radius(radius, True) - if color is not None: - q.set_color(glm3(color)) + + process_vector_args(self, q, vector_args) def register_point_cloud(name, points, enabled=None, radius=None, point_render_mode=None, color=None, material=None, transparency=None): diff --git a/src/polyscope/surface_mesh.py b/src/polyscope/surface_mesh.py index 3288d28..9fb23a5 100644 --- a/src/polyscope/surface_mesh.py +++ b/src/polyscope/surface_mesh.py @@ -5,6 +5,8 @@ str_to_param_viz_style, str_to_back_face_policy, back_face_policy_to_str,\ glm3 +from polyscope.common import process_color_args, process_scalar_args, process_vector_args, process_parameterization_args + class SurfaceMesh: # This class wraps a _reference_ to the underlying object, whose lifetime is managed by Polyscope @@ -211,7 +213,7 @@ def set_all_permutations(self, ## Quantities # Scalar - def add_scalar_quantity(self, name, values, defined_on='vertices', enabled=None, datatype="standard", vminmax=None, cmap=None): + def add_scalar_quantity(self, name, values, defined_on='vertices', datatype="standard", **scalar_args): if len(values.shape) != 1: raise ValueError("'values' should be a length-N array") @@ -231,17 +233,11 @@ def add_scalar_quantity(self, name, values, defined_on='vertices', enabled=None, raise ValueError("bad `defined_on` value {}, should be one of ['vertices', 'faces', 'edges', 'halfedges']".format(defined_on)) - # Support optional params - if enabled is not None: - q.set_enabled(enabled) - if vminmax is not None: - q.set_map_range(vminmax) - if cmap is not None: - q.set_color_map(cmap) - + process_scalar_args(self, q, scalar_args) + # Color - def add_color_quantity(self, name, values, defined_on='vertices', enabled=None): + def add_color_quantity(self, name, values, defined_on='vertices', **color_args): if len(values.shape) != 2 or values.shape[1] != 3: raise ValueError("'values' should be an Nx3 array") if defined_on == 'vertices': @@ -253,9 +249,8 @@ def add_color_quantity(self, name, values, defined_on='vertices', enabled=None): else: raise ValueError("bad `defined_on` value {}, should be one of ['vertices', 'faces']".format(defined_on)) - # Support optional params - if enabled is not None: - q.set_enabled(enabled) + + process_color_args(self, q, color_args) # Distance @@ -286,7 +281,7 @@ def add_distance_quantity(self, name, values, defined_on='vertices', enabled=Non # Parameterization - def add_parameterization_quantity(self, name, values, defined_on='vertices', coords_type='unit', enabled=None, viz_style=None, grid_colors=None, checker_colors=None, checker_size=None, cmap=None): + def add_parameterization_quantity(self, name, values, defined_on='vertices', coords_type='unit', **parameterization_args): if len(values.shape) != 2 or values.shape[1] != 2: raise ValueError("'values' should be an (Nx2) array") @@ -302,25 +297,11 @@ def add_parameterization_quantity(self, name, values, defined_on='vertices', coo else: raise ValueError("bad `defined_on` value {}, should be one of ['vertices', 'corners']".format(defined_on)) - - # Support optional params - if enabled is not None: - q.set_enabled(enabled) - if viz_style is not None: - viz_style_enum = str_to_param_viz_style(viz_style) - q.set_style(viz_style_enum) - if grid_colors is not None: - q.set_grid_colors((glm3(grid_colors[0]), glm3(grid_colors[1]))) - if checker_colors is not None: - q.set_checker_colors((glm3(checker_colors[0]), glm3(checker_colors[1]))) - if checker_size is not None: - q.set_checker_size(checker_size) - if cmap is not None: - q.set_color_map(cmap) + process_parameterization_args(self, q, parameterization_args) # Vector - def add_vector_quantity(self, name, values, defined_on='vertices', enabled=None, vectortype="standard", length=None, radius=None, color=None): + def add_vector_quantity(self, name, values, defined_on='vertices', vectortype="standard", **vector_args): if len(values.shape) != 2 or values.shape[1] not in [2,3]: raise ValueError("'values' should be an Nx3 array (or Nx2 for 2D)") @@ -343,18 +324,11 @@ def add_vector_quantity(self, name, values, defined_on='vertices', enabled=None, else: raise ValueError("bad `defined_on` value {}, should be one of ['vertices', 'faces']".format(defined_on)) - # Support optional params - if enabled is not None: - q.set_enabled(enabled) - if length is not None: - q.set_length(length, True) - if radius is not None: - q.set_radius(radius, True) - if color is not None: - q.set_color(glm3(color)) + + process_vector_args(self, q, vector_args) - def add_tangent_vector_quantity(self, name, values, basisX, basisY, n_sym=1, defined_on='vertices', enabled=None, vectortype="standard", length=None, radius=None, color=None): + def add_tangent_vector_quantity(self, name, values, basisX, basisY, n_sym=1, defined_on='vertices', vectortype="standard", **vector_args): if len(values.shape) != 2 or values.shape[1] != 2: raise ValueError("'values' should be an Nx2 array") if len(basisX.shape) != 2 or basisX.shape[1] != 3: raise ValueError("'basisX' should be an Nx3 array") @@ -378,33 +352,19 @@ def add_tangent_vector_quantity(self, name, values, basisX, basisY, n_sym=1, def else: raise ValueError("bad `defined_on` value {}, should be one of ['vertices', 'faces']".format(defined_on)) - # Support optional params - if enabled is not None: - q.set_enabled(enabled) - if length is not None: - q.set_length(length, True) - if radius is not None: - q.set_radius(radius, True) - if color is not None: - q.set_color(glm3(color)) + + process_vector_args(self, q, vector_args) - def add_one_form_vector_quantity(self, name, values, orientations, enabled=None, length=None, radius=None, color=None): + def add_one_form_vector_quantity(self, name, values, orientations, **vector_args): if len(values.shape) != 1 or values.shape[0] != self.n_edges(): raise ValueError("'values' should be length n_edges array") if len(orientations.shape) != 1 or orientations.shape[0] != self.n_edges(): raise ValueError("'orientations' should be length n_edges array") q = self.bound_mesh.add_one_form_tangent_vector_quantity(name, values, orientations) - # Support optional params - if enabled is not None: - q.set_enabled(enabled) - if length is not None: - q.set_length(length, True) - if radius is not None: - q.set_radius(radius, True) - if color is not None: - q.set_color(glm3(color)) + process_vector_args(self, q, vector_args) + def register_surface_mesh(name, vertices, faces, enabled=None, color=None, edge_color=None, smooth_shade=None, diff --git a/src/polyscope/volume_mesh.py b/src/polyscope/volume_mesh.py index 8f994b6..12d600f 100644 --- a/src/polyscope/volume_mesh.py +++ b/src/polyscope/volume_mesh.py @@ -3,6 +3,8 @@ from polyscope.core import str_to_datatype, str_to_vectortype, str_to_param_coords_type, str_to_param_viz_style, glm3 +from polyscope.common import process_color_args, process_scalar_args, process_vector_args, process_parameterization_args + class VolumeMesh: # This class wraps a _reference_ to the underlying object, whose lifetime is managed by Polyscope @@ -172,7 +174,7 @@ def get_material(self): ## Quantities # Scalar - def add_scalar_quantity(self, name, values, defined_on='vertices', enabled=None, datatype="standard", vminmax=None, cmap=None): + def add_scalar_quantity(self, name, values, defined_on='vertices', datatype="standard", **scalar_args): if len(values.shape) != 1: raise ValueError("'values' should be a length-N array") @@ -184,19 +186,14 @@ def add_scalar_quantity(self, name, values, defined_on='vertices', enabled=None, q = self.bound_mesh.add_cell_scalar_quantity(name, values, str_to_datatype(datatype)) else: raise ValueError("bad `defined_on` value {}, should be one of ['vertices', 'cells']".format(defined_on)) - - - # Support optional params - if enabled is not None: - q.set_enabled(enabled) - if vminmax is not None: - q.set_map_range(vminmax) - if cmap is not None: - q.set_color_map(cmap) + + + process_scalar_args(self, q, scalar_args) + # Color - def add_color_quantity(self, name, values, defined_on='vertices', enabled=None): + def add_color_quantity(self, name, values, defined_on='vertices', **color_args): if len(values.shape) != 2 or values.shape[1] != 3: raise ValueError("'values' should be an Nx3 array") if defined_on == 'vertices': @@ -208,13 +205,12 @@ def add_color_quantity(self, name, values, defined_on='vertices', enabled=None): else: raise ValueError("bad `defined_on` value {}, should be one of ['vertices', 'cells']".format(defined_on)) - # Support optional params - if enabled is not None: - q.set_enabled(enabled) + + process_color_args(self, q, color_args) # Vector - def add_vector_quantity(self, name, values, defined_on='vertices', enabled=None, vectortype="standard", length=None, radius=None, color=None): + def add_vector_quantity(self, name, values, defined_on='vertices', vectortype="standard", **vector_args): if len(values.shape) != 2 or values.shape[1] != 3: raise ValueError("'values' should be an Nx3 array") @@ -227,15 +223,8 @@ def add_vector_quantity(self, name, values, defined_on='vertices', enabled=None, else: raise ValueError("bad `defined_on` value {}, should be one of ['vertices', 'cells']".format(defined_on)) - # Support optional params - if enabled is not None: - q.set_enabled(enabled) - if length is not None: - q.set_length(length, True) - if radius is not None: - q.set_radius(radius, True) - if color is not None: - q.set_color(glm3(color)) + + process_vector_args(self, q, vector_args) From ac055d6b21b5318e27fa0134e7ec3ac651a3a4ea Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Mon, 24 Apr 2023 21:31:39 -0700 Subject: [PATCH 09/88] fix json setter --- src/polyscope/core.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/polyscope/core.py b/src/polyscope/core.py index a1cc711..a1af8a7 100644 --- a/src/polyscope/core.py +++ b/src/polyscope/core.py @@ -135,8 +135,8 @@ def look_at_dir(camera_location, target, up_dir, fly_to=False): def set_view_projection_mode(s): psb.set_view_projection_mode(str_to_projection_mode(s)) -def set_view_from_json(s): - psb.view_from_json(s) +def set_view_from_json(s, fly_to=False): + psb.set_view_from_json(s, fly_to) def get_view_as_json(): return psb.get_view_as_json() From fd229906b2a98c3c0b07029d528f0404221fe858 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Wed, 10 May 2023 21:41:11 -0700 Subject: [PATCH 10/88] work on image stuff --- src/cpp/core.cpp | 13 ++++++++++++- src/cpp/surface_mesh.cpp | 2 ++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/cpp/core.cpp b/src/cpp/core.cpp index 46ce572..fb851c4 100644 --- a/src/cpp/core.cpp +++ b/src/cpp/core.cpp @@ -298,6 +298,17 @@ PYBIND11_MODULE(polyscope_bindings, m) { .value("sphere", ps::PointRenderMode::Sphere) .value("quad", ps::PointRenderMode::Quad) .export_values(); + + py::enum_(m, "ImageOrigin") + .value("lower_left", ps::ImageOrigin::LowerLeft) + .value("upper_left", ps::ImageOrigin::UpperLeft) + .export_values(); + + py::enum_(m, "MeshShadeStyle") + .value("smooth", ps::MeshShadeStyle::Smooth) + .value("flat", ps::MeshShadeStyle::Flat) + .value("tri_flat", ps::MeshShadeStyle::TriFlat) + .export_values(); // === Mini bindings for a little bit of glm py::class_(m, "glm_vec3"). @@ -319,7 +330,7 @@ PYBIND11_MODULE(polyscope_bindings, m) { bind_point_cloud(m); bind_curve_network(m); bind_volume_mesh(m); - bind_floating_quantities(m); + // bind_floating_quantities(m); bind_imgui(m); } diff --git a/src/cpp/surface_mesh.cpp b/src/cpp/surface_mesh.cpp index 2629d7a..3d501e1 100644 --- a/src/cpp/surface_mesh.cpp +++ b/src/cpp/surface_mesh.cpp @@ -76,6 +76,8 @@ void bind_surface_mesh(py::module& m) { .def("get_edge_width", &ps::SurfaceMesh::getEdgeWidth, "Get edge width") .def("set_smooth_shade", &ps::SurfaceMesh::setSmoothShade, "Set smooth shading") .def("get_smooth_shade", &ps::SurfaceMesh::isSmoothShade, "Get if smooth shading is enabled") + .def("set_shade_style", &ps::SurfaceMesh::setShadeStyle, "Set shading") + .def("get_shade_style", &ps::SurfaceMesh::getShadeStyle, "Get shading") .def("set_material", &ps::SurfaceMesh::setMaterial, "Set material") .def("get_material", &ps::SurfaceMesh::getMaterial, "Get material") .def("set_back_face_policy", &ps::SurfaceMesh::setBackFacePolicy, "Set back face policy") From 347478a9e638e956857970b096b0c1271e682884 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Wed, 10 May 2023 21:44:06 -0700 Subject: [PATCH 11/88] update dep --- deps/polyscope | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/polyscope b/deps/polyscope index ae02c63..33cfdf2 160000 --- a/deps/polyscope +++ b/deps/polyscope @@ -1 +1 @@ -Subproject commit ae02c63996ff14cdf36f8535479ee1dfcf941bc2 +Subproject commit 33cfdf224117bd94dac42f37ca0c0be6407d3dde From cdcc9a9a9a294fbaa72d1d54279e096cc950d62b Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Wed, 31 May 2023 22:45:32 -0700 Subject: [PATCH 12/88] add bindings for floating quantities --- deps/polyscope | 2 +- src/cpp/core.cpp | 2 +- src/cpp/floating_quantities.cpp | 65 +++++++++++++-------------------- src/cpp/utils.h | 18 +++++---- 4 files changed, 39 insertions(+), 48 deletions(-) diff --git a/deps/polyscope b/deps/polyscope index 33cfdf2..825dd99 160000 --- a/deps/polyscope +++ b/deps/polyscope @@ -1 +1 @@ -Subproject commit 33cfdf224117bd94dac42f37ca0c0be6407d3dde +Subproject commit 825dd9904b4bf4733a33188c50af2237e1625bc6 diff --git a/src/cpp/core.cpp b/src/cpp/core.cpp index fb851c4..c8f648a 100644 --- a/src/cpp/core.cpp +++ b/src/cpp/core.cpp @@ -330,7 +330,7 @@ PYBIND11_MODULE(polyscope_bindings, m) { bind_point_cloud(m); bind_curve_network(m); bind_volume_mesh(m); - // bind_floating_quantities(m); + bind_floating_quantities(m); bind_imgui(m); } diff --git a/src/cpp/floating_quantities.cpp b/src/cpp/floating_quantities.cpp index ad30ed4..5d2f111 100644 --- a/src/cpp/floating_quantities.cpp +++ b/src/cpp/floating_quantities.cpp @@ -4,9 +4,11 @@ #include "Eigen/Dense" -#include "polyscope/point_cloud.h" #include "polyscope/polyscope.h" +#include "polyscope/floating_quantities.h" +#include "polyscope/image_quantity.h" + #include "utils.h" namespace py = pybind11; @@ -18,47 +20,32 @@ using overload_cast_ = pybind11::detail::overload_cast_impl; // clang-format off -/* void bind_floating_quantities(py::module& m) { - // == Helper quantity classes - - // Scalar quantities - bindScalarQuantity(m, "PointCloudScalarQuantity"); - - // Color quantities - bindColorQuantity(m, "PointCloudColorQuantity"); - - // Vector quantities - bindVectorQuantity(m, "PointCloudVectorQuantity"); - - - // == Main class - bindStructure(m, "PointCloud") - - // basics - .def("update_point_positions", &ps::PointCloud::updatePointPositions, "Update point positions") - .def("update_point_positions2D", &ps::PointCloud::updatePointPositions2D, "Update point positions") - .def("n_points", &ps::PointCloud::nPoints, "# points") - - // options - .def("set_radius", &ps::PointCloud::setPointRadius, "Set radius") - .def("get_radius", &ps::PointCloud::getPointRadius, "Get radius") - .def("set_color", &ps::PointCloud::setPointColor, "Set color") - .def("get_color", &ps::PointCloud::getPointColor, "Get color") - .def("set_material", &ps::PointCloud::setMaterial, "Set material") - .def("get_material", &ps::PointCloud::getMaterial, "Get material") - .def("set_point_render_mode", &ps::PointCloud::setPointRenderMode, "Set point render mode") - .def("get_point_render_mode", &ps::PointCloud::getPointRenderMode, "Get point render mode") - - // slice planes - .def("set_ignore_slice_plane", &ps::PointCloud::setIgnoreSlicePlane, "Set ignore slice plane") - .def("get_ignore_slice_plane", &ps::PointCloud::getIgnoreSlicePlane, "Get ignore slice plane") - .def("set_cull_whole_elements", &ps::PointCloud::setCullWholeElements, "Set cull whole elements") - .def("get_cull_whole_elements", &ps::PointCloud::getCullWholeElements, "Get cull whole elements") - + // == Global floating quantity management + + m.def("remove_floating_quantity", &ps::removeFloatingQuantity); + m.def("remove_all_floating_quantities", &ps::removeAllFloatingQuantities); + + // == Image floating quantities + + bindScalarQuantity(m, "ScalarImageQuantity"); + bindColorQuantity(m, "ColorImageQuantity"); + + m.def("add_scalar_image_quantity", &ps::addScalarImageQuantity); + m.def("add_color_image_quantity", &ps::addColorImageQuantity); + m.def("add_color_alpha_image_quantity", &ps::addColorAlphaImageQuantity); + + // == Render image floating quantities + + bindQuantity(m, "DepthRenderImageQuantity"); + bindScalarQuantity(m, "ScalarRenderImageQuantity"); + bindColorQuantity(m, "ColorRenderImageQuantity"); + + m.def("add_depth_render_image_quantity", &ps::addDepthRenderImageQuantity); + m.def("add_color_render_image_quantity", &ps::addColorRenderImageQuantity); + m.def("add_scalar_render_image_quantity", &ps::addScalarRenderImageQuantity); } -*/ // clang-format on diff --git a/src/cpp/utils.h b/src/cpp/utils.h index d72856e..d909549 100644 --- a/src/cpp/utils.h +++ b/src/cpp/utils.h @@ -93,12 +93,18 @@ py::class_ bindStructure(py::module& m, std::string name) { // clang-format on } +// Common bindings for quantities that do not fall in to a more specific quantity below +template +py::class_ bindQuantity(py::module& m, std::string name) { + return py::class_(m, name.c_str()) + .def("set_enabled", &Q::setEnabled, "Set enabled"); +} + // Add common bindings for all scalar quantities template py::class_ bindScalarQuantity(py::module& m, std::string name) { - return py::class_(m, name.c_str()) - .def("set_enabled", &ScalarQ::setEnabled, "Set enabled") + return bindQuantity(m, name.c_str()) .def("set_color_map", &ScalarQ::setColorMap, "Set color map") .def("set_map_range", &ScalarQ::setMapRange, "Set map range") .def("set_isoline_width", &ScalarQ::setIsolineWidth, "Set isoline width"); @@ -106,8 +112,7 @@ py::class_ bindScalarQuantity(py::module& m, std::string name) { template py::class_ bindVMVScalarQuantity(py::module& m, std::string name) { - return py::class_(m, name.c_str()) - .def("set_enabled", &VolumeMeshVertexScalarQuantity::setEnabled, "Set enabled") + return bindQuantity(m, name.c_str()) .def("set_color_map", &VolumeMeshVertexScalarQuantity::setColorMap, "Set color map") .def("set_map_range", &VolumeMeshVertexScalarQuantity::setMapRange, "Set map range") .def("set_isoline_width", &VolumeMeshVertexScalarQuantity::setIsolineWidth, "Set isoline width") @@ -121,14 +126,13 @@ py::class_ bindVMVScalarQuantity(py::module& m, // Add common bindings for all color quantities template py::class_ bindColorQuantity(py::module& m, std::string name) { - return py::class_(m, name.c_str()).def("set_enabled", &ColorQ::setEnabled, "Set enabled"); + return bindQuantity(m, name.c_str()); } // Add common bindings for all vector quantities template py::class_ bindVectorQuantity(py::module& m, std::string name) { - return py::class_(m, name.c_str()) - .def("set_enabled", &VectorQ::setEnabled, "Set enabled") + return bindQuantity(m, name.c_str()) .def("set_length", &VectorQ::setVectorLengthScale, "Set length") .def("set_radius", &VectorQ::setVectorRadius, "Set radius") .def("set_color", &VectorQ::setVectorColor, "Set color"); From 2255ff01ca1683c1c5c5f91042040eb75b97f222 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sun, 18 Jun 2023 23:59:57 -0700 Subject: [PATCH 13/88] add ruff --- ruff.toml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 ruff.toml diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 0000000..4d2050e --- /dev/null +++ b/ruff.toml @@ -0,0 +1,10 @@ + +# Never enforce +# - `E501` line length violations) +# - `E701` statement after colon on same line +ignore = ["E501","E701"] + +# Ignore `E402` (import violations) in all `__init__.py` files, and in `path/to/file.py`. +[per-file-ignores] +"__init__.py" = ["E402"] +"path/to/file.py" = ["E402"] From a3b6e9f1087fdc20cfaf6798b467ad962773a6d4 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Mon, 19 Jun 2023 00:00:36 -0700 Subject: [PATCH 14/88] reuse structure code, cam param bindings, cam view bindings --- CMakeLists.txt | 1 + deps/polyscope | 2 +- src/cpp/camera_view.cpp | 48 +++++++++ src/cpp/core.cpp | 48 +++++++-- src/cpp/curve_network.cpp | 7 -- src/cpp/point_cloud.cpp | 7 -- src/cpp/surface_mesh.cpp | 7 -- src/cpp/utils.h | 10 ++ src/cpp/volume_mesh.cpp | 6 -- src/polyscope/camera_view.py | 171 +++++++++++++++++++++++++++++++++ src/polyscope/core.py | 68 +++++++++++++ src/polyscope/curve_network.py | 116 ++++++---------------- src/polyscope/point_cloud.py | 108 +++++---------------- src/polyscope/structure.py | 73 ++++++++++++++ src/polyscope/surface_mesh.py | 158 ++++++++++-------------------- src/polyscope/volume_mesh.py | 115 ++++++---------------- 16 files changed, 546 insertions(+), 399 deletions(-) create mode 100644 src/cpp/camera_view.cpp create mode 100644 src/polyscope/camera_view.py create mode 100644 src/polyscope/structure.py diff --git a/CMakeLists.txt b/CMakeLists.txt index c6e5134..feaa79a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,6 +17,7 @@ pybind11_add_module(polyscope_bindings src/cpp/point_cloud.cpp src/cpp/curve_network.cpp src/cpp/volume_mesh.cpp + src/cpp/camera_view.cpp src/cpp/floating_quantities.cpp src/cpp/imgui.cpp ) diff --git a/deps/polyscope b/deps/polyscope index 825dd99..e17a790 160000 --- a/deps/polyscope +++ b/deps/polyscope @@ -1 +1 @@ -Subproject commit 825dd9904b4bf4733a33188c50af2237e1625bc6 +Subproject commit e17a7901868f0b7ddf79ab036aab1499f5b13856 diff --git a/src/cpp/camera_view.cpp b/src/cpp/camera_view.cpp new file mode 100644 index 0000000..2bfaf99 --- /dev/null +++ b/src/cpp/camera_view.cpp @@ -0,0 +1,48 @@ +#include +#include +#include + +#include "Eigen/Dense" + +#include "polyscope/camera_view.h" +#include "polyscope/polyscope.h" + +#include "utils.h" + +namespace py = pybind11; +namespace ps = polyscope; + + +// clang-format off +void bind_camera_view(py::module& m) { + + // == Helper quantity classes + + // == Main class + bindStructure(m, "CameraView") + + // basics + .def("update_camera_parameters", &ps::CameraView::updateCameraParameters, "Update camera parameters") + + // options + .def("set_widget_color", &ps::CameraView::setWidgetColor, "Set color") + .def("get_widget_color", &ps::CameraView::getWidgetColor, "Get color") + .def("set_widget_thickness", &ps::CameraView::setWidgetThickness, "Set widget thickness") + .def("get_widget_thickness", &ps::CameraView::getWidgetThickness, "Get widget thickness") + .def("set_widget_focal_length", &ps::CameraView::setWidgetFocalLength, "Set widget focal length") + .def("get_widget_focal_length", &ps::CameraView::getWidgetFocalLength, "Get widget focal length") + + // camera controls + .def("set_view_to_this_camera", &ps::CameraView::setViewToThisCamera, "Set view to this camera") + ; + + + // Static adders and getters + m.def("register_camera_view_vec", &ps::registerCameraView, + py::arg("name"), py::arg("parameters"), "Register a camera view", py::return_value_policy::reference); + m.def("remove_camera_view", &ps::removeCameraView, "Remove a camera view by name"); + m.def("get_camera_view", &ps::getCameraView, "Get a camera view by name", py::return_value_policy::reference); + m.def("has_camera_view", &ps::hasCameraView, "Check for a camera view by name"); + +} +// clang-format on diff --git a/src/cpp/core.cpp b/src/cpp/core.cpp index c8f648a..6965176 100644 --- a/src/cpp/core.cpp +++ b/src/cpp/core.cpp @@ -7,6 +7,7 @@ #include "Eigen/Dense" #include "polyscope/affine_remapper.h" +#include "polyscope/camera_parameters.h" #include "polyscope/curve_network.h" #include "polyscope/messages.h" #include "polyscope/pick.h" @@ -29,6 +30,7 @@ void bind_surface_mesh(py::module& m); void bind_point_cloud(py::module& m); void bind_curve_network(py::module& m); void bind_volume_mesh(py::module& m); +void bind_camera_view(py::module& m); void bind_floating_quantities(py::module& m); void bind_imgui(py::module& m); @@ -102,12 +104,7 @@ PYBIND11_MODULE(polyscope_bindings, m) { m.def("set_open_imgui_window_for_user_callback", [](bool x) { ps::options::openImGuiWindowForUserCallback= x; }); m.def("set_invoke_user_callback_for_nested_show", [](bool x) { ps::options::invokeUserCallbackForNestedShow = x; }); m.def("set_give_focus_on_show", [](bool x) { ps::options::giveFocusOnShow = x; }); - m.def("set_navigation_style", [](ps::view::NavigateStyle x) { ps::view::style = x; }); - m.def("get_navigation_style", ps::view::getNavigateStyle); - m.def("set_up_dir", [](ps::UpDir x) { ps::view::setUpDir(x); }); - m.def("get_up_dir", ps::view::getUpDir); - m.def("set_front_dir", [](ps::FrontDir x) { ps::view::setFrontDir(x); }); - m.def("get_front_dir", ps::view::getFrontDir); + // === Scene extents m.def("set_automatically_compute_scene_extents", [](bool x) { ps::options::automaticallyComputeSceneExtents = x; }); @@ -116,7 +113,14 @@ PYBIND11_MODULE(polyscope_bindings, m) { m.def("set_bounding_box", [](glm::vec3 low, glm::vec3 high) { ps::state::boundingBox = std::tuple(low, high); }); m.def("get_bounding_box", []() { return ps::state::boundingBox; }); - // === Camera controls + // === Camera controls & View + m.def("set_navigation_style", [](ps::view::NavigateStyle x) { ps::view::style = x; }); + m.def("get_navigation_style", ps::view::getNavigateStyle); + m.def("set_up_dir", [](ps::UpDir x) { ps::view::setUpDir(x); }); + m.def("get_up_dir", ps::view::getUpDir); + m.def("set_front_dir", [](ps::FrontDir x) { ps::view::setFrontDir(x); }); + m.def("get_front_dir", ps::view::getFrontDir); + m.def("reset_camera_to_home_view", ps::view::resetCameraToHomeView); m.def("look_at", [](glm::vec3 location, glm::vec3 target, bool flyTo) { ps::view::lookAt(location, target, flyTo); @@ -125,6 +129,8 @@ PYBIND11_MODULE(polyscope_bindings, m) { ps::view::lookAt(location, target, upDir, flyTo); }); m.def("set_view_projection_mode", [](ps::ProjectionMode x) { ps::view::projectionMode = x; }); + m.def("get_view_camera_parameters", &ps::view::getCameraParametersForCurrentView); + m.def("set_view_camera_parameters", &ps::view::setViewToCamera); m.def("set_view_from_json", ps::view::setViewFromJson); m.def("get_view_as_json", ps::view::getViewAsJson); @@ -219,6 +225,33 @@ PYBIND11_MODULE(polyscope_bindings, m) { m.def("add_scene_slice_plane", ps::addSceneSlicePlane, "add a slice plane", py::return_value_policy::reference); m.def("remove_last_scene_slice_plane", ps::removeLastSceneSlicePlane, "remove last scene plane"); + // === Camera Parameters + py::class_(m, "CameraIntrinsics") + .def(py::init<>()) + .def_static("from_FoV_deg_vertical_and_aspect", &ps::CameraIntrinsics::fromFoVDegVerticalAndAspect) + .def_static("from_FoV_deg_horizontal_and_aspect", &ps::CameraIntrinsics::fromFoVDegHorizontalAndAspect) + .def_static("from_FoV_deg_horizontal_and_vertical", &ps::CameraIntrinsics::fromFoVDegHorizontalAndVertical) + ; + py::class_(m, "CameraExtrinsics") + .def(py::init<>()) + .def_static("from_vectors", &ps::CameraExtrinsics::fromVectors) + .def_static("from_matrix", &ps::CameraExtrinsics::fromMatrix) + ; + py::class_(m, "CameraParameters") + .def(py::init()) + .def("get_T", &ps::CameraParameters::getT) + .def("get_R", &ps::CameraParameters::getR) + .def("get_view_mat", &ps::CameraParameters::getViewMat) + .def("get_E", &ps::CameraParameters::getE) + .def("get_position", &ps::CameraParameters::getPosition) + .def("get_look_dir", &ps::CameraParameters::getLookDir) + .def("get_up_dir", &ps::CameraParameters::getUpDir) + .def("get_right_dir", &ps::CameraParameters::getRightDir) + .def("get_camera_frame", &ps::CameraParameters::getCameraFrame) + .def("get_fov_vertical_deg", &ps::CameraParameters::getFoVVerticalDegrees) + .def("get_aspect", &ps::CameraParameters::getAspectRatioWidthOverHeight) + ; + // === Enums py::enum_(m, "NavigateStyle") @@ -330,6 +363,7 @@ PYBIND11_MODULE(polyscope_bindings, m) { bind_point_cloud(m); bind_curve_network(m); bind_volume_mesh(m); + bind_camera_view(m); bind_floating_quantities(m); bind_imgui(m); diff --git a/src/cpp/curve_network.cpp b/src/cpp/curve_network.cpp index 998df2a..40331b2 100644 --- a/src/cpp/curve_network.cpp +++ b/src/cpp/curve_network.cpp @@ -48,13 +48,6 @@ void bind_curve_network(py::module& m) { .def("set_material", &ps::CurveNetwork::setMaterial, "Set material") .def("get_material", &ps::CurveNetwork::getMaterial, "Get material") - // slice planes - .def("set_ignore_slice_plane", &ps::CurveNetwork::setIgnoreSlicePlane, "Set ignore slice plane") - .def("get_ignore_slice_plane", &ps::CurveNetwork::getIgnoreSlicePlane, "Get ignore slice plane") - .def("set_cull_whole_elements", &ps::CurveNetwork::setCullWholeElements, "Set cull whole elements") - .def("get_cull_whole_elements", &ps::CurveNetwork::getCullWholeElements, "Get cull whole elements") - - // quantities .def("add_node_color_quantity", &ps::CurveNetwork::addNodeColorQuantity, "Add a color function at nodes", py::arg("name"), py::arg("values"), py::return_value_policy::reference) diff --git a/src/cpp/point_cloud.cpp b/src/cpp/point_cloud.cpp index b339443..f82e172 100644 --- a/src/cpp/point_cloud.cpp +++ b/src/cpp/point_cloud.cpp @@ -50,13 +50,6 @@ void bind_point_cloud(py::module& m) { .def("set_point_render_mode", &ps::PointCloud::setPointRenderMode, "Set point render mode") .def("get_point_render_mode", &ps::PointCloud::getPointRenderMode, "Get point render mode") - // slice planes - .def("set_ignore_slice_plane", &ps::PointCloud::setIgnoreSlicePlane, "Set ignore slice plane") - .def("get_ignore_slice_plane", &ps::PointCloud::getIgnoreSlicePlane, "Get ignore slice plane") - .def("set_cull_whole_elements", &ps::PointCloud::setCullWholeElements, "Set cull whole elements") - .def("get_cull_whole_elements", &ps::PointCloud::getCullWholeElements, "Get cull whole elements") - - // variable radius .def("set_point_radius_quantity", overload_cast_()(&ps::PointCloud::setPointRadiusQuantity), diff --git a/src/cpp/surface_mesh.cpp b/src/cpp/surface_mesh.cpp index 3d501e1..93dcd3f 100644 --- a/src/cpp/surface_mesh.cpp +++ b/src/cpp/surface_mesh.cpp @@ -85,13 +85,6 @@ void bind_surface_mesh(py::module& m) { .def("set_back_face_color", &ps::SurfaceMesh::setBackFaceColor, "Set back face color") .def("get_back_face_color", &ps::SurfaceMesh::getBackFaceColor, "Get back face color") - // slice planes - .def("set_ignore_slice_plane", &ps::SurfaceMesh::setIgnoreSlicePlane, "Set ignore slice plane") - .def("get_ignore_slice_plane", &ps::SurfaceMesh::getIgnoreSlicePlane, "Get ignore slice plane") - .def("set_cull_whole_elements", &ps::SurfaceMesh::setCullWholeElements, "Set cull whole elements") - .def("get_cull_whole_elements", &ps::SurfaceMesh::getCullWholeElements, "Get cull whole elements") - - // permutations & bases .def("set_edge_permutation", &ps::SurfaceMesh::setEdgePermutation, "Set edge permutation") .def("set_halfedge_permutation", &ps::SurfaceMesh::setHalfedgePermutation, diff --git a/src/cpp/utils.h b/src/cpp/utils.h index d909549..509fdb0 100644 --- a/src/cpp/utils.h +++ b/src/cpp/utils.h @@ -13,6 +13,10 @@ namespace py = pybind11; namespace ps = polyscope; +// For overloaded functions, with C++11 compiler only +template +using overload_cast_ = pybind11::detail::overload_cast_impl; + // Some conversion helpers template inline glm::mat eigen2glm(const Eigen::Matrix& mat_eigen) { @@ -64,6 +68,12 @@ py::class_ bindStructure(py::module& m, std::string name) { .def("set_transparency", &StructureT::setTransparency, "Set transparency alpha") .def("get_transparency", &StructureT::getTransparency, "Get transparency alpha") + // slice plane things + .def("set_ignore_slice_plane", &StructureT::setIgnoreSlicePlane, "Set ignore slice plane") + .def("get_ignore_slice_plane", &StructureT::getIgnoreSlicePlane, "Get ignore slice plane") + .def("set_cull_whole_elements", &StructureT::setCullWholeElements, "Set cull whole elements") + .def("get_cull_whole_elements", &StructureT::getCullWholeElements, "Get cull whole elememts") + // quantites .def("remove_all_quantities", &StructureT::removeAllQuantities, "Remove all quantities") .def("remove_quantity", &StructureT::removeQuantity, py::arg("name"), py::arg("errorIfAbsent") = false, diff --git a/src/cpp/volume_mesh.cpp b/src/cpp/volume_mesh.cpp index c088c3c..2c00e15 100644 --- a/src/cpp/volume_mesh.cpp +++ b/src/cpp/volume_mesh.cpp @@ -52,12 +52,6 @@ void bind_volume_mesh(py::module& m) { .def("set_material", &ps::VolumeMesh::setMaterial, "Set material") .def("get_material", &ps::VolumeMesh::getMaterial, "Get material") - // slice planes - .def("set_ignore_slice_plane", &ps::VolumeMesh::setIgnoreSlicePlane, "Set ignore slice plane") - .def("get_ignore_slice_plane", &ps::VolumeMesh::getIgnoreSlicePlane, "Get ignore slice plane") - .def("set_cull_whole_elements", &ps::VolumeMesh::setCullWholeElements, "Set cull whole elements") - .def("get_cull_whole_elements", &ps::VolumeMesh::getCullWholeElements, "Get cull whole elements") - // = quantities diff --git a/src/polyscope/camera_view.py b/src/polyscope/camera_view.py new file mode 100644 index 0000000..b91e2f1 --- /dev/null +++ b/src/polyscope/camera_view.py @@ -0,0 +1,171 @@ +import polyscope_bindings as psb + +from polyscope.core import glm3, radians, degrees +from polyscope.structure import Structure +from polyscope.common import process_color_args, process_scalar_args, process_vector_args + +from math import tan, atan + +import numpy as np + +''' +def canonicalize_camera_params(root, look_dir, up_dir, view_mat, fov_vert_deg, fov_horz_deg, aspect): + + # validate combinations of args + + # check for a valid combination of extrinsics + ext_as_vec = (root is not None) or (look_dir is not None) or (up_dir is not None) + if ext_as_vec: + if (root is None) or (look_dir is None) or (up_dir is None): + raise ValueError("if any of 'root', 'look_dir', 'up_dir' is specified, they all must be") + if view_mat is not None: + raise ValueError("if any of 'root', 'look_dir', 'up_dir' is specified, 'view_mat' cannot be specified") + + # convert to glm vectors + root = glm3(root) + look_dir = glm3(look_dir) + up_dir = glm3(up_dir) + + ext_vals = { + 'style' : 'vec', + 'root' : root, + 'look_dir' : look_dir, + 'up_dir' : up_dir, + } + + ext_as_mat = view_mat is not None + if ext_as_mat: + view_mat = np.array(view_mat) + + ext_vals = { + 'style' : 'mat', + 'view_mat' : view_mat, + } + + + # check for a valid combination of intrinsics + # aspect is defined as "width over height" + if fov_vert_deg is not None: + if aspect is not None: + pass + elif fov_horz_deg is not None: + aspect = tan(0.5*radians(float(fov_horz_deg))) / tan(0.5*radians(float(fov_vert_deg))) + else: + raise ValueError("must pass one of 'fov_horz_deg' or 'aspect'") + + elif fov_horz_deg is not None: + if aspect is not None: + fov_vert_deg = degrees(2. * atan(tan(0.5*radians(float(fov_horz_deg))) / aspect)) + else: + raise ValueError("must pass one of 'fov_horz_deg' or 'aspect'") + else: + raise ValueError("must pass one of 'fov_vert_deg' or 'fov_horz_deg'") + + int_vals = { + 'fov_vert_deg' : float(fov_vert_deg), + 'aspect' : float(aspect) + } + + + return ext_vals, int_vals +''' + + + +class CameraView(Structure): + + # This class wraps a _reference_ to the underlying object, whose lifetime is managed by Polyscope + + # End users should not call this constrctor, use register_camera_view instead + def __init__(self, name=None, camera_parameters=None, instance=None): + + super().__init__() + + if instance is not None: + # Wrap an existing instance + self.bound_instance = instance + + else: + # Create a new instance + self.bound_instance = psb.register_camera_view_vec(name, camera_parameters) + + + # Update + def update_camera_parameters(self, camera_parameters): + self.bound_instance.update_camera_parameters(camera_parameters) + + + ## Camera things + + def set_view_to_this_camera(self, with_flight=False): + self.bound_cloud.set_view_to_this_camera(with_flight) + + def get_parameters(self): + return CameraParameters(instance=self.bound_cloud.get_parameters()) + + ## Options + + # Widget color + def set_widget_color(self, val): + self.bound_instance.set_widget_color(glm3(val)) + def get_widget_color(self): + return self.bound_instance.get_widget_color().as_tuple() + + # Widget thickness + def set_widget_thickness(self, val): + self.bound_instance.set_widget_thickness(float(val)) + def get_widget_thickness(self): + return self.bound_instance.get_widget_thickness() + + # Widget focal length + def set_widget_focal_length(self, val): + self.bound_instance.set_widget_focal_length(float(val)) + def get_widget_focal_length(self): + return self.bound_instance.get_widget_focal_length() + + + ## Quantities + + +def register_camera_view(name, camera_parameters, + enabled=None, transparency=None, + widget_color=None, widget_thickness=None, widget_focal_length=None, + ): + """Register a new camera view""" + if not psb.isInitialized(): + raise RuntimeError("Polyscope has not been initialized") + + p = CameraView(name, camera_parameters) + + # == Apply options + if enabled is not None: + p.set_enabled(enabled) + if transparency is not None: + p.set_transparency(transparency) + if widget_color is not None: + p.set_widget_color(widget_color) + if widget_thickness is not None: + p.set_widget_thickness(widget_thickness) + if widget_focal_length is not None: + p.set_widget_focal_length(widget_focal_length) + + return p + +def remove_camera_view(name, error_if_absent=True): + """Remove a camera view by name""" + psb.remove_camera_view(name, error_if_absent) + +def get_camera_view(name): + """Get camera view by name""" + if not has_camera_view(name): + raise ValueError("no camera view with name " + str(name)) + + raw_instance = psb.get_camera_view(name) + + # Wrap the instance + return CameraView(instance=raw_instance) + +def has_camera_view(name): + """Check if a camera view exists by name""" + return psb.has_camera_view(name) + diff --git a/src/polyscope/core.py b/src/polyscope/core.py index a1af8a7..67d0ba7 100644 --- a/src/polyscope/core.py +++ b/src/polyscope/core.py @@ -141,6 +141,13 @@ def set_view_from_json(s, fly_to=False): def get_view_as_json(): return psb.get_view_as_json() +def get_view_camera_parameters(): + return CameraParameters(instance=psb.get_view_camera_parameters()) + +def set_view_camera_parameters(params): + if not isinstance(params, CameraParameters): raise ValueError("must pass CameraParameters") + return set_view_camera_parameters(params.instance) + ### Messages def info(message): @@ -244,12 +251,73 @@ def add_scene_slice_plane(): def remove_last_scene_slice_plane(): psb.remove_last_scene_slice_plane() +### Camera Parameters + +class CameraIntrinsics: + + def __init__(self, fov_vertical_deg=None, fov_horizontal_deg=None, aspect=None): + + if fov_vertical_deg is not None and fov_aspect is not None: + self.instance = psb.CameraIntrinsics.fromFoVDegVerticalAndAspect(float(fov_vertical_deg), float(aspect)) + elif fov_horizontal_deg is not None and fov_aspect is not None: + self.instance = psb.CameraIntrinsics.fromFoVDegHorizontalAndAspect(float(fov_horizontal_deg), float(aspect)) + elif fov_vertical_deg is not None and fov_horizontal_deg is not None: + self.instance = psb.CameraIntrinsics.fromFoVDegHorizontalAndVertical(float(fov_horizontal_deg), float(fov_vertical_deg)) + else: + raise ValueError("bad arguments, at least two of (fov_vertical_deg,fov_horizontal_deg,aspect) must be given and non-None") + +class CameraExtrinsics: + + def __init__(self, mat=None): + + if mat is not None: + mat = np.array(mat) + if mat.shape != (4,4): raise ValueError("mat should be a 4x4 numpy matrix") + self.instance = psb.CameraExtrinsics.fromMatrix(mat) + + elif (root is not None) and (look_dir is not None) and (up_dir is not None): + + root = glm3(root) + look_dir = glm3(look_dir) + up_dir = glm3(up_dir) + self.instance = psb.CameraExtrinsics.from_vectors(root, look_dir, up_dir) + + else: + raise ValueError("bad arguments, must pass non-None (root,look_dir,up_dir) or non-None mat") + + +class CameraParameters: + + def __init__(self, intrinsics=None, extrinsics=None, instance=None): + if instance is not None: + self.instance = instance + else: + self.instance = psb.CameraParameters(intrinsics.instance, extrinsics.instance) + + # getters + def get_T(self): return self.instance.get_T() + def get_R(self): return self.instance.get_R() + def get_view_mat(self): return self.instance.get_view_mat() + def get_E(self): return self.instance.get_E() + def get_position(self): return self.instance.get_position() + def get_look_dir(self): return self.instance.get_look_dir() + def get_up_dir(self): return self.instance.get_up_dir() + def get_right_dir(self): return self.instance.get_right_dir() + def get_camera_frame(self): return self.instance.get_camera_frame() + def get_fov_vertical_deg(self): return self.instance.get_fov_vertical_deg() + def get_aspect(self): return self.instance.get_aspect() + + ## Small utilities def glm3(vals): return psb.glm_vec3(vals[0], vals[1], vals[2]) def glm4(vals): return psb.glm_vec4(vals[0], vals[1], vals[2], vals[3]) +def degrees(val): + return 180.*val / np.PI +def radians(val): + return np.PI * val / 180. ### Materials diff --git a/src/polyscope/curve_network.py b/src/polyscope/curve_network.py index 6f8333f..fedce7a 100644 --- a/src/polyscope/curve_network.py +++ b/src/polyscope/curve_network.py @@ -1,19 +1,21 @@ import polyscope_bindings as psb from polyscope.core import str_to_datatype, str_to_vectortype, glm3 - +from polyscope.structure import Structure from polyscope.common import process_color_args, process_scalar_args, process_vector_args, process_parameterization_args -class CurveNetwork: +class CurveNetwork(Structure): # This class wraps a _reference_ to the underlying object, whose lifetime is managed by Polyscope # End users should not call this constrctor, use register_curve_network instead def __init__(self, name=None, nodes=None, edges=None, instance=None): + + super().__init__() if instance is not None: # Wrap an existing instance - self.bound_network = instance + self.bound_instance = instance else: # Create a new instance @@ -26,14 +28,14 @@ def __init__(self, name=None, nodes=None, edges=None, instance=None): if nodes.shape[1] == 3: if edges == 'line': - self.bound_network = psb.register_curve_network_line(name, nodes) + self.bound_instance = psb.register_curve_network_line(name, nodes) elif edges == 'loop': - self.bound_network = psb.register_curve_network_loop(name, nodes) + self.bound_instance = psb.register_curve_network_loop(name, nodes) elif nodes.shape[1] == 2: if edges == 'line': - self.bound_network = psb.register_curve_network_line2D(name, nodes) + self.bound_instance = psb.register_curve_network_line2D(name, nodes) elif edges == 'loop': - self.bound_network = psb.register_curve_network_loop2D(name, nodes) + self.bound_instance = psb.register_curve_network_loop2D(name, nodes) else: # Common case: process edges as numpy array @@ -41,9 +43,9 @@ def __init__(self, name=None, nodes=None, edges=None, instance=None): raise ValueError("curve network edges should have shape (N_edge,2); shape is " + str(edges.shape)) if nodes.shape[1] == 3: - self.bound_network = psb.register_curve_network(name, nodes, edges) + self.bound_instance = psb.register_curve_network(name, nodes, edges) elif nodes.shape[1] == 2: - self.bound_network = psb.register_curve_network2D(name, nodes, edges) + self.bound_instance = psb.register_curve_network2D(name, nodes, edges) def check_shape(self, points): # Helper to validate arrays @@ -51,79 +53,19 @@ def check_shape(self, points): raise ValueError("curve network node positions should have shape (N,3); shape is " + str(points.shape)) def n_nodes(self): - return self.bound_network.n_nodes() + return self.bound_instance.n_nodes() def n_edges(self): - return self.bound_network.n_edges() - + return self.bound_instance.n_edges() - ## Structure management - - def remove(self): - '''Remove the structure itself''' - self.bound_network.remove() - def remove_all_quantities(self): - '''Remove all quantities on the structure''' - self.bound_network.remove_all_quantities() - def remove_quantity(self, name): - '''Remove a single quantity on the structure''' - self.bound_network.remove_quantity(name) - - # Enable/disable - def set_enabled(self, val=True): - self.bound_network.set_enabled(val) - def is_enabled(self): - return self.bound_network.is_enabled() - - # Transparency - def set_transparency(self, val): - self.bound_network.set_transparency(val) - def get_transparency(self): - return self.bound_network.get_transparency() - - # Transformation things - def center_bounding_box(self): - self.bound_network.center_bounding_box() - def rescale_to_unit(self): - self.bound_network.rescale_to_unit() - def reset_transform(self): - self.bound_network.reset_transform() - def set_transform(self, new_mat4x4): - self.bound_network.set_transform(new_mat4x4) - def set_position(self, new_vec3): - self.bound_network.set_position(new_vec3) - def translate(self, trans_vec3): - self.bound_network.translate(trans_vec3) - def get_transform(self): - return self.bound_network.get_transform() - def get_position(self): - return self.bound_network.get_position() - - # Slice planes - def set_cull_whole_elements(self, val): - self.bound_network.set_cull_whole_elements(val) - def get_cull_whole_elements(self): - return self.bound_network.get_cull_whole_elements() - def set_ignore_slice_plane(self, plane, val): - # take either a string or a slice plane object as input - if isinstance(plane, str): - self.bound_network.set_ignore_slice_plane(plane, val) - else: - self.bound_network.set_ignore_slice_plane(plane.get_name(), val) - def get_ignore_slice_plane(self, plane): - # take either a string or a slice plane object as input - if isinstance(plane, str): - return self.bound_network.get_ignore_slice_plane(plane) - else: - return self.bound_network.get_ignore_slice_plane(plane.get_name()) # Update def update_node_positions(self, nodes): self.check_shape(nodes) if nodes.shape[1] == 3: - self.bound_network.update_node_positions(nodes) + self.bound_instance.update_node_positions(nodes) elif nodes.shape[1] == 2: - self.bound_network.update_node_positions2D(nodes) + self.bound_instance.update_node_positions2D(nodes) else: raise ValueError("bad node shape") @@ -131,21 +73,21 @@ def update_node_positions(self, nodes): # Radius def set_radius(self, rad, relative=True): - self.bound_network.set_radius(rad, relative) + self.bound_instance.set_radius(rad, relative) def get_radius(self): - return self.bound_network.get_radius() + return self.bound_instance.get_radius() # Color def set_color(self, val): - self.bound_network.set_color(glm3(val)) + self.bound_instance.set_color(glm3(val)) def get_color(self): - return self.bound_network.get_color().as_tuple() + return self.bound_instance.get_color().as_tuple() # Material def set_material(self, mat): - self.bound_network.set_material(mat) + self.bound_instance.set_material(mat) def get_material(self): - return self.bound_network.get_material() + return self.bound_instance.get_material() ## Quantities @@ -157,10 +99,10 @@ def add_scalar_quantity(self, name, values, defined_on='nodes', datatype="standa if defined_on == 'nodes': if values.shape[0] != self.n_nodes(): raise ValueError("'values' should be a length n_nodes array") - q = self.bound_network.add_node_scalar_quantity(name, values, str_to_datatype(datatype)) + q = self.bound_instance.add_node_scalar_quantity(name, values, str_to_datatype(datatype)) elif defined_on == 'edges': if values.shape[0] != self.n_edges(): raise ValueError("'values' should be a length n_edges array") - q = self.bound_network.add_edge_scalar_quantity(name, values, str_to_datatype(datatype)) + q = self.bound_instance.add_edge_scalar_quantity(name, values, str_to_datatype(datatype)) else: raise ValueError("bad `defined_on` value {}, should be one of ['nodes', 'edges']".format(defined_on)) @@ -175,10 +117,10 @@ def add_color_quantity(self, name, values, defined_on='nodes', **color_args): if defined_on == 'nodes': if values.shape[0] != self.n_nodes(): raise ValueError("'values' should be a length n_nodes array") - q = self.bound_network.add_node_color_quantity(name, values) + q = self.bound_instance.add_node_color_quantity(name, values) elif defined_on == 'edges': if values.shape[0] != self.n_edges(): raise ValueError("'values' should be a length n_edges array") - q = self.bound_network.add_edge_color_quantity(name, values) + q = self.bound_instance.add_edge_color_quantity(name, values) else: raise ValueError("bad `defined_on` value {}, should be one of ['nodes', 'edges']".format(defined_on)) @@ -195,17 +137,17 @@ def add_vector_quantity(self, name, values, defined_on='nodes', vectortype="stan if values.shape[0] != self.n_nodes(): raise ValueError("'values' should be a length n_nodes array") if values.shape[1] == 2: - q = self.bound_network.add_node_vector_quantity2D(name, values, str_to_vectortype(vectortype)) + q = self.bound_instance.add_node_vector_quantity2D(name, values, str_to_vectortype(vectortype)) elif values.shape[1] == 3: - q = self.bound_network.add_node_vector_quantity(name, values, str_to_vectortype(vectortype)) + q = self.bound_instance.add_node_vector_quantity(name, values, str_to_vectortype(vectortype)) elif defined_on == 'edges': if values.shape[0] != self.n_edges(): raise ValueError("'values' should be a length n_edges array") if values.shape[1] == 2: - q = self.bound_network.add_edge_vector_quantity2D(name, values, str_to_vectortype(vectortype)) + q = self.bound_instance.add_edge_vector_quantity2D(name, values, str_to_vectortype(vectortype)) elif values.shape[1] == 3: - q = self.bound_network.add_edge_vector_quantity(name, values, str_to_vectortype(vectortype)) + q = self.bound_instance.add_edge_vector_quantity(name, values, str_to_vectortype(vectortype)) else: raise ValueError("bad `defined_on` value {}, should be one of ['nodes', 'edges']".format(defined_on)) diff --git a/src/polyscope/point_cloud.py b/src/polyscope/point_cloud.py index 9b0392c..d654926 100644 --- a/src/polyscope/point_cloud.py +++ b/src/polyscope/point_cloud.py @@ -1,19 +1,21 @@ import polyscope_bindings as psb from polyscope.core import str_to_datatype, str_to_vectortype, glm3, str_to_point_render_mode, point_render_mode_to_str - +from polyscope.structure import Structure from polyscope.common import process_color_args, process_scalar_args, process_vector_args, process_parameterization_args -class PointCloud: +class PointCloud(Structure): # This class wraps a _reference_ to the underlying object, whose lifetime is managed by Polyscope # End users should not call this constrctor, use register_point_cloud instead def __init__(self, name=None, points=None, instance=None): + super().__init__() + if instance is not None: # Wrap an existing instance - self.bound_cloud = instance + self.bound_instance = instance else: # Create a new instance @@ -21,9 +23,9 @@ def __init__(self, name=None, points=None, instance=None): self.check_shape(points) if points.shape[1] == 3: - self.bound_cloud = psb.register_point_cloud(name, points) + self.bound_instance = psb.register_point_cloud(name, points) elif points.shape[1] == 2: - self.bound_cloud = psb.register_point_cloud2D(name, points) + self.bound_instance = psb.register_point_cloud2D(name, points) else: raise ValueError("bad point cloud shape") @@ -34,112 +36,52 @@ def check_shape(self, points): if (len(points.shape) != 2) or (points.shape[1] not in (2,3)): raise ValueError("Point cloud positions should have shape (N,3); shape is " + str(points.shape)) - def n_points(self): - return self.bound_cloud.n_points() + return self.bound_instance.n_points() - ## Structure management - - def remove(self): - '''Remove the structure itself''' - self.bound_cloud.remove() - def remove_all_quantities(self): - '''Remove all quantities on the structure''' - self.bound_cloud.remove_all_quantities() - def remove_quantity(self, name): - '''Remove a single quantity on the structure''' - self.bound_cloud.remove_quantity(name) - - # Enable/disable - def set_enabled(self, val=True): - self.bound_cloud.set_enabled(val) - def is_enabled(self): - return self.bound_cloud.is_enabled() - - # Transparency - def set_transparency(self, val): - self.bound_cloud.set_transparency(val) - def get_transparency(self): - return self.bound_cloud.get_transparency() - - # Transformation things - def center_bounding_box(self): - self.bound_cloud.center_bounding_box() - def rescale_to_unit(self): - self.bound_cloud.rescale_to_unit() - def reset_transform(self): - self.bound_cloud.reset_transform() - def set_transform(self, new_mat4x4): - self.bound_cloud.set_transform(new_mat4x4) - def set_position(self, new_vec3): - self.bound_cloud.set_position(new_vec3) - def translate(self, trans_vec3): - self.bound_cloud.translate(trans_vec3) - def get_transform(self): - return self.bound_cloud.get_transform() - def get_position(self): - return self.bound_cloud.get_position() - # Point render mode def set_point_render_mode(self, val): - self.bound_cloud.set_point_render_mode(str_to_point_render_mode(val)) + self.bound_instance.set_point_render_mode(str_to_point_render_mode(val)) def get_point_render_mode(self): - return point_render_mode_to_str(self.bound_cloud.get_point_render_mode()) - - # Slice planes - def set_cull_whole_elements(self, val): - self.bound_cloud.set_cull_whole_elements(val) - def get_cull_whole_elements(self): - return self.bound_cloud.get_cull_whole_elements() - def set_ignore_slice_plane(self, plane, val): - # take either a string or a slice plane object as input - if isinstance(plane, str): - self.bound_cloud.set_ignore_slice_plane(plane, val) - else: - self.bound_cloud.set_ignore_slice_plane(plane.get_name(), val) - def get_ignore_slice_plane(self, plane): - # take either a string or a slice plane object as input - if isinstance(plane, str): - return self.bound_cloud.get_ignore_slice_plane(plane) - else: - return self.bound_cloud.get_ignore_slice_plane(plane.get_name()) + return point_render_mode_to_str(self.bound_instance.get_point_render_mode()) + # Update def update_point_positions(self, points): self.check_shape(points) if points.shape[1] == 3: - self.bound_cloud.update_point_positions(points) + self.bound_instance.update_point_positions(points) elif points.shape[1] == 2: - self.bound_cloud.update_point_positions2D(points) + self.bound_instance.update_point_positions2D(points) else: raise ValueError("bad point cloud shape") def set_point_radius_quantity(self, quantity_name, autoscale=True): - self.bound_cloud.set_point_radius_quantity(quantity_name, autoscale) + self.bound_instance.set_point_radius_quantity(quantity_name, autoscale) def clear_point_radius_quantity(self): - self.bound_cloud.clear_point_radius_quantity() + self.bound_instance.clear_point_radius_quantity() ## Options # Point radius def set_radius(self, rad, relative=True): - self.bound_cloud.set_radius(rad, relative) + self.bound_instance.set_radius(rad, relative) def get_radius(self): - return self.bound_cloud.get_radius() + return self.bound_instance.get_radius() # Point color def set_color(self, val): - self.bound_cloud.set_color(glm3(val)) + self.bound_instance.set_color(glm3(val)) def get_color(self): - return self.bound_cloud.get_color().as_tuple() + return self.bound_instance.get_color().as_tuple() # Point material def set_material(self, mat): - self.bound_cloud.set_material(mat) + self.bound_instance.set_material(mat) def get_material(self): - return self.bound_cloud.get_material() + return self.bound_instance.get_material() ## Quantities @@ -148,7 +90,7 @@ def get_material(self): def add_scalar_quantity(self, name, values, datatype="standard", **scalar_args): if len(values.shape) != 1 or values.shape[0] != self.n_points(): raise ValueError("'values' should be a length-N array") - q = self.bound_cloud.add_scalar_quantity(name, values, str_to_datatype(datatype)) + q = self.bound_instance.add_scalar_quantity(name, values, str_to_datatype(datatype)) process_scalar_args(self, q, scalar_args) @@ -157,7 +99,7 @@ def add_scalar_quantity(self, name, values, datatype="standard", **scalar_args): def add_color_quantity(self, name, values, **color_args): if len(values.shape) != 2 or values.shape[0] != self.n_points() or values.shape[1] != 3: raise ValueError("'values' should be an Nx3 array") - q = self.bound_cloud.add_color_quantity(name, values) + q = self.bound_instance.add_color_quantity(name, values) process_color_args(self, q, color_args) @@ -167,9 +109,9 @@ def add_vector_quantity(self, name, values, vectortype="standard", **vector_args if len(values.shape) != 2 or values.shape[0] != self.n_points() or values.shape[1] not in [2,3]: raise ValueError("'values' should be an Nx3 array (or Nx2 for 2D)") if values.shape[1] == 2: - q = self.bound_cloud.add_vector_quantity2D(name, values, str_to_vectortype(vectortype)) + q = self.bound_instance.add_vector_quantity2D(name, values, str_to_vectortype(vectortype)) elif values.shape[1] == 3: - q = self.bound_cloud.add_vector_quantity(name, values, str_to_vectortype(vectortype)) + q = self.bound_instance.add_vector_quantity(name, values, str_to_vectortype(vectortype)) process_vector_args(self, q, vector_args) diff --git a/src/polyscope/structure.py b/src/polyscope/structure.py new file mode 100644 index 0000000..a8d118f --- /dev/null +++ b/src/polyscope/structure.py @@ -0,0 +1,73 @@ +import polyscope_bindings as psb + +# Base class for common properties and methods on structures +class Structure: + + def __init__(self): + self.bound_instance = None + + + ## Structure management + + def remove(self): + '''Remove the structure itself''' + self.bound_instance.remove() + def remove_all_quantities(self): + '''Remove all quantities on the structure''' + self.bound_instance.remove_all_quantities() + def remove_quantity(self, name): + '''Remove a single quantity on the structure''' + self.bound_instance.remove_quantity(name) + + + ## Enable/disable + + def set_enabled(self, val=True): + self.bound_instance.set_enabled(val) + def is_enabled(self): + return self.bound_instance.is_enabled() + + ## Transparency + + def set_transparency(self, val): + self.bound_instance.set_transparency(val) + def get_transparency(self): + return self.bound_instance.get_transparency() + + ## Transformation things + + def center_bounding_box(self): + self.bound_instance.center_bounding_box() + def rescale_to_unit(self): + self.bound_instance.rescale_to_unit() + def reset_transform(self): + self.bound_instance.reset_transform() + def set_transform(self, new_mat4x4): + self.bound_instance.set_transform(new_mat4x4) + def set_position(self, new_vec3): + self.bound_instance.set_position(new_vec3) + def translate(self, trans_vec3): + self.bound_instance.translate(trans_vec3) + def get_transform(self): + return self.bound_instance.get_transform() + def get_position(self): + return self.bound_instance.get_position() + + ## Slice planes + + def set_cull_whole_elements(self, val): + self.bound_instance.set_cull_whole_elements(val) + def get_cull_whole_elements(self): + return self.bound_instance.get_cull_whole_elements() + def set_ignore_slice_plane(self, plane, val): + # take either a string or a slice plane object as input + if isinstance(plane, str): + self.bound_instance.set_ignore_slice_plane(plane, val) + else: + self.bound_instance.set_ignore_slice_plane(plane.get_name(), val) + def get_ignore_slice_plane(self, plane): + # take either a string or a slice plane object as input + if isinstance(plane, str): + return self.bound_instance.get_ignore_slice_plane(plane) + else: + return self.bound_instance.get_ignore_slice_plane(plane.get_name()) diff --git a/src/polyscope/surface_mesh.py b/src/polyscope/surface_mesh.py index 9fb23a5..e20a318 100644 --- a/src/polyscope/surface_mesh.py +++ b/src/polyscope/surface_mesh.py @@ -4,19 +4,21 @@ from polyscope.core import str_to_datatype, str_to_vectortype, str_to_param_coords_type, \ str_to_param_viz_style, str_to_back_face_policy, back_face_policy_to_str,\ glm3 - +from polyscope.structure import Structure from polyscope.common import process_color_args, process_scalar_args, process_vector_args, process_parameterization_args -class SurfaceMesh: +class SurfaceMesh(Structure): # This class wraps a _reference_ to the underlying object, whose lifetime is managed by Polyscope # End users should not call this constrctor, use register_surface_mesh instead def __init__(self, name=None, vertices=None, faces=None, instance=None): + + super().__init__() if instance is not None: # Wrap an existing instance - self.bound_mesh = instance + self.bound_instance = instance else: # Create a new instance @@ -28,9 +30,9 @@ def __init__(self, name=None, vertices=None, faces=None, instance=None): if (len(faces.shape) != 2): raise ValueError("surface mesh face array should have shape (F,D) for some D; shape is " + str(faces.shape)) if vertices.shape[1] == 3: - self.bound_mesh = psb.register_surface_mesh(name, vertices, faces) + self.bound_instance = psb.register_surface_mesh(name, vertices, faces) elif vertices.shape[1] == 2: - self.bound_mesh = psb.register_surface_mesh2D(name, vertices, faces) + self.bound_instance = psb.register_surface_mesh2D(name, vertices, faces) else: # Faces is something else, try to iterate through it to build a list of lists @@ -40,9 +42,9 @@ def __init__(self, name=None, vertices=None, faces=None, instance=None): faces_copy.append(f_copy) if vertices.shape[1] == 3: - self.bound_mesh = psb.register_surface_mesh_list(name, vertices, faces) + self.bound_instance = psb.register_surface_mesh_list(name, vertices, faces) elif vertices.shape[1] == 2: - self.bound_mesh = psb.register_surface_mesh_list2D(name, vertices, faces) + self.bound_instance = psb.register_surface_mesh_list2D(name, vertices, faces) @@ -53,85 +55,25 @@ def check_shape(self, points): def n_vertices(self): - return self.bound_mesh.n_vertices() + return self.bound_instance.n_vertices() def n_faces(self): - return self.bound_mesh.n_faces() + return self.bound_instance.n_faces() def n_edges(self): - return self.bound_mesh.n_edges() + return self.bound_instance.n_edges() def n_halfedges(self): - return self.bound_mesh.n_halfedges() + return self.bound_instance.n_halfedges() def n_corners(self): - return self.bound_mesh.n_corners() + return self.bound_instance.n_corners() - ## Structure management - - def remove(self): - '''Remove the structure itself''' - self.bound_mesh.remove() - def remove_all_quantities(self): - '''Remove all quantities on the structure''' - self.bound_mesh.remove_all_quantities() - def remove_quantity(self, name): - '''Remove a single quantity on the structure''' - self.bound_mesh.remove_quantity(name) - - # Enable/disable - def set_enabled(self, val=True): - self.bound_mesh.set_enabled(val) - def is_enabled(self): - return self.bound_mesh.is_enabled() - - # Transparency - def set_transparency(self, val): - self.bound_mesh.set_transparency(val) - def get_transparency(self): - return self.bound_mesh.get_transparency() - - # Transformation things - def center_bounding_box(self): - self.bound_mesh.center_bounding_box() - def rescale_to_unit(self): - self.bound_mesh.rescale_to_unit() - def reset_transform(self): - self.bound_mesh.reset_transform() - def set_transform(self, new_mat4x4): - self.bound_mesh.set_transform(new_mat4x4) - def set_position(self, new_vec3): - self.bound_mesh.set_position(new_vec3) - def translate(self, trans_vec3): - self.bound_mesh.translate(trans_vec3) - def get_transform(self): - return self.bound_mesh.get_transform() - def get_position(self): - return self.bound_mesh.get_position() - - - # Slice planes - def set_cull_whole_elements(self, val): - self.bound_mesh.set_cull_whole_elements(val) - def get_cull_whole_elements(self): - return self.bound_mesh.get_cull_whole_elements() - def set_ignore_slice_plane(self, plane, val): - # take either a string or a slice plane object as input - if isinstance(plane, str): - self.bound_mesh.set_ignore_slice_plane(plane, val) - else: - self.bound_mesh.set_ignore_slice_plane(plane.get_name(), val) - def get_ignore_slice_plane(self, plane): - # take either a string or a slice plane object as input - if isinstance(plane, str): - return self.bound_mesh.get_ignore_slice_plane(plane) - else: - return self.bound_mesh.get_ignore_slice_plane(plane.get_name()) # Update def update_vertex_positions(self, vertices): self.check_shape(vertices) if vertices.shape[1] == 3: - self.bound_mesh.update_vertex_positions(vertices) + self.bound_instance.update_vertex_positions(vertices) elif vertices.shape[1] == 2: - self.bound_mesh.update_vertex_positions2D(vertices) + self.bound_instance.update_vertex_positions2D(vertices) else: raise ValueError("bad vertex shape") @@ -140,62 +82,62 @@ def update_vertex_positions(self, vertices): # Color def set_color(self, val): - self.bound_mesh.set_color(glm3(val)) + self.bound_instance.set_color(glm3(val)) def get_color(self): - return self.bound_mesh.get_color().as_tuple() + return self.bound_instance.get_color().as_tuple() # Edge Color def set_edge_color(self, val): - self.bound_mesh.set_edge_color(glm3(val)) + self.bound_instance.set_edge_color(glm3(val)) def get_edge_color(self): - return self.bound_mesh.get_edge_color().as_tuple() + return self.bound_instance.get_edge_color().as_tuple() # Edge width def set_edge_width(self, val): - self.bound_mesh.set_edge_width(val) + self.bound_instance.set_edge_width(val) def get_edge_width(self): - return self.bound_mesh.get_edge_width() + return self.bound_instance.get_edge_width() # Smooth shade def set_smooth_shade(self, val): - self.bound_mesh.set_smooth_shade(val) + self.bound_instance.set_smooth_shade(val) def get_smooth_shade(self): - return self.bound_mesh.get_smooth_shade() + return self.bound_instance.get_smooth_shade() # Material def set_material(self, mat): - self.bound_mesh.set_material(mat) + self.bound_instance.set_material(mat) def get_material(self): - return self.bound_mesh.get_material() + return self.bound_instance.get_material() # Color def set_back_face_policy(self, val): - self.bound_mesh.set_back_face_policy(str_to_back_face_policy(val)) + self.bound_instance.set_back_face_policy(str_to_back_face_policy(val)) def get_back_face_policy(self): - return back_face_policy_to_str(self.bound_mesh.get_back_face_policy()) + return back_face_policy_to_str(self.bound_instance.get_back_face_policy()) # Back face color def set_back_face_color(self, val): - self.bound_mesh.set_back_face_color(glm3(val)) + self.bound_instance.set_back_face_color(glm3(val)) def get_back_face_color(self): - return self.bound_mesh.get_back_face_color().as_tuple() + return self.bound_instance.get_back_face_color().as_tuple() ## Permutations and bases def set_edge_permutation(self, perm, expected_size=None): if len(perm.shape) != 1 or perm.shape[0] != self.n_edges(): raise ValueError("'perm' should be an array with one entry per edge") if expected_size is None: expected_size = 0 - self.bound_mesh.set_edge_permutation(perm, expected_size) + self.bound_instance.set_edge_permutation(perm, expected_size) def set_corner_permutation(self, perm, expected_size=None): if len(perm.shape) != 1 or perm.shape[0] != self.n_corners(): raise ValueError("'perm' should be an array with one entry per corner") if expected_size is None: expected_size = 0 - self.bound_mesh.set_corner_permutation(perm, expected_size) + self.bound_instance.set_corner_permutation(perm, expected_size) def set_halfedge_permutation(self, perm, expected_size=None): if len(perm.shape) != 1 or perm.shape[0] != self.n_halfedges(): raise ValueError("'perm' should be an array with one entry per halfedge") if expected_size is None: expected_size = 0 - self.bound_mesh.set_halfedge_permutation(perm, expected_size) + self.bound_instance.set_halfedge_permutation(perm, expected_size) def set_all_permutations(self, vertex_perm=None, vertex_perm_size=None, # now ignored @@ -219,16 +161,16 @@ def add_scalar_quantity(self, name, values, defined_on='vertices', datatype="sta if defined_on == 'vertices': if values.shape[0] != self.n_vertices(): raise ValueError("'values' should be a length n_vertices array") - q = self.bound_mesh.add_vertex_scalar_quantity(name, values, str_to_datatype(datatype)) + q = self.bound_instance.add_vertex_scalar_quantity(name, values, str_to_datatype(datatype)) elif defined_on == 'faces': if values.shape[0] != self.n_faces(): raise ValueError("'values' should be a length n_faces array") - q = self.bound_mesh.add_face_scalar_quantity(name, values, str_to_datatype(datatype)) + q = self.bound_instance.add_face_scalar_quantity(name, values, str_to_datatype(datatype)) elif defined_on == 'edges': if values.shape[0] != self.n_edges(): raise ValueError("'values' should be a length n_edges array") - q = self.bound_mesh.add_edge_scalar_quantity(name, values, str_to_datatype(datatype)) + q = self.bound_instance.add_edge_scalar_quantity(name, values, str_to_datatype(datatype)) elif defined_on == 'halfedges': if values.shape[0] != self.n_halfedges(): raise ValueError("'values' should be a length n_halfedges array") - q = self.bound_mesh.add_halfedge_scalar_quantity(name, values, str_to_datatype(datatype)) + q = self.bound_instance.add_halfedge_scalar_quantity(name, values, str_to_datatype(datatype)) else: raise ValueError("bad `defined_on` value {}, should be one of ['vertices', 'faces', 'edges', 'halfedges']".format(defined_on)) @@ -242,10 +184,10 @@ def add_color_quantity(self, name, values, defined_on='vertices', **color_args): if defined_on == 'vertices': if values.shape[0] != self.n_vertices(): raise ValueError("'values' should be a length n_vertices array") - q = self.bound_mesh.add_vertex_color_quantity(name, values) + q = self.bound_instance.add_vertex_color_quantity(name, values) elif defined_on == 'faces': if values.shape[0] != self.n_faces(): raise ValueError("'values' should be a length n_faces array") - q = self.bound_mesh.add_face_color_quantity(name, values) + q = self.bound_instance.add_face_color_quantity(name, values) else: raise ValueError("bad `defined_on` value {}, should be one of ['vertices', 'faces']".format(defined_on)) @@ -262,9 +204,9 @@ def add_distance_quantity(self, name, values, defined_on='vertices', enabled=Non if values.shape[0] != self.n_vertices(): raise ValueError("'values' should be a length n_vertices array") if signed: - q = self.bound_mesh.add_vertex_signed_distance_quantity(name, values) + q = self.bound_instance.add_vertex_signed_distance_quantity(name, values) else: - q = self.bound_mesh.add_vertex_distance_quantity(name, values) + q = self.bound_instance.add_vertex_distance_quantity(name, values) else: raise ValueError("bad `defined_on` value {}, should be one of ['vertices']".format(defined_on)) @@ -290,10 +232,10 @@ def add_parameterization_quantity(self, name, values, defined_on='vertices', coo if defined_on == 'vertices': if values.shape[0] != self.n_vertices(): raise ValueError("'values' should be a length n_vertices array") - q = self.bound_mesh.add_vertex_parameterization_quantity(name, values, coords_type_enum) + q = self.bound_instance.add_vertex_parameterization_quantity(name, values, coords_type_enum) elif defined_on == 'corners': if values.shape[0] != self.n_corners(): raise ValueError("'values' should be a length n_faces array") - q = self.bound_mesh.add_corner_parameterization_quantity(name, values, coords_type_enum) + q = self.bound_instance.add_corner_parameterization_quantity(name, values, coords_type_enum) else: raise ValueError("bad `defined_on` value {}, should be one of ['vertices', 'corners']".format(defined_on)) @@ -309,17 +251,17 @@ def add_vector_quantity(self, name, values, defined_on='vertices', vectortype="s if values.shape[0] != self.n_vertices(): raise ValueError("'values' should be a length n_vertices array") if values.shape[1] == 2: - q = self.bound_mesh.add_vertex_vector_quantity2D(name, values, str_to_vectortype(vectortype)) + q = self.bound_instance.add_vertex_vector_quantity2D(name, values, str_to_vectortype(vectortype)) elif values.shape[1] == 3: - q = self.bound_mesh.add_vertex_vector_quantity(name, values, str_to_vectortype(vectortype)) + q = self.bound_instance.add_vertex_vector_quantity(name, values, str_to_vectortype(vectortype)) elif defined_on == 'faces': if values.shape[0] != self.n_faces(): raise ValueError("'values' should be a length n_faces array") if values.shape[1] == 2: - q = self.bound_mesh.add_face_vector_quantity2D(name, values, str_to_vectortype(vectortype)) + q = self.bound_instance.add_face_vector_quantity2D(name, values, str_to_vectortype(vectortype)) elif values.shape[1] == 3: - q = self.bound_mesh.add_face_vector_quantity(name, values, str_to_vectortype(vectortype)) + q = self.bound_instance.add_face_vector_quantity(name, values, str_to_vectortype(vectortype)) else: raise ValueError("bad `defined_on` value {}, should be one of ['vertices', 'faces']".format(defined_on)) @@ -340,14 +282,14 @@ def add_tangent_vector_quantity(self, name, values, basisX, basisY, n_sym=1, def if basisX.shape[0] != self.n_vertices(): raise ValueError("'basisX' should be a length n_vertices array") if basisY.shape[0] != self.n_vertices(): raise ValueError("'basisY' should be a length n_vertices array") - q = self.bound_mesh.add_vertex_tangent_vector_quantity(name, values, basisX, basisY, n_sym, str_to_vectortype(vectortype)) + q = self.bound_instance.add_vertex_tangent_vector_quantity(name, values, basisX, basisY, n_sym, str_to_vectortype(vectortype)) elif defined_on == 'faces': if values.shape[0] != self.n_faces(): raise ValueError("'values' should be a length n_faces array") if basisX.shape[0] != self.n_faces(): raise ValueError("'basisX' should be a length n_faces array") if basisY.shape[0] != self.n_faces(): raise ValueError("'basisY' should be a length n_faces array") - q = self.bound_mesh.add_face_tangent_vector_quantity(name, values, basisX, basisY, n_sym, str_to_vectortype(vectortype)) + q = self.bound_instance.add_face_tangent_vector_quantity(name, values, basisX, basisY, n_sym, str_to_vectortype(vectortype)) else: raise ValueError("bad `defined_on` value {}, should be one of ['vertices', 'faces']".format(defined_on)) @@ -361,7 +303,7 @@ def add_one_form_vector_quantity(self, name, values, orientations, **vector_args if len(values.shape) != 1 or values.shape[0] != self.n_edges(): raise ValueError("'values' should be length n_edges array") if len(orientations.shape) != 1 or orientations.shape[0] != self.n_edges(): raise ValueError("'orientations' should be length n_edges array") - q = self.bound_mesh.add_one_form_tangent_vector_quantity(name, values, orientations) + q = self.bound_instance.add_one_form_tangent_vector_quantity(name, values, orientations) process_vector_args(self, q, vector_args) diff --git a/src/polyscope/volume_mesh.py b/src/polyscope/volume_mesh.py index 12d600f..23cf963 100644 --- a/src/polyscope/volume_mesh.py +++ b/src/polyscope/volume_mesh.py @@ -2,19 +2,21 @@ import numpy as np from polyscope.core import str_to_datatype, str_to_vectortype, str_to_param_coords_type, str_to_param_viz_style, glm3 - +from polyscope.structure import Structure from polyscope.common import process_color_args, process_scalar_args, process_vector_args, process_parameterization_args -class VolumeMesh: +class VolumeMesh(Structure): # This class wraps a _reference_ to the underlying object, whose lifetime is managed by Polyscope # End users should not call this constrctor, use register_volume_mesh instead def __init__(self, name=None, vertices=None, tets=None, hexes=None, mixed_cells=None, instance=None): + + super().__init__() if instance is not None: # Wrap an existing instance - self.bound_mesh = instance + self.bound_instance = instance else: # Create a new instance @@ -28,15 +30,15 @@ def __init__(self, name=None, vertices=None, tets=None, hexes=None, mixed_cells= raise ValueError("specify EITHER mixed_cells OR tets/hexes but not both") if mixed_cells is not None: - self.bound_mesh = psb.register_volume_mesh(name, vertices, mixed_cells) + self.bound_instance = psb.register_volume_mesh(name, vertices, mixed_cells) else: if tets is None: - self.bound_mesh = psb.register_hex_mesh(name, vertices, hexes) + self.bound_instance = psb.register_hex_mesh(name, vertices, hexes) elif hexes is None: - self.bound_mesh = psb.register_tet_mesh(name, vertices, tets) + self.bound_instance = psb.register_tet_mesh(name, vertices, tets) else: - self.bound_mesh = psb.register_tet_hex_mesh(name, vertices, tets, hexes) + self.bound_instance = psb.register_tet_hex_mesh(name, vertices, tets, hexes) def check_shape(self, points): @@ -65,110 +67,51 @@ def check_index_array(self, arr, dim, name): def n_vertices(self): - return self.bound_mesh.n_vertices() + return self.bound_instance.n_vertices() def n_faces(self): - return self.bound_mesh.n_faces() + return self.bound_instance.n_faces() def n_cells(self): - return self.bound_mesh.n_cells() + return self.bound_instance.n_cells() ## Structure management - - def remove(self): - '''Remove the structure itself''' - self.bound_mesh.remove() - def remove_all_quantities(self): - '''Remove all quantities on the structure''' - self.bound_mesh.remove_all_quantities() - def remove_quantity(self, name): - '''Remove a single quantity on the structure''' - self.bound_mesh.remove_quantity(name) - - # Enable/disable - def set_enabled(self, val=True): - self.bound_mesh.set_enabled(val) - def is_enabled(self): - return self.bound_mesh.is_enabled() - - # Transparency - def set_transparency(self, val): - self.bound_mesh.set_transparency(val) - def get_transparency(self): - return self.bound_mesh.get_transparency() - - # Transformation things - def center_bounding_box(self): - self.bound_mesh.center_bounding_box() - def rescale_to_unit(self): - self.bound_mesh.rescale_to_unit() - def reset_transform(self): - self.bound_mesh.reset_transform() - def set_transform(self, new_mat4x4): - self.bound_mesh.set_transform(new_mat4x4) - def set_position(self, new_vec3): - self.bound_mesh.set_position(new_vec3) - def translate(self, trans_vec3): - self.bound_mesh.translate(trans_vec3) - def get_transform(self): - return self.bound_mesh.get_transform() - def get_position(self): - return self.bound_mesh.get_position() - - # Slice planes - def set_cull_whole_elements(self, val): - self.bound_mesh.set_cull_whole_elements(val) - def get_cull_whole_elements(self): - return self.bound_mesh.get_cull_whole_elements() - def set_ignore_slice_plane(self, plane, val): - # take either a string or a slice plane object as input - if isinstance(plane, str): - self.bound_mesh.set_ignore_slice_plane(plane, val) - else: - self.bound_mesh.set_ignore_slice_plane(plane.get_name(), val) - def get_ignore_slice_plane(self, plane): - # take either a string or a slice plane object as input - if isinstance(plane, str): - return self.bound_mesh.get_ignore_slice_plane(plane) - else: - return self.bound_mesh.get_ignore_slice_plane(plane.get_name()) - # Update def update_vertex_positions(self, vertices): self.check_shape(vertices) - self.bound_mesh.update_vertex_positions(vertices) + self.bound_instance.update_vertex_positions(vertices) ## Options # Color def set_color(self, val): - self.bound_mesh.set_color(glm3(val)) + self.bound_instance.set_color(glm3(val)) def get_color(self): - return self.bound_mesh.get_color().as_tuple() + return self.bound_instance.get_color().as_tuple() # Interior Color def set_interior_color(self, val): - self.bound_mesh.set_interior_color(glm3(val)) + self.bound_instance.set_interior_color(glm3(val)) def get_interior_color(self): - return self.bound_mesh.get_interior_color().as_tuple() + return self.bound_instance.get_interior_color().as_tuple() # Edge Color def set_edge_color(self, val): - self.bound_mesh.set_edge_color(glm3(val)) + self.bound_instance.set_edge_color(glm3(val)) def get_edge_color(self): - return self.bound_mesh.get_edge_color().as_tuple() + return self.bound_instance.get_edge_color().as_tuple() # Edge width def set_edge_width(self, val): - self.bound_mesh.set_edge_width(val) + self.bound_instance.set_edge_width(val) def get_edge_width(self): - return self.bound_mesh.get_edge_width() + return self.bound_instance.get_edge_width() # Material def set_material(self, mat): - self.bound_mesh.set_material(mat) + self.bound_instance.set_material(mat) def get_material(self): - return self.bound_mesh.get_material() + return self.bound_instance.get_material() ## Quantities @@ -180,10 +123,10 @@ def add_scalar_quantity(self, name, values, defined_on='vertices', datatype="sta if defined_on == 'vertices': if values.shape[0] != self.n_vertices(): raise ValueError("'values' should be a length n_vertices array") - q = self.bound_mesh.add_vertex_scalar_quantity(name, values, str_to_datatype(datatype)) + q = self.bound_instance.add_vertex_scalar_quantity(name, values, str_to_datatype(datatype)) elif defined_on == 'cells': if values.shape[0] != self.n_cells(): raise ValueError("'values' should be a length n_cells array") - q = self.bound_mesh.add_cell_scalar_quantity(name, values, str_to_datatype(datatype)) + q = self.bound_instance.add_cell_scalar_quantity(name, values, str_to_datatype(datatype)) else: raise ValueError("bad `defined_on` value {}, should be one of ['vertices', 'cells']".format(defined_on)) @@ -198,10 +141,10 @@ def add_color_quantity(self, name, values, defined_on='vertices', **color_args): if defined_on == 'vertices': if values.shape[0] != self.n_vertices(): raise ValueError("'values' should be a length n_vertices array") - q = self.bound_mesh.add_vertex_color_quantity(name, values) + q = self.bound_instance.add_vertex_color_quantity(name, values) elif defined_on == 'cells': if values.shape[0] != self.n_cells(): raise ValueError("'values' should be a length n_cells array") - q = self.bound_mesh.add_cell_color_quantity(name, values) + q = self.bound_instance.add_cell_color_quantity(name, values) else: raise ValueError("bad `defined_on` value {}, should be one of ['vertices', 'cells']".format(defined_on)) @@ -216,10 +159,10 @@ def add_vector_quantity(self, name, values, defined_on='vertices', vectortype="s if defined_on == 'vertices': if values.shape[0] != self.n_vertices(): raise ValueError("'values' should be a length n_vertices array") - q = self.bound_mesh.add_vertex_vector_quantity(name, values, str_to_vectortype(vectortype)) + q = self.bound_instance.add_vertex_vector_quantity(name, values, str_to_vectortype(vectortype)) elif defined_on == 'cells': if values.shape[0] != self.n_cells(): raise ValueError("'values' should be a length n_cells array") - q = self.bound_mesh.add_cell_vector_quantity(name, values, str_to_vectortype(vectortype)) + q = self.bound_instance.add_cell_vector_quantity(name, values, str_to_vectortype(vectortype)) else: raise ValueError("bad `defined_on` value {}, should be one of ['vertices', 'cells']".format(defined_on)) From 6a255aaff65bc397416dc8ceea00260fe5cb2ae6 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Mon, 19 Jun 2023 16:05:06 -0700 Subject: [PATCH 15/88] tests and fixes for camera views --- src/cpp/camera_view.cpp | 2 +- src/polyscope/__init__.py | 1 + src/polyscope/camera_view.py | 76 +++----------------------------- src/polyscope/core.py | 12 ++--- test/polyscope_test.py | 85 ++++++++++++++++++++++++++++++++++++ 5 files changed, 99 insertions(+), 77 deletions(-) diff --git a/src/cpp/camera_view.cpp b/src/cpp/camera_view.cpp index 2bfaf99..2da808b 100644 --- a/src/cpp/camera_view.cpp +++ b/src/cpp/camera_view.cpp @@ -38,7 +38,7 @@ void bind_camera_view(py::module& m) { // Static adders and getters - m.def("register_camera_view_vec", &ps::registerCameraView, + m.def("register_camera_view", &ps::registerCameraView, py::arg("name"), py::arg("parameters"), "Register a camera view", py::return_value_policy::reference); m.def("remove_camera_view", &ps::removeCameraView, "Remove a camera view by name"); m.def("get_camera_view", &ps::getCameraView, "Get a camera view by name", py::return_value_policy::reference); diff --git a/src/polyscope/__init__.py b/src/polyscope/__init__.py index 26a2fc5..0df5541 100644 --- a/src/polyscope/__init__.py +++ b/src/polyscope/__init__.py @@ -3,3 +3,4 @@ from polyscope.point_cloud import * from polyscope.curve_network import * from polyscope.volume_mesh import * +from polyscope.camera_view import * diff --git a/src/polyscope/camera_view.py b/src/polyscope/camera_view.py index b91e2f1..159da7c 100644 --- a/src/polyscope/camera_view.py +++ b/src/polyscope/camera_view.py @@ -8,70 +8,6 @@ import numpy as np -''' -def canonicalize_camera_params(root, look_dir, up_dir, view_mat, fov_vert_deg, fov_horz_deg, aspect): - - # validate combinations of args - - # check for a valid combination of extrinsics - ext_as_vec = (root is not None) or (look_dir is not None) or (up_dir is not None) - if ext_as_vec: - if (root is None) or (look_dir is None) or (up_dir is None): - raise ValueError("if any of 'root', 'look_dir', 'up_dir' is specified, they all must be") - if view_mat is not None: - raise ValueError("if any of 'root', 'look_dir', 'up_dir' is specified, 'view_mat' cannot be specified") - - # convert to glm vectors - root = glm3(root) - look_dir = glm3(look_dir) - up_dir = glm3(up_dir) - - ext_vals = { - 'style' : 'vec', - 'root' : root, - 'look_dir' : look_dir, - 'up_dir' : up_dir, - } - - ext_as_mat = view_mat is not None - if ext_as_mat: - view_mat = np.array(view_mat) - - ext_vals = { - 'style' : 'mat', - 'view_mat' : view_mat, - } - - - # check for a valid combination of intrinsics - # aspect is defined as "width over height" - if fov_vert_deg is not None: - if aspect is not None: - pass - elif fov_horz_deg is not None: - aspect = tan(0.5*radians(float(fov_horz_deg))) / tan(0.5*radians(float(fov_vert_deg))) - else: - raise ValueError("must pass one of 'fov_horz_deg' or 'aspect'") - - elif fov_horz_deg is not None: - if aspect is not None: - fov_vert_deg = degrees(2. * atan(tan(0.5*radians(float(fov_horz_deg))) / aspect)) - else: - raise ValueError("must pass one of 'fov_horz_deg' or 'aspect'") - else: - raise ValueError("must pass one of 'fov_vert_deg' or 'fov_horz_deg'") - - int_vals = { - 'fov_vert_deg' : float(fov_vert_deg), - 'aspect' : float(aspect) - } - - - return ext_vals, int_vals -''' - - - class CameraView(Structure): # This class wraps a _reference_ to the underlying object, whose lifetime is managed by Polyscope @@ -87,21 +23,21 @@ def __init__(self, name=None, camera_parameters=None, instance=None): else: # Create a new instance - self.bound_instance = psb.register_camera_view_vec(name, camera_parameters) + self.bound_instance = psb.register_camera_view(name, camera_parameters.instance) # Update def update_camera_parameters(self, camera_parameters): - self.bound_instance.update_camera_parameters(camera_parameters) + self.bound_instance.update_camera_parameters(camera_parameters.instance) ## Camera things def set_view_to_this_camera(self, with_flight=False): - self.bound_cloud.set_view_to_this_camera(with_flight) + self.bound_instance.set_view_to_this_camera(with_flight) def get_parameters(self): - return CameraParameters(instance=self.bound_cloud.get_parameters()) + return CameraParameters(instance=self.bound_instance.get_parameters()) ## Options @@ -118,8 +54,8 @@ def get_widget_thickness(self): return self.bound_instance.get_widget_thickness() # Widget focal length - def set_widget_focal_length(self, val): - self.bound_instance.set_widget_focal_length(float(val)) + def set_widget_focal_length(self, val, relative=True): + self.bound_instance.set_widget_focal_length(float(val), relative) def get_widget_focal_length(self): return self.bound_instance.get_widget_focal_length() diff --git a/src/polyscope/core.py b/src/polyscope/core.py index 67d0ba7..9dbc6ab 100644 --- a/src/polyscope/core.py +++ b/src/polyscope/core.py @@ -257,18 +257,18 @@ class CameraIntrinsics: def __init__(self, fov_vertical_deg=None, fov_horizontal_deg=None, aspect=None): - if fov_vertical_deg is not None and fov_aspect is not None: - self.instance = psb.CameraIntrinsics.fromFoVDegVerticalAndAspect(float(fov_vertical_deg), float(aspect)) - elif fov_horizontal_deg is not None and fov_aspect is not None: - self.instance = psb.CameraIntrinsics.fromFoVDegHorizontalAndAspect(float(fov_horizontal_deg), float(aspect)) + if fov_vertical_deg is not None and aspect is not None: + self.instance = psb.CameraIntrinsics.from_FoV_deg_vertical_and_aspect(float(fov_vertical_deg), float(aspect)) + elif fov_horizontal_deg is not None and aspect is not None: + self.instance = psb.CameraIntrinsics.from_FoV_deg_horizontal_and_aspect(float(fov_horizontal_deg), float(aspect)) elif fov_vertical_deg is not None and fov_horizontal_deg is not None: - self.instance = psb.CameraIntrinsics.fromFoVDegHorizontalAndVertical(float(fov_horizontal_deg), float(fov_vertical_deg)) + self.instance = psb.CameraIntrinsics.from_FoV_deg_horizontal_and_vertical(float(fov_horizontal_deg), float(fov_vertical_deg)) else: raise ValueError("bad arguments, at least two of (fov_vertical_deg,fov_horizontal_deg,aspect) must be given and non-None") class CameraExtrinsics: - def __init__(self, mat=None): + def __init__(self, root=None, look_dir=None, up_dir=None, mat=None): if mat is not None: mat = np.array(mat) diff --git a/test/polyscope_test.py b/test/polyscope_test.py index 6f79df7..ac6648b 100644 --- a/test/polyscope_test.py +++ b/test/polyscope_test.py @@ -1519,6 +1519,91 @@ def test_vector(self): ps.remove_all_structures() +class TestCameraView(unittest.TestCase): + + def generate_parameters(self): + intrinsics = ps.CameraIntrinsics(fov_vertical_deg=60, aspect=2) + extrinsics = ps.CameraExtrinsics(root=(2., 2., 2.), look_dir=(-1., -1.,-1.), up_dir=(0.,1.,0.)) + return ps.CameraParameters(intrinsics, extrinsics) + + def test_add_remove(self): + + # add + cam = ps.register_camera_view("cam1", self.generate_parameters()) + self.assertTrue(ps.has_camera_view("cam1")) + self.assertFalse(ps.has_camera_view("nope")) + + # remove by name + ps.register_camera_view("cam2", self.generate_parameters()) + ps.remove_camera_view("cam2") + self.assertTrue(ps.has_camera_view("cam1")) + self.assertFalse(ps.has_camera_view("cam2")) + + # remove by ref + c = ps.register_camera_view("cam3", self.generate_parameters()) + c.remove() + self.assertTrue(ps.has_camera_view("cam1")) + self.assertFalse(ps.has_camera_view("cam3")) + + # get by name + ps.register_camera_view("cam3", self.generate_parameters()) + p = ps.get_camera_view("cam3") # should be wrapped instance, not underlying PSB instance + self.assertTrue(isinstance(p, ps.CameraView)) + + ps.remove_all_structures() + + def test_render(self): + + cam = ps.register_camera_view("cam1", self.generate_parameters()) + ps.show(3) + ps.remove_all_structures() + + def test_transform(self): + + cam = ps.register_camera_view("cam1", self.generate_parameters()) + test_transforms(self, cam) + ps.remove_all_structures() + + def test_options(self): + + cam = ps.register_camera_view("cam1", self.generate_parameters()) + + # widget color + color = (0.3, 0.3, 0.5) + cam.set_widget_color(color) + ret_color = cam.get_widget_color() + for i in range(3): + self.assertAlmostEqual(ret_color[i], color[i]) + + # widget thickness + cam.set_widget_thickness(0.03) + self.assertAlmostEqual(0.03, cam.get_widget_thickness()) + + # widget focal length + cam.set_widget_focal_length(0.03, False) + self.assertAlmostEqual(0.03, cam.get_widget_focal_length()) + + ps.show(3) + ps.remove_all_structures() + + def test_update(self): + + cam = ps.register_camera_view("cam1", self.generate_parameters()) + cam.update_camera_parameters(self.generate_parameters()) + + ps.show(3) + ps.remove_all_structures() + + def test_camera_things(self): + + cam = ps.register_camera_view("cam1", self.generate_parameters()) + cam.set_view_to_this_camera() + ps.show(3) + cam.set_view_to_this_camera(with_flight=True) + ps.show(3) + + ps.remove_all_structures() + if __name__ == '__main__': # Parse out test-specific args (this is kinda poor design, but very useful) From 9b3a660b57ebafbe1bb96c618b4bf163a4040d2a Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Mon, 3 Jul 2023 10:57:14 -0700 Subject: [PATCH 16/88] WIP on floating and cameras --- src/cpp/core.cpp | 4 +++- src/cpp/utils.h | 12 ++++++------ src/polyscope/common.py | 12 ++++++++++++ src/polyscope/core.py | 23 +++++++++++++++++++++++ src/polyscope/structure.py | 21 +++++++++++++++++++++ test/polyscope_test.py | 14 ++++++++++++++ 6 files changed, 79 insertions(+), 7 deletions(-) diff --git a/src/cpp/core.cpp b/src/cpp/core.cpp index 6965176..280c473 100644 --- a/src/cpp/core.cpp +++ b/src/cpp/core.cpp @@ -17,6 +17,8 @@ #include "polyscope/view.h" #include "polyscope/volume_mesh.h" +#include "utils.h" + namespace py = pybind11; namespace ps = polyscope; @@ -359,12 +361,12 @@ PYBIND11_MODULE(polyscope_bindings, m) { }); // === Bind structures defined in other files + bind_floating_quantities(m); bind_surface_mesh(m); bind_point_cloud(m); bind_curve_network(m); bind_volume_mesh(m); bind_camera_view(m); - bind_floating_quantities(m); bind_imgui(m); } diff --git a/src/cpp/utils.h b/src/cpp/utils.h index 509fdb0..a70468d 100644 --- a/src/cpp/utils.h +++ b/src/cpp/utils.h @@ -91,12 +91,12 @@ py::class_ bindStructure(py::module& m, std::string name) { .def("get_position", [](StructureT& s) { return glm2eigen(s.getPosition()); }, "get the position of the shape origin after transform") // floating quantites - .def("add_scalar_image_quantity", &StructureT::template addScalarImageQuantity, py::arg("name"), py::arg("dimX"), py::arg("dimY"), py::arg("values"), py::arg("imageOrigin")=ps::ImageOrigin::UpperLeft, py::arg("type")=ps::DataType::STANDARD) - .def("add_color_image_quantity", &StructureT::template addColorImageQuantity, py::arg("name"), py::arg("dimX"), py::arg("dimY"), py::arg("values_rgb"), py::arg("imageOrigin")=ps::ImageOrigin::UpperLeft) - .def("add_color_alpha_image_quantity", &StructureT::template addColorAlphaImageQuantity, py::arg("name"), py::arg("dimX"), py::arg("dimY"), py::arg("values_rgba"), py::arg("imageOrigin")=ps::ImageOrigin::UpperLeft) - .def("add_depth_render_image_quantity", &StructureT::template addDepthRenderImageQuantity, py::arg("name"), py::arg("dimX"), py::arg("dimY"), py::arg("depthData"), py::arg("normalData"), py::arg("imageOrigin")=ps::ImageOrigin::UpperLeft) - .def("add_color_render_image_quantity", &StructureT::template addColorRenderImageQuantity, py::arg("name"), py::arg("dimX"), py::arg("dimY"), py::arg("depthData"), py::arg("normalData"), py::arg("colorData"), py::arg("imageOrigin")=ps::ImageOrigin::UpperLeft) - .def("add_scalar_render_image_quantity", &StructureT::template addScalarRenderImageQuantity, py::arg("name"), py::arg("dimX"), py::arg("dimY"), py::arg("depthData"), py::arg("normalData"), py::arg("scalarData"), py::arg("imageOrigin")=ps::ImageOrigin::UpperLeft, py::arg("type")=ps::DataType::STANDARD) + .def("add_scalar_image_quantity", &StructureT::template addScalarImageQuantity, py::arg("name"), py::arg("dimX"), py::arg("dimY"), py::arg("values"), py::arg("imageOrigin")=ps::ImageOrigin::UpperLeft, py::arg("type")=ps::DataType::STANDARD, py::return_value_policy::reference) + .def("add_color_image_quantity", &StructureT::template addColorImageQuantity, py::arg("name"), py::arg("dimX"), py::arg("dimY"), py::arg("values_rgb"), py::arg("imageOrigin")=ps::ImageOrigin::UpperLeft, py::return_value_policy::reference) + .def("add_color_alpha_image_quantity", &StructureT::template addColorAlphaImageQuantity, py::arg("name"), py::arg("dimX"), py::arg("dimY"), py::arg("values_rgba"), py::arg("imageOrigin")=ps::ImageOrigin::UpperLeft, py::return_value_policy::reference) + .def("add_depth_render_image_quantity", &StructureT::template addDepthRenderImageQuantity, py::arg("name"), py::arg("dimX"), py::arg("dimY"), py::arg("depthData"), py::arg("normalData"), py::arg("imageOrigin")=ps::ImageOrigin::UpperLeft, py::return_value_policy::reference) + .def("add_color_render_image_quantity", &StructureT::template addColorRenderImageQuantity, py::arg("name"), py::arg("dimX"), py::arg("dimY"), py::arg("depthData"), py::arg("normalData"), py::arg("colorData"), py::arg("imageOrigin")=ps::ImageOrigin::UpperLeft, py::return_value_policy::reference) + .def("add_scalar_render_image_quantity", &StructureT::template addScalarRenderImageQuantity, py::arg("name"), py::arg("dimX"), py::arg("dimY"), py::arg("depthData"), py::arg("normalData"), py::arg("scalarData"), py::arg("imageOrigin")=ps::ImageOrigin::UpperLeft, py::arg("type")=ps::DataType::STANDARD, py::return_value_policy::reference) ; diff --git a/src/polyscope/common.py b/src/polyscope/common.py index f7377d0..6078c46 100644 --- a/src/polyscope/common.py +++ b/src/polyscope/common.py @@ -3,6 +3,18 @@ from polyscope.core import str_to_datatype, str_to_vectortype, glm3, str_to_point_render_mode, point_render_mode_to_str, str_to_param_viz_style +def check_is_scalar_image(values, dimX, dimY): + if len(values.shape) != 2 or values.shape[0] != dimX or values.shape[1] != dimY: + raise ValueError(f"'values' should be a ({dimX},{dimY}) array") + +def check_is_color_image(values, dimX, dimY): + if len(values.shape) != 3 or values.shape[0] != dimX or values.shape[1] != dimY or values.shape[2] != 3: + raise ValueError(f"'values' should be a ({dimX},{dimY},3) array") + +def check_is_coloralpha_image(values, dimX, dimY): + if len(values.shape) != 3 or values.shape[0] != dimX or values.shape[1] != dimY or values.shape[2] != 4: + raise ValueError(f"'values' should be a ({dimX},{dimY},4) array") + def process_color_args(structure, quantity, color_args): for arg,val in color_args.items(): diff --git a/src/polyscope/core.py b/src/polyscope/core.py index 9dbc6ab..b4972d9 100644 --- a/src/polyscope/core.py +++ b/src/polyscope/core.py @@ -555,3 +555,26 @@ def point_render_mode_to_str(val): raise ValueError("Bad point render mode specifier '{}', should be one of [{}]".format(val, ",".join(["'{}'".format(x) for x in d_point_render_mode.values()]))) + +# Image origin to/from string +d_image_origin = { + "lower_left" : psb.ImageOrigin.lower_left, + "upper_left" : psb.ImageOrigin.upper_left, + } + +def str_to_image_origin(s): + + if s not in d_image_origin: + raise ValueError("Bad image origin specifier '{}', should be one of [{}]".format(s, + ",".join(["'{}'".format(x) for x in d_image_origin.keys()]))) + + return d_image_origin[s] + +def image_origin_to_str(val): + + for k,v in d_image_origin.items(): + if v == val: + return k + + raise ValueError("Bad image origin specifier '{}', should be one of [{}]".format(val, + ",".join(["'{}'".format(x) for x in d_image_origin.values()]))) diff --git a/src/polyscope/structure.py b/src/polyscope/structure.py index a8d118f..d90eb16 100644 --- a/src/polyscope/structure.py +++ b/src/polyscope/structure.py @@ -1,5 +1,8 @@ import polyscope_bindings as psb +from polyscope.common import check_is_scalar_image, check_is_color_image, check_is_coloralpha_image, process_scalar_args, process_color_args +from polyscope.core import image_origin_to_str, str_to_image_origin, str_to_datatype + # Base class for common properties and methods on structures class Structure: @@ -71,3 +74,21 @@ def get_ignore_slice_plane(self, plane): return self.bound_instance.get_ignore_slice_plane(plane) else: return self.bound_instance.get_ignore_slice_plane(plane.get_name()) + + + ## Image Floating Quantities + + def add_scalar_image_quantity(self, name, dimX, dimY, values, image_origin="upper_left", datatype="standard", **scalar_args): + + dimX = int(dimX) + dimY = int(dimY) + check_is_scalar_image(values, dimX, dimY) + + values_flat = values.reshape(dimX*dimY) + + q = self.bound_instance.add_scalar_image_quantity(name, dimX, dimY, values_flat, + str_to_image_origin(image_origin), str_to_datatype(datatype)) + + process_scalar_args(self, q, scalar_args) + + diff --git a/test/polyscope_test.py b/test/polyscope_test.py index ac6648b..36a4d54 100644 --- a/test/polyscope_test.py +++ b/test/polyscope_test.py @@ -1603,6 +1603,20 @@ def test_camera_things(self): ps.show(3) ps.remove_all_structures() + + def test_floating_images(self): + + # technically these can be added to any structure, but we will test them here + + cam = ps.register_camera_view("cam1", self.generate_parameters()) + + dimX = 300 + dimY = 600 + + cam.add_scalar_image_quantity("scalar_img", dimX, dimY, np.zeros((dimX, dimY))) + cam.add_scalar_image_quantity("scalar_img2", dimX, dimY, np.zeros((dimX, dimY)), enabled=True, image_origin='lower_left', datatype='symmetric', vminmax=(-3.,.3), cmap='reds') + + ps.show(3) if __name__ == '__main__': From afe53d743e207118770ae3a5d4fb62ca7a0b48f2 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Fri, 7 Jul 2023 12:26:36 -0700 Subject: [PATCH 17/88] update dep --- deps/polyscope | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/polyscope b/deps/polyscope index e17a790..79fa00c 160000 --- a/deps/polyscope +++ b/deps/polyscope @@ -1 +1 @@ -Subproject commit e17a7901868f0b7ddf79ab036aab1499f5b13856 +Subproject commit 79fa00cc359049309e3b6ee00c01ff15c0fb30dd From 2b58b99524cf9649f4a5527cb403ad6646c094e4 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Fri, 7 Jul 2023 12:27:14 -0700 Subject: [PATCH 18/88] update gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 31f7492..ba129da 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ dist/ *.pyc __pycache__/ .cache +*.egg-info # Editor and OS things imgui.ini From 331baa51bd8f88777e62df3dd161734ad181eb4c Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Fri, 7 Jul 2023 12:27:48 -0700 Subject: [PATCH 19/88] view utils and floating helpers --- src/cpp/core.cpp | 7 ++++++- src/cpp/floating_quantities.cpp | 6 +++++- src/polyscope/common.py | 6 +++--- src/polyscope/core.py | 26 +++++++++++++++++++++++--- src/polyscope/structure.py | 32 +++++++++++++++++++++++++------- test/polyscope_test.py | 9 +++++++++ 6 files changed, 71 insertions(+), 15 deletions(-) diff --git a/src/cpp/core.cpp b/src/cpp/core.cpp index 280c473..93253c8 100644 --- a/src/cpp/core.cpp +++ b/src/cpp/core.cpp @@ -133,6 +133,11 @@ PYBIND11_MODULE(polyscope_bindings, m) { m.def("set_view_projection_mode", [](ps::ProjectionMode x) { ps::view::projectionMode = x; }); m.def("get_view_camera_parameters", &ps::view::getCameraParametersForCurrentView); m.def("set_view_camera_parameters", &ps::view::setViewToCamera); + m.def("set_window_size", &ps::view::setWindowSize); + m.def("get_window_size", &ps::view::getWindowSize); + m.def("get_buffer_size", &ps::view::getBufferSize); + m.def("set_window_resizable", &ps::view::setWindowResizable); + m.def("get_window_resizable", &ps::view::getWindowResizable); m.def("set_view_from_json", ps::view::setViewFromJson); m.def("get_view_as_json", ps::view::getViewAsJson); @@ -362,9 +367,9 @@ PYBIND11_MODULE(polyscope_bindings, m) { // === Bind structures defined in other files bind_floating_quantities(m); - bind_surface_mesh(m); bind_point_cloud(m); bind_curve_network(m); + bind_surface_mesh(m); bind_volume_mesh(m); bind_camera_view(m); bind_imgui(m); diff --git a/src/cpp/floating_quantities.cpp b/src/cpp/floating_quantities.cpp index 5d2f111..03b041d 100644 --- a/src/cpp/floating_quantities.cpp +++ b/src/cpp/floating_quantities.cpp @@ -23,7 +23,11 @@ using overload_cast_ = pybind11::detail::overload_cast_impl; void bind_floating_quantities(py::module& m) { // == Global floating quantity management - + + // global floating quantity structure + bindStructure(m, "FloatingQuantityStructure"); + m.def("get_global_floating_quantity_structure", &ps::getGlobalFloatingQuantityStructure, py::return_value_policy::reference); + m.def("remove_floating_quantity", &ps::removeFloatingQuantity); m.def("remove_all_floating_quantities", &ps::removeAllFloatingQuantities); diff --git a/src/polyscope/common.py b/src/polyscope/common.py index 6078c46..52c9632 100644 --- a/src/polyscope/common.py +++ b/src/polyscope/common.py @@ -5,15 +5,15 @@ def check_is_scalar_image(values, dimX, dimY): if len(values.shape) != 2 or values.shape[0] != dimX or values.shape[1] != dimY: - raise ValueError(f"'values' should be a ({dimX},{dimY}) array") + raise ValueError(f"'values' should be a ({dimX},{dimY}) array. Shape is {values.shape}.") def check_is_color_image(values, dimX, dimY): if len(values.shape) != 3 or values.shape[0] != dimX or values.shape[1] != dimY or values.shape[2] != 3: - raise ValueError(f"'values' should be a ({dimX},{dimY},3) array") + raise ValueError(f"'values' should be a ({dimX},{dimY},3) array. Shape is {values.shape}.") def check_is_coloralpha_image(values, dimX, dimY): if len(values.shape) != 3 or values.shape[0] != dimX or values.shape[1] != dimY or values.shape[2] != 4: - raise ValueError(f"'values' should be a ({dimX},{dimY},4) array") + raise ValueError(f"'values' should be a ({dimX},{dimY},4) array. Shape is {values.shape}.") def process_color_args(structure, quantity, color_args): diff --git a/src/polyscope/core.py b/src/polyscope/core.py index b4972d9..4a87d3d 100644 --- a/src/polyscope/core.py +++ b/src/polyscope/core.py @@ -135,8 +135,25 @@ def look_at_dir(camera_location, target, up_dir, fly_to=False): def set_view_projection_mode(s): psb.set_view_projection_mode(str_to_projection_mode(s)) -def set_view_from_json(s, fly_to=False): - psb.set_view_from_json(s, fly_to) +def set_window_size(width, height): + width = int(width) + height = int(height) + psb.set_window_size(width, height) + +def get_window_size(): + return psb.get_window_size() + +def get_buffer_size(): + return psb.get_buffer_size() + +def set_window_resizable(is_resizable): + psb.set_window_resizable(is_resizable) + +def get_window_resizable(): + return psb.get_window_resizable() + +def set_view_from_json(json_str, fly_to=False): + psb.set_view_from_json(json_str, fly_to) def get_view_as_json(): return psb.get_view_as_json() @@ -146,7 +163,10 @@ def get_view_camera_parameters(): def set_view_camera_parameters(params): if not isinstance(params, CameraParameters): raise ValueError("must pass CameraParameters") - return set_view_camera_parameters(params.instance) + set_view_camera_parameters(params.instance) + +def get_view_buffer_resolution(): + return CameraParameters(instance=psb.get_view_camera_parameters()) ### Messages diff --git a/src/polyscope/structure.py b/src/polyscope/structure.py index d90eb16..8c5d49a 100644 --- a/src/polyscope/structure.py +++ b/src/polyscope/structure.py @@ -79,16 +79,34 @@ def get_ignore_slice_plane(self, plane): ## Image Floating Quantities def add_scalar_image_quantity(self, name, dimX, dimY, values, image_origin="upper_left", datatype="standard", **scalar_args): + """ + Add a "floating" image quantity to the structure + """ - dimX = int(dimX) - dimY = int(dimY) - check_is_scalar_image(values, dimX, dimY) + # Call the general version (this abstraction allows us to handle the free-floating case via the same code) + return add_scalar_image_quantity(name, dimX, dimY, values, image_origin="upper_left", datatype="standard", struct_ref=self, **scalar_args) - values_flat = values.reshape(dimX*dimY) - q = self.bound_instance.add_scalar_image_quantity(name, dimX, dimY, values_flat, - str_to_image_origin(image_origin), str_to_datatype(datatype)) - process_scalar_args(self, q, scalar_args) +def _resolve_floating_struct_instance(struct_ref): + if struct_ref is None: + return psb.get_global_floating_quantity_structure() + else: + return struct_ref.bound_instance + +def add_scalar_image_quantity(name, dimX, dimY, values, image_origin, datatype, struct_ref=None, **scalar_args): + + struct_instance_ref = _resolve_floating_struct_instance(struct_ref) + + dimX = int(dimX) + dimY = int(dimY) + check_is_scalar_image(values, dimX, dimY) + + values_flat = values.reshape(dimX*dimY) + + q = struct_instance_ref.add_scalar_image_quantity(name, dimX, dimY, values_flat, + str_to_image_origin(image_origin), str_to_datatype(datatype)) + + process_scalar_args(struct_ref, q, scalar_args) diff --git a/test/polyscope_test.py b/test/polyscope_test.py index 36a4d54..f8a8739 100644 --- a/test/polyscope_test.py +++ b/test/polyscope_test.py @@ -100,6 +100,15 @@ def test_view_options(self): ps.set_view_projection_mode("orthographic") ps.set_view_projection_mode("perspective") + + ps.set_window_size(800, 600) + self.assertEqual(ps.get_window_size(), (800,600)) + + tup = ps.get_buffer_size() + w, h = int(tup[0]), int(tup[1]) + + ps.set_window_resizable(True) + self.assertEqual(ps.get_window_resizable(), True) ps.show(3) From 3c08678d20672ee7239a8551d535bbe246670382 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Tue, 11 Jul 2023 10:15:42 -0700 Subject: [PATCH 20/88] update dep --- deps/polyscope | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/polyscope b/deps/polyscope index 79fa00c..3d8b995 160000 --- a/deps/polyscope +++ b/deps/polyscope @@ -1 +1 @@ -Subproject commit 79fa00cc359049309e3b6ee00c01ff15c0fb30dd +Subproject commit 3d8b995fb1d2bde943a4cf68837f5c2bf1b2bbaa From 228515c3527336a5b3e1951dccbe0af95ad2fdb8 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Tue, 11 Jul 2023 10:17:58 -0700 Subject: [PATCH 21/88] cleanup args parsing --- src/polyscope/common.py | 158 +++++++++++++++++++-------------- src/polyscope/curve_network.py | 14 ++- src/polyscope/point_cloud.py | 14 ++- src/polyscope/surface_mesh.py | 27 +++++- src/polyscope/volume_mesh.py | 17 +++- 5 files changed, 158 insertions(+), 72 deletions(-) diff --git a/src/polyscope/common.py b/src/polyscope/common.py index 52c9632..d659e74 100644 --- a/src/polyscope/common.py +++ b/src/polyscope/common.py @@ -3,87 +3,111 @@ from polyscope.core import str_to_datatype, str_to_vectortype, glm3, str_to_point_render_mode, point_render_mode_to_str, str_to_param_viz_style -def check_is_scalar_image(values, dimX, dimY): - if len(values.shape) != 2 or values.shape[0] != dimX or values.shape[1] != dimY: - raise ValueError(f"'values' should be a ({dimX},{dimY}) array. Shape is {values.shape}.") +def check_is_scalar_image(values): + if len(values.shape) != 2: + raise ValueError(f"'values' should be a (height,width) array. Shape is {values.shape}.") -def check_is_color_image(values, dimX, dimY): - if len(values.shape) != 3 or values.shape[0] != dimX or values.shape[1] != dimY or values.shape[2] != 3: - raise ValueError(f"'values' should be a ({dimX},{dimY},3) array. Shape is {values.shape}.") +def check_is_color_image(values): + if len(values.shape) != 3 or values.shape[2] != 3: + raise ValueError(f"'values' should be a (height,width,3) array. Shape is {values.shape}.") -def check_is_coloralpha_image(values, dimX, dimY): - if len(values.shape) != 3 or values.shape[0] != dimX or values.shape[1] != dimY or values.shape[2] != 4: - raise ValueError(f"'values' should be a ({dimX},{dimY},4) array. Shape is {values.shape}.") +def check_is_color_alpha_image(values): + if len(values.shape) != 3 or values.shape[2] != 4: + raise ValueError(f"'values' should be a (height,width,4) array. Shape is {values.shape}.") -def process_color_args(structure, quantity, color_args): +def check_and_pop_arg(d, argname): + if argname in d: + return d.pop(argname) + return None - for arg,val in color_args.items(): +# Process args, removing them from the dict if they are present +def process_quantity_args(structure, quantity, quantity_args): - if arg == 'enabled' and val is not None: - quantity.set_enabled(val) - - else: - raise ValueError(f"Polyscope: Unrecognized color quantity keyword argument {arg}: {val}") + val = check_and_pop_arg(quantity_args, 'enabled') + if val is not None: + quantity.set_enabled(val) +# Process args, removing them from the dict if they are present +def process_color_args(structure, quantity, color_args): -def process_scalar_args(structure, quantity, scalar_args): + pass # none yet - for arg,val in scalar_args.items(): - if arg == 'enabled' and val is not None: - quantity.set_enabled(val) - - elif arg == 'vminmax' and val is not None: - quantity.set_map_range(val) +# Process args, removing them from the dict if they are present +def process_scalar_args(structure, quantity, scalar_args): + + val = check_and_pop_arg(scalar_args, 'vminmax') + if val is not None: + quantity.set_map_range(val) - elif arg == 'cmap' and val is not None: - quantity.set_color_map(val) + val = check_and_pop_arg(scalar_args, 'cmap') + if val is not None: + quantity.set_color_map(val) - else: - raise ValueError(f"Polyscope: Unrecognized scalar quantity keyword argument {arg}: {val}") +# Process args, removing them from the dict if they are present def process_vector_args(structure, quantity, vector_args): - - for arg,val in vector_args.items(): - - if arg == 'enabled' and val is not None: - quantity.set_enabled(val) - - elif arg == 'length' and val is not None: - quantity.set_length(val, True) - - elif arg == 'radius' and val is not None: - quantity.set_radius(val, True) - - elif arg == 'color' and val is not None: - quantity.set_color(glm3(val)) - - else: - raise ValueError(f"Polyscope: Unrecognized vector quantity keyword argument {arg}: {val}") - + + val = check_and_pop_arg(vector_args, 'length') + if val is not None: + quantity.set_length(val, True) + + val = check_and_pop_arg(vector_args, 'radius') + if val is not None: + quantity.set_radius(val, True) + + val = check_and_pop_arg(vector_args, 'color') + if val is not None: + quantity.set_color(glm3(val)) + + +# Process args, removing them from the dict if they are present def process_parameterization_args(structure, quantity, parameterization_args): - for arg,val in parameterization_args.items(): - - if arg == 'enabled' and val is not None: - quantity.set_enabled(val) - - elif arg == 'viz_style' and val is not None: - viz_style_enum = str_to_param_viz_style(val) - quantity.set_style(viz_style_enum) + val = check_and_pop_arg(parameterization_args, 'viz_style') + if val is not None: + viz_style_enum = str_to_param_viz_style(val) + quantity.set_style(viz_style_enum) + + val = check_and_pop_arg(parameterization_args, 'grid_colors') + if val is not None: + quantity.set_grid_colors((glm3(val[0]), glm3(val[1]))) + + val = check_and_pop_arg(parameterization_args, 'checker_colors') + if val is not None: + quantity.set_checker_colors((glm3(val[0]), glm3(val[1]))) + + val = check_and_pop_arg(parameterization_args, 'checker_size') + if val is not None: + quantity.set_checker_size(val) + + val = check_and_pop_arg(parameterization_args, 'cmap') + if val is not None: + quantity.set_color_map(val) - elif arg == 'grid_colors' and val is not None: - quantity.set_grid_colors((glm3(val[0]), glm3(val[1]))) - - elif arg == 'checker_colors' and val is not None: - quantity.set_checker_colors((glm3(val[0]), glm3(val[1]))) - - elif arg == 'checker_size' and val is not None: - quantity.set_checker_size(val) - - elif arg == 'cmap' and val is not None: - quantity.set_color_map(val) - - else: - raise ValueError(f"Polyscope: Unrecognized parameterization quantity keyword argument {arg}: {val}") + +# Process args, removing them from the dict if they are present +def process_image_args(structure, quantity, image_args): + + val = check_and_pop_arg(image_args, 'show_fullscreen') + if val is not None: + quantity.set_show_fullscreen(val) + + val = check_and_pop_arg(image_args, 'show_in_imgui_window') + if val is not None: + quantity.set_show_in_imgui_window(val) + + val = check_and_pop_arg(image_args, 'show_in_camera_billboard') + if val is not None: + quantity.set_show_in_camera_billboard(val) + + val = check_and_pop_arg(image_args, 'transparency') + if val is not None: + quantity.set_transparency(val) + + + +def check_all_args_processed(structure, quantity, args): + for arg,val in args.items(): + raise ValueError(f"Polyscope: Unrecognized quantity keyword argument {arg}: {val}") diff --git a/src/polyscope/curve_network.py b/src/polyscope/curve_network.py index fedce7a..d0e66d8 100644 --- a/src/polyscope/curve_network.py +++ b/src/polyscope/curve_network.py @@ -2,7 +2,7 @@ from polyscope.core import str_to_datatype, str_to_vectortype, glm3 from polyscope.structure import Structure -from polyscope.common import process_color_args, process_scalar_args, process_vector_args, process_parameterization_args +from polyscope.common import process_quantity_args, process_scalar_args, process_color_args, process_vector_args, check_all_args_processed class CurveNetwork(Structure): @@ -107,7 +107,11 @@ def add_scalar_quantity(self, name, values, defined_on='nodes', datatype="standa raise ValueError("bad `defined_on` value {}, should be one of ['nodes', 'edges']".format(defined_on)) + # process and act on additional arguments + # note: each step modifies the args dict and removes processed args + process_quantity_args(self, q, scalar_args) process_scalar_args(self, q, scalar_args) + check_all_args_processed(self, q, scalar_args) # Color @@ -125,7 +129,11 @@ def add_color_quantity(self, name, values, defined_on='nodes', **color_args): raise ValueError("bad `defined_on` value {}, should be one of ['nodes', 'edges']".format(defined_on)) + # process and act on additional arguments + # note: each step modifies the args dict and removes processed args + process_quantity_args(self, q, color_args) process_color_args(self, q, color_args) + check_all_args_processed(self, q, color_args) # Vector @@ -153,7 +161,11 @@ def add_vector_quantity(self, name, values, defined_on='nodes', vectortype="stan raise ValueError("bad `defined_on` value {}, should be one of ['nodes', 'edges']".format(defined_on)) + # process and act on additional arguments + # note: each step modifies the args dict and removes processed args + process_quantity_args(self, q, vector_args) process_vector_args(self, q, vector_args) + check_all_args_processed(self, q, vector_args) def register_curve_network(name, nodes, edges, enabled=None, radius=None, color=None, material=None, transparency=None): diff --git a/src/polyscope/point_cloud.py b/src/polyscope/point_cloud.py index d654926..623b73b 100644 --- a/src/polyscope/point_cloud.py +++ b/src/polyscope/point_cloud.py @@ -2,7 +2,7 @@ from polyscope.core import str_to_datatype, str_to_vectortype, glm3, str_to_point_render_mode, point_render_mode_to_str from polyscope.structure import Structure -from polyscope.common import process_color_args, process_scalar_args, process_vector_args, process_parameterization_args +from polyscope.common import process_quantity_args, process_scalar_args, process_color_args, process_vector_args, process_parameterization_args, check_all_args_processed class PointCloud(Structure): @@ -92,7 +92,11 @@ def add_scalar_quantity(self, name, values, datatype="standard", **scalar_args): q = self.bound_instance.add_scalar_quantity(name, values, str_to_datatype(datatype)) + # process and act on additional arguments + # note: each step modifies the args dict and removes processed args + process_quantity_args(self, q, scalar_args) process_scalar_args(self, q, scalar_args) + check_all_args_processed(self, q, scalar_args) # Color @@ -101,7 +105,11 @@ def add_color_quantity(self, name, values, **color_args): q = self.bound_instance.add_color_quantity(name, values) + # process and act on additional arguments + # note: each step modifies the args dict and removes processed args + process_quantity_args(self, q, color_args) process_color_args(self, q, color_args) + check_all_args_processed(self, q, color_args) # Vector @@ -113,7 +121,11 @@ def add_vector_quantity(self, name, values, vectortype="standard", **vector_args elif values.shape[1] == 3: q = self.bound_instance.add_vector_quantity(name, values, str_to_vectortype(vectortype)) + # process and act on additional arguments + # note: each step modifies the args dict and removes processed args + process_quantity_args(self, q, vector_args) process_vector_args(self, q, vector_args) + check_all_args_processed(self, q, vector_args) def register_point_cloud(name, points, enabled=None, radius=None, point_render_mode=None, color=None, material=None, transparency=None): diff --git a/src/polyscope/surface_mesh.py b/src/polyscope/surface_mesh.py index e20a318..191e783 100644 --- a/src/polyscope/surface_mesh.py +++ b/src/polyscope/surface_mesh.py @@ -5,7 +5,7 @@ str_to_param_viz_style, str_to_back_face_policy, back_face_policy_to_str,\ glm3 from polyscope.structure import Structure -from polyscope.common import process_color_args, process_scalar_args, process_vector_args, process_parameterization_args +from polyscope.common import process_quantity_args, process_scalar_args, process_color_args, process_vector_args, process_parameterization_args, check_all_args_processed class SurfaceMesh(Structure): @@ -175,7 +175,11 @@ def add_scalar_quantity(self, name, values, defined_on='vertices', datatype="sta raise ValueError("bad `defined_on` value {}, should be one of ['vertices', 'faces', 'edges', 'halfedges']".format(defined_on)) + # process and act on additional arguments + # note: each step modifies the args dict and removes processed args + process_quantity_args(self, q, scalar_args) process_scalar_args(self, q, scalar_args) + check_all_args_processed(self, q, scalar_args) # Color @@ -192,10 +196,15 @@ def add_color_quantity(self, name, values, defined_on='vertices', **color_args): raise ValueError("bad `defined_on` value {}, should be one of ['vertices', 'faces']".format(defined_on)) + # process and act on additional arguments + # note: each step modifies the args dict and removes processed args + process_quantity_args(self, q, color_args) process_color_args(self, q, color_args) + check_all_args_processed(self, q, color_args) # Distance + # [deprecated], this is just a special set of options for a scalar quantity now def add_distance_quantity(self, name, values, defined_on='vertices', enabled=None, signed=False, vminmax=None, stripe_size=None, stripe_size_relative=True, cmap=None): if len(values.shape) != 1: raise ValueError("'values' should be a length-N array") @@ -239,7 +248,11 @@ def add_parameterization_quantity(self, name, values, defined_on='vertices', coo else: raise ValueError("bad `defined_on` value {}, should be one of ['vertices', 'corners']".format(defined_on)) + # process and act on additional arguments + # note: each step modifies the args dict and removes processed args + process_quantity_args(self, q, parameterization_args) process_parameterization_args(self, q, parameterization_args) + check_all_args_processed(self, q, parameterization_args) # Vector @@ -267,7 +280,11 @@ def add_vector_quantity(self, name, values, defined_on='vertices', vectortype="s raise ValueError("bad `defined_on` value {}, should be one of ['vertices', 'faces']".format(defined_on)) + # process and act on additional arguments + # note: each step modifies the args dict and removes processed args + process_quantity_args(self, q, vector_args) process_vector_args(self, q, vector_args) + check_all_args_processed(self, q, vector_args) def add_tangent_vector_quantity(self, name, values, basisX, basisY, n_sym=1, defined_on='vertices', vectortype="standard", **vector_args): @@ -295,7 +312,11 @@ def add_tangent_vector_quantity(self, name, values, basisX, basisY, n_sym=1, def raise ValueError("bad `defined_on` value {}, should be one of ['vertices', 'faces']".format(defined_on)) + # process and act on additional arguments + # note: each step modifies the args dict and removes processed args + process_quantity_args(self, q, vector_args) process_vector_args(self, q, vector_args) + check_all_args_processed(self, q, vector_args) def add_one_form_vector_quantity(self, name, values, orientations, **vector_args): @@ -305,7 +326,11 @@ def add_one_form_vector_quantity(self, name, values, orientations, **vector_args q = self.bound_instance.add_one_form_tangent_vector_quantity(name, values, orientations) + # process and act on additional arguments + # note: each step modifies the args dict and removes processed args + process_quantity_args(self, q, vector_args) process_vector_args(self, q, vector_args) + check_all_args_processed(self, q, vector_args) diff --git a/src/polyscope/volume_mesh.py b/src/polyscope/volume_mesh.py index 23cf963..8bdc1a9 100644 --- a/src/polyscope/volume_mesh.py +++ b/src/polyscope/volume_mesh.py @@ -3,7 +3,7 @@ from polyscope.core import str_to_datatype, str_to_vectortype, str_to_param_coords_type, str_to_param_viz_style, glm3 from polyscope.structure import Structure -from polyscope.common import process_color_args, process_scalar_args, process_vector_args, process_parameterization_args +from polyscope.common import process_quantity_args, process_scalar_args, process_color_args, process_vector_args, check_all_args_processed class VolumeMesh(Structure): @@ -131,7 +131,11 @@ def add_scalar_quantity(self, name, values, defined_on='vertices', datatype="sta raise ValueError("bad `defined_on` value {}, should be one of ['vertices', 'cells']".format(defined_on)) + # process and act on additional arguments + # note: each step modifies the args dict and removes processed args + process_quantity_args(self, q, scalar_args) process_scalar_args(self, q, scalar_args) + check_all_args_processed(self, q, scalar_args) @@ -149,7 +153,11 @@ def add_color_quantity(self, name, values, defined_on='vertices', **color_args): raise ValueError("bad `defined_on` value {}, should be one of ['vertices', 'cells']".format(defined_on)) + # process and act on additional arguments + # note: each step modifies the args dict and removes processed args + process_quantity_args(self, q, color_args) process_color_args(self, q, color_args) + check_all_args_processed(self, q, color_args) # Vector @@ -167,13 +175,18 @@ def add_vector_quantity(self, name, values, defined_on='vertices', vectortype="s raise ValueError("bad `defined_on` value {}, should be one of ['vertices', 'cells']".format(defined_on)) + # process and act on additional arguments + # note: each step modifies the args dict and removes processed args + process_quantity_args(self, q, vector_args) process_vector_args(self, q, vector_args) + check_all_args_processed(self, q, vector_args) def register_volume_mesh(name, vertices, tets=None, hexes=None, mixed_cells=None, enabled=None, color=None, interior_color=None, edge_color=None, edge_width=None, material=None, transparency=None): - """Register a new surface mesh""" + """Register a new volume mesh""" + if not psb.isInitialized(): raise RuntimeError("Polyscope has not been initialized") From 4ecf17ab488670cf4777449d60dde03fb75b8ab6 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Tue, 11 Jul 2023 10:18:34 -0700 Subject: [PATCH 22/88] more plumbing to support camera views --- src/cpp/camera_view.cpp | 1 + src/cpp/core.cpp | 30 ++++++++----- src/cpp/floating_quantities.cpp | 18 +++++--- src/cpp/utils.h | 19 ++++++++ src/polyscope/__init__.py | 1 + src/polyscope/camera_view.py | 9 ++-- src/polyscope/core.py | 29 +++++++----- src/polyscope/structure.py | 80 +++++++++++++++++++++++++++++---- test/polyscope_test.py | 70 +++++++++++++++++++++++++++-- 9 files changed, 211 insertions(+), 46 deletions(-) diff --git a/src/cpp/camera_view.cpp b/src/cpp/camera_view.cpp index 2da808b..f64fb24 100644 --- a/src/cpp/camera_view.cpp +++ b/src/cpp/camera_view.cpp @@ -22,6 +22,7 @@ void bind_camera_view(py::module& m) { bindStructure(m, "CameraView") // basics + .def("get_camera_parameters", &ps::CameraView::getCameraParameters, "Get camera parameters") .def("update_camera_parameters", &ps::CameraView::updateCameraParameters, "Update camera parameters") // options diff --git a/src/cpp/core.cpp b/src/cpp/core.cpp index 93253c8..2fba0af 100644 --- a/src/cpp/core.cpp +++ b/src/cpp/core.cpp @@ -241,24 +241,29 @@ PYBIND11_MODULE(polyscope_bindings, m) { ; py::class_(m, "CameraExtrinsics") .def(py::init<>()) - .def_static("from_vectors", &ps::CameraExtrinsics::fromVectors) - .def_static("from_matrix", &ps::CameraExtrinsics::fromMatrix) + .def_static("from_vectors", &ps::CameraExtrinsics::fromVectors) + .def_static("from_matrix", [](Eigen::Matrix4f mat) { return ps::CameraExtrinsics::fromMatrix(eigen2glm(mat)); }) ; py::class_(m, "CameraParameters") .def(py::init()) - .def("get_T", &ps::CameraParameters::getT) - .def("get_R", &ps::CameraParameters::getR) - .def("get_view_mat", &ps::CameraParameters::getViewMat) - .def("get_E", &ps::CameraParameters::getE) - .def("get_position", &ps::CameraParameters::getPosition) - .def("get_look_dir", &ps::CameraParameters::getLookDir) - .def("get_up_dir", &ps::CameraParameters::getUpDir) - .def("get_right_dir", &ps::CameraParameters::getRightDir) - .def("get_camera_frame", &ps::CameraParameters::getCameraFrame) + .def("get_intrinsics", [](ps::CameraParameters& c) { return c.intrinsics; }) + .def("get_extrinsics", [](ps::CameraParameters& c) { return c.extrinsics; }) + .def("get_T", [](ps::CameraParameters& c) { return glm2eigen(c.getT()); }) + .def("get_R", [](ps::CameraParameters& c) { return glm2eigen(c.getR()); }) + .def("get_view_mat", [](ps::CameraParameters& c) { return glm2eigen(c.getViewMat()); }) + .def("get_E", [](ps::CameraParameters& c) { return glm2eigen(c.getE()); }) + .def("get_position", [](ps::CameraParameters& c) { return glm2eigen(c.getPosition()); }) + .def("get_look_dir", [](ps::CameraParameters& c) { return glm2eigen(c.getLookDir()); }) + .def("get_up_dir", [](ps::CameraParameters& c) { return glm2eigen(c.getUpDir()); }) + .def("get_right_dir", [](ps::CameraParameters& c) { return glm2eigen(c.getRightDir()); }) + .def("get_camera_frame", [](ps::CameraParameters& c) { + return std::make_tuple(glm2eigen(c.getLookDir()), glm2eigen(c.getUpDir()), glm2eigen(c.getRightDir())); + }) .def("get_fov_vertical_deg", &ps::CameraParameters::getFoVVerticalDegrees) .def("get_aspect", &ps::CameraParameters::getAspectRatioWidthOverHeight) ; - + // TODO test + // === Enums py::enum_(m, "NavigateStyle") @@ -365,6 +370,7 @@ PYBIND11_MODULE(polyscope_bindings, m) { return std::tuple(x[0], x[1], x[2], x[3]); }); + // === Bind structures defined in other files bind_floating_quantities(m); bind_point_cloud(m); diff --git a/src/cpp/floating_quantities.cpp b/src/cpp/floating_quantities.cpp index 03b041d..fd3826c 100644 --- a/src/cpp/floating_quantities.cpp +++ b/src/cpp/floating_quantities.cpp @@ -33,8 +33,11 @@ void bind_floating_quantities(py::module& m) { // == Image floating quantities - bindScalarQuantity(m, "ScalarImageQuantity"); - bindColorQuantity(m, "ColorImageQuantity"); + auto qScalarImage = bindScalarQuantity(m, "ScalarImageQuantity"); + addImageQuantityBindings(qScalarImage); + + auto qColorImage = bindColorQuantity(m, "ColorImageQuantity"); + addImageQuantityBindings(qColorImage); m.def("add_scalar_image_quantity", &ps::addScalarImageQuantity); m.def("add_color_image_quantity", &ps::addColorImageQuantity); @@ -42,9 +45,14 @@ void bind_floating_quantities(py::module& m) { // == Render image floating quantities - bindQuantity(m, "DepthRenderImageQuantity"); - bindScalarQuantity(m, "ScalarRenderImageQuantity"); - bindColorQuantity(m, "ColorRenderImageQuantity"); + auto qDepthRenderImage = bindQuantity(m, "DepthRenderImageQuantity"); + // addImageQuantityBindings(qDepthRenderImage); // TODO need to make render images inherit from image quantity base + + auto qScalarRenderImage = bindScalarQuantity(m, "ScalarRenderImageQuantity"); + // addImageQuantityBindings(qScalarRenderImage); + + auto qColorRenderImage = bindColorQuantity(m, "ColorRenderImageQuantity"); + // addImageQuantityBindings(qColorRenderImage); m.def("add_depth_render_image_quantity", &ps::addDepthRenderImageQuantity); m.def("add_color_render_image_quantity", &ps::addColorRenderImageQuantity); diff --git a/src/cpp/utils.h b/src/cpp/utils.h index a70468d..43f65a7 100644 --- a/src/cpp/utils.h +++ b/src/cpp/utils.h @@ -147,3 +147,22 @@ py::class_ bindVectorQuantity(py::module& m, std::string name) { .def("set_radius", &VectorQ::setVectorRadius, "Set radius") .def("set_color", &VectorQ::setVectorColor, "Set color"); } + +// Add common image options +// Note: unlike the others above, this adds methods to an existing quantity rather than binding a new one. +template +void addImageQuantityBindings(py::class_& imageQ) { + + imageQ.def("set_show_fullscreen", &ImageQ::setShowFullscreen); + imageQ.def("get_show_fullscreen", &ImageQ::getShowFullscreen); + + imageQ.def("set_show_in_imgui_window", &ImageQ::setShowInImGuiWindow); + imageQ.def("get_show_in_imgui_window", &ImageQ::getShowInImGuiWindow); + + imageQ.def("set_show_in_camera_billboard", &ImageQ::setShowInCameraBillboard); + imageQ.def("get_show_in_camera_billboard", &ImageQ::getShowInCameraBillboard); + + imageQ.def("set_transparency", &ImageQ::setTransparency); + imageQ.def("get_transparency", &ImageQ::getTransparency); + +} diff --git a/src/polyscope/__init__.py b/src/polyscope/__init__.py index 0df5541..b09fecf 100644 --- a/src/polyscope/__init__.py +++ b/src/polyscope/__init__.py @@ -1,4 +1,5 @@ from polyscope.core import * +from polyscope.structure import * from polyscope.surface_mesh import * from polyscope.point_cloud import * from polyscope.curve_network import * diff --git a/src/polyscope/camera_view.py b/src/polyscope/camera_view.py index 159da7c..aeb26d4 100644 --- a/src/polyscope/camera_view.py +++ b/src/polyscope/camera_view.py @@ -1,10 +1,7 @@ import polyscope_bindings as psb -from polyscope.core import glm3, radians, degrees +from polyscope.core import glm3, CameraParameters from polyscope.structure import Structure -from polyscope.common import process_color_args, process_scalar_args, process_vector_args - -from math import tan, atan import numpy as np @@ -36,8 +33,8 @@ def update_camera_parameters(self, camera_parameters): def set_view_to_this_camera(self, with_flight=False): self.bound_instance.set_view_to_this_camera(with_flight) - def get_parameters(self): - return CameraParameters(instance=self.bound_instance.get_parameters()) + def get_camera_parameters(self): + return CameraParameters(instance=self.bound_instance.get_camera_parameters()) ## Options diff --git a/src/polyscope/core.py b/src/polyscope/core.py index 4a87d3d..5344814 100644 --- a/src/polyscope/core.py +++ b/src/polyscope/core.py @@ -275,9 +275,11 @@ def remove_last_scene_slice_plane(): class CameraIntrinsics: - def __init__(self, fov_vertical_deg=None, fov_horizontal_deg=None, aspect=None): + def __init__(self, fov_vertical_deg=None, fov_horizontal_deg=None, aspect=None, instance=None): - if fov_vertical_deg is not None and aspect is not None: + if instance is not None: + self.instance = instance + elif fov_vertical_deg is not None and aspect is not None: self.instance = psb.CameraIntrinsics.from_FoV_deg_vertical_and_aspect(float(fov_vertical_deg), float(aspect)) elif fov_horizontal_deg is not None and aspect is not None: self.instance = psb.CameraIntrinsics.from_FoV_deg_horizontal_and_aspect(float(fov_horizontal_deg), float(aspect)) @@ -288,24 +290,27 @@ def __init__(self, fov_vertical_deg=None, fov_horizontal_deg=None, aspect=None): class CameraExtrinsics: - def __init__(self, root=None, look_dir=None, up_dir=None, mat=None): + def __init__(self, root=None, look_dir=None, up_dir=None, mat=None, instance=None): + + if instance is not None: + self.instance = instance - if mat is not None: - mat = np.array(mat) + elif mat is not None: + mat = np.asarray(mat) if mat.shape != (4,4): raise ValueError("mat should be a 4x4 numpy matrix") - self.instance = psb.CameraExtrinsics.fromMatrix(mat) + self.instance = psb.CameraExtrinsics.from_matrix(mat) elif (root is not None) and (look_dir is not None) and (up_dir is not None): - root = glm3(root) - look_dir = glm3(look_dir) - up_dir = glm3(up_dir) + root = np.asarray(root) + look_dir = np.asarray(look_dir) + up_dir = np.asarray(up_dir) self.instance = psb.CameraExtrinsics.from_vectors(root, look_dir, up_dir) else: raise ValueError("bad arguments, must pass non-None (root,look_dir,up_dir) or non-None mat") - +# TODO still needs tests class CameraParameters: def __init__(self, intrinsics=None, extrinsics=None, instance=None): @@ -315,9 +320,11 @@ def __init__(self, intrinsics=None, extrinsics=None, instance=None): self.instance = psb.CameraParameters(intrinsics.instance, extrinsics.instance) # getters + def get_intrinsics(self): return CameraIntrinsics(instance=self.instance.get_intrinsics()) + def get_extrinsics(self): return CameraExtrinsics(instance=self.instance.get_extrinsics()) def get_T(self): return self.instance.get_T() def get_R(self): return self.instance.get_R() - def get_view_mat(self): return self.instance.get_view_mat() + def get_view_mat(self): return self.instance.get_view_mat() # same as get_E() def get_E(self): return self.instance.get_E() def get_position(self): return self.instance.get_position() def get_look_dir(self): return self.instance.get_look_dir() diff --git a/src/polyscope/structure.py b/src/polyscope/structure.py index 8c5d49a..b324a73 100644 --- a/src/polyscope/structure.py +++ b/src/polyscope/structure.py @@ -1,6 +1,6 @@ import polyscope_bindings as psb -from polyscope.common import check_is_scalar_image, check_is_color_image, check_is_coloralpha_image, process_scalar_args, process_color_args +from polyscope.common import check_is_scalar_image, check_is_color_image, check_is_color_alpha_image, process_scalar_args, process_color_args, process_image_args, process_quantity_args, check_all_args_processed from polyscope.core import image_origin_to_str, str_to_image_origin, str_to_datatype # Base class for common properties and methods on structures @@ -78,13 +78,29 @@ def get_ignore_slice_plane(self, plane): ## Image Floating Quantities - def add_scalar_image_quantity(self, name, dimX, dimY, values, image_origin="upper_left", datatype="standard", **scalar_args): + def add_scalar_image_quantity(self, name, values, image_origin="upper_left", datatype="standard", **option_args): """ Add a "floating" image quantity to the structure """ # Call the general version (this abstraction allows us to handle the free-floating case via the same code) - return add_scalar_image_quantity(name, dimX, dimY, values, image_origin="upper_left", datatype="standard", struct_ref=self, **scalar_args) + return add_scalar_image_quantity(name, values, image_origin="upper_left", datatype="standard", struct_ref=self, **option_args) + + def add_color_image_quantity(self, name, values, image_origin="upper_left", **option_args): + """ + Add a "floating" image quantity to the structure + """ + + # Call the general version (this abstraction allows us to handle the free-floating case via the same code) + return add_color_image_quantity(name, values, image_origin="upper_left", struct_ref=self, **option_args) + + def add_color_alpha_image_quantity(self, name, values, image_origin="upper_left", **option_args): + """ + Add a "floating" image quantity to the structure + """ + + # Call the general version (this abstraction allows us to handle the free-floating case via the same code) + return add_color_alpha_image_quantity(name, values, image_origin="upper_left", struct_ref=self, **option_args) @@ -95,18 +111,64 @@ def _resolve_floating_struct_instance(struct_ref): return struct_ref.bound_instance -def add_scalar_image_quantity(name, dimX, dimY, values, image_origin, datatype, struct_ref=None, **scalar_args): +def add_scalar_image_quantity(name, values, image_origin="upper_left", datatype="standard", struct_ref=None, **option_args): struct_instance_ref = _resolve_floating_struct_instance(struct_ref) - dimX = int(dimX) - dimY = int(dimY) - check_is_scalar_image(values, dimX, dimY) + check_is_scalar_image(values) + dimY = values.shape[0] + dimX = values.shape[1] - values_flat = values.reshape(dimX*dimY) + values_flat = values.flatten() q = struct_instance_ref.add_scalar_image_quantity(name, dimX, dimY, values_flat, str_to_image_origin(image_origin), str_to_datatype(datatype)) - process_scalar_args(struct_ref, q, scalar_args) + # process and act on additional arguments + # note: each step modifies the option_args dict and removes processed args + process_quantity_args(struct_ref, q, option_args) + process_image_args(struct_ref, q, option_args) + process_scalar_args(struct_ref, q, option_args) + check_all_args_processed(struct_ref, q, option_args) + +def add_color_image_quantity(name, values, image_origin="upper_left", struct_ref=None, **option_args): + + struct_instance_ref = _resolve_floating_struct_instance(struct_ref) + + check_is_color_image(values) + dimY = values.shape[0] + dimX = values.shape[1] + + values_flat = values.reshape(-1,3) + + q = struct_instance_ref.add_color_image_quantity(name, dimX, dimY, values_flat, + str_to_image_origin(image_origin)) + + # process and act on additional arguments + # note: each step modifies the option_args dict and removes processed args + process_quantity_args(struct_ref, q, option_args) + process_image_args(struct_ref, q, option_args) + process_color_args(struct_ref, q, option_args) + check_all_args_processed(struct_ref, q, option_args) + + +def add_color_alpha_image_quantity(name, values, image_origin="upper_left", struct_ref=None, **option_args): + + struct_instance_ref = _resolve_floating_struct_instance(struct_ref) + + check_is_color_alpha_image(values) + dimY = values.shape[0] + dimX = values.shape[1] + + values_flat = values.reshape(-1,4) + + q = struct_instance_ref.add_color_alpha_image_quantity(name, dimX, dimY, values_flat, + str_to_image_origin(image_origin)) + + # process and act on additional arguments + # note: each step modifies the option_args dict and removes processed args + process_quantity_args(struct_ref, q, option_args) + process_image_args(struct_ref, q, option_args) + process_color_args(struct_ref, q, option_args) + check_all_args_processed(struct_ref, q, option_args) diff --git a/test/polyscope_test.py b/test/polyscope_test.py index f8a8739..0561ef1 100644 --- a/test/polyscope_test.py +++ b/test/polyscope_test.py @@ -22,6 +22,10 @@ # Path to test assets assets_prefix = path.join(path.dirname(__file__), "assets/") +def assertArrayWithShape(self, arr, shape): + self.assertTrue(isinstance(arr, np.ndarray)) + self.assertEqual(tuple(arr.shape), tuple(shape)) + class TestCore(unittest.TestCase): @@ -1612,8 +1616,65 @@ def test_camera_things(self): ps.show(3) ps.remove_all_structures() + + def test_camera_parameters(self): + + cam = ps.register_camera_view("cam1", self.generate_parameters()) + params = cam.get_camera_parameters() + + self.assertTrue(isinstance(params.get_intrinsics(), ps.CameraIntrinsics)) + self.assertTrue(isinstance(params.get_extrinsics(), ps.CameraExtrinsics)) + + assertArrayWithShape(self, params.get_R(), [3,3]) + assertArrayWithShape(self, params.get_T(), [3]) + assertArrayWithShape(self, params.get_view_mat(), [4,4]) + assertArrayWithShape(self, params.get_E(), [4,4]) + assertArrayWithShape(self, params.get_position(), [3]) + assertArrayWithShape(self, params.get_look_dir(), [3]) + assertArrayWithShape(self, params.get_up_dir(), [3]) + assertArrayWithShape(self, params.get_right_dir(), [3]) + assertArrayWithShape(self, params.get_camera_frame()[0], [3]) + assertArrayWithShape(self, params.get_camera_frame()[1], [3]) + assertArrayWithShape(self, params.get_camera_frame()[2], [3]) + + self.assertTrue(isinstance(params.get_fov_vertical_deg(), float)) + self.assertTrue(isinstance(params.get_aspect(), float)) + + def test_floating_scalar_images(self): + + # technically these can be added to any structure, but we will test them here + + cam = ps.register_camera_view("cam1", self.generate_parameters()) + + dimX = 300 + dimY = 600 + + cam.add_scalar_image_quantity("scalar_img", np.zeros((dimX, dimY))) + cam.add_scalar_image_quantity("scalar_img2", np.zeros((dimX, dimY)), enabled=True, image_origin='lower_left', datatype='symmetric', vminmax=(-3.,.3), cmap='reds', show_in_camera_billboard=True) + cam.add_scalar_image_quantity("scalar_img3", np.zeros((dimX, dimY)), enabled=True, show_in_imgui_window=True, show_in_camera_billboard=False) + cam.add_scalar_image_quantity("scalar_img4", np.zeros((dimX, dimY)), enabled=True, show_fullscreen=True, show_in_camera_billboard=False, transparency=0.5) + + ps.show(3) + ps.remove_all_structures() + + def test_floating_color_images(self): + + # technically these can be added to any structure, but we will test them here + + cam = ps.register_camera_view("cam1", self.generate_parameters()) + + dimX = 300 + dimY = 600 + + cam.add_color_image_quantity("color_img", np.zeros((dimX, dimY, 3))) + cam.add_color_image_quantity("color_img2", np.zeros((dimX, dimY, 3)), enabled=True, image_origin='lower_left', show_in_camera_billboard=True) + cam.add_color_image_quantity("color_img3", np.zeros((dimX, dimY, 3)), enabled=True, show_in_imgui_window=True, show_in_camera_billboard=False) + cam.add_color_image_quantity("color_img4", np.zeros((dimX, dimY, 3)), enabled=True, show_fullscreen=True, show_in_camera_billboard=False, transparency=0.5) + + ps.show(3) + ps.remove_all_structures() - def test_floating_images(self): + def test_floating_color_alpha_images(self): # technically these can be added to any structure, but we will test them here @@ -1622,10 +1683,13 @@ def test_floating_images(self): dimX = 300 dimY = 600 - cam.add_scalar_image_quantity("scalar_img", dimX, dimY, np.zeros((dimX, dimY))) - cam.add_scalar_image_quantity("scalar_img2", dimX, dimY, np.zeros((dimX, dimY)), enabled=True, image_origin='lower_left', datatype='symmetric', vminmax=(-3.,.3), cmap='reds') + cam.add_color_alpha_image_quantity("color_alpha_img", np.zeros((dimX, dimY, 4))) + cam.add_color_alpha_image_quantity("color_alpha_img2", np.zeros((dimX, dimY, 4)), enabled=True, image_origin='lower_left', show_in_camera_billboard=True) + cam.add_color_alpha_image_quantity("color_alpha_img3", np.zeros((dimX, dimY, 4)), enabled=True, show_in_imgui_window=True, show_in_camera_billboard=False) + cam.add_color_alpha_image_quantity("color_alpha_img4", np.zeros((dimX, dimY, 4)), enabled=True, show_fullscreen=True, show_in_camera_billboard=False) ps.show(3) + ps.remove_all_structures() if __name__ == '__main__': From 67f5fd88218260aacbafdbad62c0f341f5c568a6 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Fri, 14 Jul 2023 18:01:37 -0700 Subject: [PATCH 23/88] update deps --- deps/polyscope | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/polyscope b/deps/polyscope index 3d8b995..9296c27 160000 --- a/deps/polyscope +++ b/deps/polyscope @@ -1 +1 @@ -Subproject commit 3d8b995fb1d2bde943a4cf68837f5c2bf1b2bbaa +Subproject commit 9296c27bc31a94176107ed6aa786c7c691ed1d50 From c17712df4f53230afeb8f4d7c9d61047480e662d Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Fri, 14 Jul 2023 18:02:42 -0700 Subject: [PATCH 24/88] bindings for render quantities --- src/cpp/floating_quantities.cpp | 14 ++- src/polyscope/__init__.py | 1 + src/polyscope/common.py | 31 +++++- src/polyscope/floating_quantities.py | 157 +++++++++++++++++++++++++++ src/polyscope/structure.py | 91 ++++------------ test/polyscope_test.py | 76 +++++++++++++ 6 files changed, 295 insertions(+), 75 deletions(-) create mode 100644 src/polyscope/floating_quantities.py diff --git a/src/cpp/floating_quantities.cpp b/src/cpp/floating_quantities.cpp index fd3826c..d845649 100644 --- a/src/cpp/floating_quantities.cpp +++ b/src/cpp/floating_quantities.cpp @@ -46,13 +46,17 @@ void bind_floating_quantities(py::module& m) { // == Render image floating quantities auto qDepthRenderImage = bindQuantity(m, "DepthRenderImageQuantity"); - // addImageQuantityBindings(qDepthRenderImage); // TODO need to make render images inherit from image quantity base - - auto qScalarRenderImage = bindScalarQuantity(m, "ScalarRenderImageQuantity"); - // addImageQuantityBindings(qScalarRenderImage); + qDepthRenderImage.def("set_material", &ps::DepthRenderImageQuantity::setMaterial, "Set material"); + qDepthRenderImage.def("set_color", &ps::DepthRenderImageQuantity::setColor, "Set color"); + qDepthRenderImage.def("set_transparency", &ps::DepthRenderImageQuantity::setTransparency, "Set transparency"); auto qColorRenderImage = bindColorQuantity(m, "ColorRenderImageQuantity"); - // addImageQuantityBindings(qColorRenderImage); + qColorRenderImage.def("set_material", &ps::ColorRenderImageQuantity::setMaterial, "Set material"); + qColorRenderImage.def("set_transparency", &ps::ColorRenderImageQuantity::setTransparency, "Set transparency"); + + auto qScalarRenderImage = bindScalarQuantity(m, "ScalarRenderImageQuantity"); + qScalarRenderImage.def("set_material", &ps::ScalarRenderImageQuantity::setMaterial, "Set material"); + qScalarRenderImage.def("set_transparency", &ps::ScalarRenderImageQuantity::setTransparency, "Set transparency"); m.def("add_depth_render_image_quantity", &ps::addDepthRenderImageQuantity); m.def("add_color_render_image_quantity", &ps::addColorRenderImageQuantity); diff --git a/src/polyscope/__init__.py b/src/polyscope/__init__.py index b09fecf..df6f7b9 100644 --- a/src/polyscope/__init__.py +++ b/src/polyscope/__init__.py @@ -1,5 +1,6 @@ from polyscope.core import * from polyscope.structure import * +from polyscope.floating_quantities import * from polyscope.surface_mesh import * from polyscope.point_cloud import * from polyscope.curve_network import * diff --git a/src/polyscope/common.py b/src/polyscope/common.py index d659e74..e7d80aa 100644 --- a/src/polyscope/common.py +++ b/src/polyscope/common.py @@ -7,14 +7,31 @@ def check_is_scalar_image(values): if len(values.shape) != 2: raise ValueError(f"'values' should be a (height,width) array. Shape is {values.shape}.") -def check_is_color_image(values): +def check_is_image3(values): if len(values.shape) != 3 or values.shape[2] != 3: raise ValueError(f"'values' should be a (height,width,3) array. Shape is {values.shape}.") -def check_is_color_alpha_image(values): +def check_is_image4(values): if len(values.shape) != 3 or values.shape[2] != 4: raise ValueError(f"'values' should be a (height,width,4) array. Shape is {values.shape}.") +def check_image_dims_compatible(images): + + dimX = None + dimY = None + + for img in images: + + if dimX is None: + dimX = img.shape[0] + dimY = img.shape[1] + else: + new_dimX = img.shape[0] + new_dimY = img.shape[1] + + if (dimX, dimY) != (new_dimX, new_dimY): + raise ValueError(f"image inputs must have same resolution. One is {(dimX,dimY)} but another is {(new_dimX, new_dimY)}.") + def check_and_pop_arg(d, argname): if argname in d: return d.pop(argname) @@ -105,6 +122,16 @@ def process_image_args(structure, quantity, image_args): if val is not None: quantity.set_transparency(val) +# Process args, removing them from the dict if they are present +def process_render_image_args(structure, quantity, image_args): + + val = check_and_pop_arg(image_args, 'transparency') + if val is not None: + quantity.set_transparency(val) + + val = check_and_pop_arg(image_args, 'material') + if val is not None: + quantity.set_material(val) def check_all_args_processed(structure, quantity, args): diff --git a/src/polyscope/floating_quantities.py b/src/polyscope/floating_quantities.py new file mode 100644 index 0000000..2717cc0 --- /dev/null +++ b/src/polyscope/floating_quantities.py @@ -0,0 +1,157 @@ +import polyscope_bindings as psb + +from polyscope.common import check_is_scalar_image, check_is_image3, check_is_image4, check_image_dims_compatible, process_scalar_args, process_color_args, process_image_args, process_render_image_args, process_quantity_args, check_all_args_processed + +from polyscope.core import str_to_image_origin, str_to_datatype, glm3 + + +def _resolve_floating_struct_instance(struct_ref): + if struct_ref is None: + return psb.get_global_floating_quantity_structure() + else: + return struct_ref.bound_instance + + +def add_scalar_image_quantity(name, values, image_origin="upper_left", datatype="standard", struct_ref=None, **option_args): + + struct_instance_ref = _resolve_floating_struct_instance(struct_ref) + + check_is_scalar_image(values) + dimY = values.shape[0] + dimX = values.shape[1] + + values_flat = values.flatten() + + q = struct_instance_ref.add_scalar_image_quantity(name, dimX, dimY, values_flat, + str_to_image_origin(image_origin), str_to_datatype(datatype)) + + # process and act on additional arguments + # note: each step modifies the option_args dict and removes processed args + process_quantity_args(struct_ref, q, option_args) + process_image_args(struct_ref, q, option_args) + process_scalar_args(struct_ref, q, option_args) + check_all_args_processed(struct_ref, q, option_args) + +def add_color_image_quantity(name, values, image_origin="upper_left", struct_ref=None, **option_args): + + struct_instance_ref = _resolve_floating_struct_instance(struct_ref) + + check_is_image3(values) + dimY = values.shape[0] + dimX = values.shape[1] + + values_flat = values.reshape(-1,3) + + q = struct_instance_ref.add_color_image_quantity(name, dimX, dimY, values_flat, + str_to_image_origin(image_origin)) + + # process and act on additional arguments + # note: each step modifies the option_args dict and removes processed args + process_quantity_args(struct_ref, q, option_args) + process_image_args(struct_ref, q, option_args) + process_color_args(struct_ref, q, option_args) + check_all_args_processed(struct_ref, q, option_args) + + +def add_color_alpha_image_quantity(name, values, image_origin="upper_left", struct_ref=None, **option_args): + + struct_instance_ref = _resolve_floating_struct_instance(struct_ref) + + check_is_image4(values) + dimY = values.shape[0] + dimX = values.shape[1] + + values_flat = values.reshape(-1,4) + + q = struct_instance_ref.add_color_alpha_image_quantity(name, dimX, dimY, values_flat, + str_to_image_origin(image_origin)) + + # process and act on additional arguments + # note: each step modifies the option_args dict and removes processed args + process_quantity_args(struct_ref, q, option_args) + process_image_args(struct_ref, q, option_args) + process_color_args(struct_ref, q, option_args) + check_all_args_processed(struct_ref, q, option_args) + + +def add_depth_render_image_quantity(name, depth_values, normal_values, image_origin="upper_left", color=None, struct_ref=None, **option_args): + + struct_instance_ref = _resolve_floating_struct_instance(struct_ref) + + check_is_scalar_image(depth_values) + check_is_image3(normal_values) + check_image_dims_compatible([depth_values, normal_values]) + dimY = depth_values.shape[0] + dimX = depth_values.shape[1] + + depth_values_flat = depth_values.flatten() + normal_values_flat = normal_values.reshape(-1,3) + + q = struct_instance_ref.add_depth_render_image_quantity(name, dimX, dimY, + depth_values_flat, normal_values_flat, + str_to_image_origin(image_origin)) + + if color is not None: + q.set_color(glm3(color)) + + # process and act on additional arguments + # note: each step modifies the option_args dict and removes processed args + process_quantity_args(struct_ref, q, option_args) + process_render_image_args(struct_ref, q, option_args) + check_all_args_processed(struct_ref, q, option_args) + + +def add_color_render_image_quantity(name, depth_values, normal_values, color_values, image_origin="upper_left", struct_ref=None, **option_args): + + struct_instance_ref = _resolve_floating_struct_instance(struct_ref) + + check_is_scalar_image(depth_values) + check_is_image3(normal_values) + check_is_image3(color_values) + check_image_dims_compatible([depth_values, normal_values]) + dimY = depth_values.shape[0] + dimX = depth_values.shape[1] + + depth_values_flat = depth_values.flatten() + normal_values_flat = normal_values.reshape(-1,3) + color_values_flat = color_values.reshape(-1,3) + + q = struct_instance_ref.add_color_render_image_quantity(name, dimX, dimY, + depth_values_flat, normal_values_flat, color_values_flat, + str_to_image_origin(image_origin)) + + + # process and act on additional arguments + # note: each step modifies the option_args dict and removes processed args + process_quantity_args(struct_ref, q, option_args) + process_color_args(struct_ref, q, option_args) + process_render_image_args(struct_ref, q, option_args) + check_all_args_processed(struct_ref, q, option_args) + + +def add_scalar_render_image_quantity(name, depth_values, normal_values, scalar_values, image_origin="upper_left", struct_ref=None, **option_args): + + struct_instance_ref = _resolve_floating_struct_instance(struct_ref) + + check_is_scalar_image(depth_values) + check_is_image3(normal_values) + check_is_scalar_image(scalar_values) + check_image_dims_compatible([depth_values, normal_values]) + dimY = depth_values.shape[0] + dimX = depth_values.shape[1] + + depth_values_flat = depth_values.flatten() + normal_values_flat = normal_values.reshape(-1,3) + scalar_values_flat = scalar_values.flatten() + + q = struct_instance_ref.add_scalar_render_image_quantity(name, dimX, dimY, + depth_values_flat, normal_values_flat, scalar_values_flat, + str_to_image_origin(image_origin)) + + + # process and act on additional arguments + # note: each step modifies the option_args dict and removes processed args + process_quantity_args(struct_ref, q, option_args) + process_scalar_args(struct_ref, q, option_args) + process_render_image_args(struct_ref, q, option_args) + check_all_args_processed(struct_ref, q, option_args) diff --git a/src/polyscope/structure.py b/src/polyscope/structure.py index b324a73..be8c074 100644 --- a/src/polyscope/structure.py +++ b/src/polyscope/structure.py @@ -1,7 +1,6 @@ import polyscope_bindings as psb -from polyscope.common import check_is_scalar_image, check_is_color_image, check_is_color_alpha_image, process_scalar_args, process_color_args, process_image_args, process_quantity_args, check_all_args_processed -from polyscope.core import image_origin_to_str, str_to_image_origin, str_to_datatype +from polyscope.floating_quantities import add_scalar_image_quantity, add_color_image_quantity, add_color_alpha_image_quantity, add_depth_render_image_quantity, add_color_render_image_quantity, add_scalar_render_image_quantity # Base class for common properties and methods on structures class Structure: @@ -84,7 +83,7 @@ def add_scalar_image_quantity(self, name, values, image_origin="upper_left", dat """ # Call the general version (this abstraction allows us to handle the free-floating case via the same code) - return add_scalar_image_quantity(name, values, image_origin="upper_left", datatype="standard", struct_ref=self, **option_args) + return add_scalar_image_quantity(name, values, image_origin=image_origin, datatype=datatype, struct_ref=self, **option_args) def add_color_image_quantity(self, name, values, image_origin="upper_left", **option_args): """ @@ -92,7 +91,7 @@ def add_color_image_quantity(self, name, values, image_origin="upper_left", **op """ # Call the general version (this abstraction allows us to handle the free-floating case via the same code) - return add_color_image_quantity(name, values, image_origin="upper_left", struct_ref=self, **option_args) + return add_color_image_quantity(name, values, image_origin=image_origin, struct_ref=self, **option_args) def add_color_alpha_image_quantity(self, name, values, image_origin="upper_left", **option_args): """ @@ -100,75 +99,31 @@ def add_color_alpha_image_quantity(self, name, values, image_origin="upper_left" """ # Call the general version (this abstraction allows us to handle the free-floating case via the same code) - return add_color_alpha_image_quantity(name, values, image_origin="upper_left", struct_ref=self, **option_args) + return add_color_alpha_image_quantity(name, values, image_origin=image_origin, struct_ref=self, **option_args) - - -def _resolve_floating_struct_instance(struct_ref): - if struct_ref is None: - return psb.get_global_floating_quantity_structure() - else: - return struct_ref.bound_instance - - -def add_scalar_image_quantity(name, values, image_origin="upper_left", datatype="standard", struct_ref=None, **option_args): + ## Render Image - struct_instance_ref = _resolve_floating_struct_instance(struct_ref) - - check_is_scalar_image(values) - dimY = values.shape[0] - dimX = values.shape[1] - - values_flat = values.flatten() - - q = struct_instance_ref.add_scalar_image_quantity(name, dimX, dimY, values_flat, - str_to_image_origin(image_origin), str_to_datatype(datatype)) - - # process and act on additional arguments - # note: each step modifies the option_args dict and removes processed args - process_quantity_args(struct_ref, q, option_args) - process_image_args(struct_ref, q, option_args) - process_scalar_args(struct_ref, q, option_args) - check_all_args_processed(struct_ref, q, option_args) - -def add_color_image_quantity(name, values, image_origin="upper_left", struct_ref=None, **option_args): - - struct_instance_ref = _resolve_floating_struct_instance(struct_ref) + def add_depth_render_image_quantity(self, name, depth_values, normal_values, image_origin="upper_left", color=None, **option_args): + """ + Add a "floating" render image quantity to the structure + """ - check_is_color_image(values) - dimY = values.shape[0] - dimX = values.shape[1] + # Call the general version (this abstraction allows us to handle the free-floating case via the same code) + return add_depth_render_image_quantity(name, depth_values, normal_values, image_origin=image_origin, color=color, struct_ref=self, **option_args) - values_flat = values.reshape(-1,3) - - q = struct_instance_ref.add_color_image_quantity(name, dimX, dimY, values_flat, - str_to_image_origin(image_origin)) + def add_color_render_image_quantity(self, name, depth_values, normal_values, color_values, image_origin="upper_left", **option_args): + """ + Add a "floating" render image quantity to the structure + """ - # process and act on additional arguments - # note: each step modifies the option_args dict and removes processed args - process_quantity_args(struct_ref, q, option_args) - process_image_args(struct_ref, q, option_args) - process_color_args(struct_ref, q, option_args) - check_all_args_processed(struct_ref, q, option_args) + # Call the general version (this abstraction allows us to handle the free-floating case via the same code) + return add_color_render_image_quantity(name, depth_values, normal_values, color_values, image_origin=image_origin, struct_ref=self, **option_args) + def add_scalar_render_image_quantity(self, name, depth_values, normal_values, scalar_values, image_origin="upper_left", **option_args): + """ + Add a "floating" render image quantity to the structure + """ -def add_color_alpha_image_quantity(name, values, image_origin="upper_left", struct_ref=None, **option_args): - - struct_instance_ref = _resolve_floating_struct_instance(struct_ref) - - check_is_color_alpha_image(values) - dimY = values.shape[0] - dimX = values.shape[1] - - values_flat = values.reshape(-1,4) - - q = struct_instance_ref.add_color_alpha_image_quantity(name, dimX, dimY, values_flat, - str_to_image_origin(image_origin)) - - # process and act on additional arguments - # note: each step modifies the option_args dict and removes processed args - process_quantity_args(struct_ref, q, option_args) - process_image_args(struct_ref, q, option_args) - process_color_args(struct_ref, q, option_args) - check_all_args_processed(struct_ref, q, option_args) + # Call the general version (this abstraction allows us to handle the free-floating case via the same code) + return add_scalar_render_image_quantity(name, depth_values, normal_values, scalar_values, image_origin=image_origin, struct_ref=self, **option_args) diff --git a/test/polyscope_test.py b/test/polyscope_test.py index 0561ef1..45454b4 100644 --- a/test/polyscope_test.py +++ b/test/polyscope_test.py @@ -1654,6 +1654,9 @@ def test_floating_scalar_images(self): cam.add_scalar_image_quantity("scalar_img3", np.zeros((dimX, dimY)), enabled=True, show_in_imgui_window=True, show_in_camera_billboard=False) cam.add_scalar_image_quantity("scalar_img4", np.zeros((dimX, dimY)), enabled=True, show_fullscreen=True, show_in_camera_billboard=False, transparency=0.5) + # true floating adder + ps.add_scalar_image_quantity("scalar_img2", np.zeros((dimX, dimY)), enabled=True, image_origin='lower_left', datatype='symmetric', vminmax=(-3.,.3), cmap='reds') + ps.show(3) ps.remove_all_structures() @@ -1670,6 +1673,9 @@ def test_floating_color_images(self): cam.add_color_image_quantity("color_img2", np.zeros((dimX, dimY, 3)), enabled=True, image_origin='lower_left', show_in_camera_billboard=True) cam.add_color_image_quantity("color_img3", np.zeros((dimX, dimY, 3)), enabled=True, show_in_imgui_window=True, show_in_camera_billboard=False) cam.add_color_image_quantity("color_img4", np.zeros((dimX, dimY, 3)), enabled=True, show_fullscreen=True, show_in_camera_billboard=False, transparency=0.5) + + # true floating adder + ps.add_color_image_quantity("color_img2", np.zeros((dimX, dimY, 3)), enabled=True, image_origin='lower_left', show_in_camera_billboard=False) ps.show(3) ps.remove_all_structures() @@ -1688,8 +1694,78 @@ def test_floating_color_alpha_images(self): cam.add_color_alpha_image_quantity("color_alpha_img3", np.zeros((dimX, dimY, 4)), enabled=True, show_in_imgui_window=True, show_in_camera_billboard=False) cam.add_color_alpha_image_quantity("color_alpha_img4", np.zeros((dimX, dimY, 4)), enabled=True, show_fullscreen=True, show_in_camera_billboard=False) + # true floating adder + ps.add_color_alpha_image_quantity("color_alpha_img3", np.zeros((dimX, dimY, 4)), enabled=True, show_in_imgui_window=True, show_in_camera_billboard=False) + + ps.show(3) + ps.remove_all_structures() + + def test_floating_depth_render_images(self): + + # technically these can be added to any structure, but we will test them here + + cam = ps.register_camera_view("cam1", self.generate_parameters()) + + dimX = 300 + dimY = 600 + + depths = np.zeros((dimX, dimY)) + normals = np.ones((dimX, dimY, 3)) + + cam.add_depth_render_image_quantity("render_img", depths, normals) + cam.add_depth_render_image_quantity("render_img2", depths, normals, enabled=True, image_origin='lower_left', color=(0., 1., 0.), material='wax', transparency=0.7) + + # true floating adder + ps.add_depth_render_image_quantity("render_img3", depths, normals, enabled=True, image_origin='lower_left', color=(0., 1., 0.), material='wax', transparency=0.7, ) + ps.show(3) ps.remove_all_structures() + + def test_floating_color_render_images(self): + + # technically these can be added to any structure, but we will test them here + + cam = ps.register_camera_view("cam1", self.generate_parameters()) + + dimX = 300 + dimY = 600 + + depths = np.zeros((dimX, dimY)) + normals = np.ones((dimX, dimY, 3)) + colors = np.ones((dimX, dimY, 3)) + + cam.add_color_render_image_quantity("render_img", depths, normals, colors) + cam.add_color_render_image_quantity("render_img2", depths, normals, colors, enabled=True, image_origin='lower_left', material='wax', transparency=0.7) + + # true floating adder + ps.add_color_render_image_quantity("render_img3", depths, normals, colors, enabled=True, image_origin='lower_left', material='wax', transparency=0.7, ) + + ps.show(3) + ps.remove_all_structures() + + def test_floating_scalar_render_images(self): + + # technically these can be added to any structure, but we will test them here + + cam = ps.register_camera_view("cam1", self.generate_parameters()) + + dimX = 300 + dimY = 600 + + depths = np.zeros((dimX, dimY)) + normals = np.ones((dimX, dimY, 3)) + scalars = np.ones((dimX, dimY)) + + cam.add_scalar_render_image_quantity("render_img", depths, normals, scalars) + cam.add_scalar_render_image_quantity("render_img2", depths, normals, scalars, enabled=True, image_origin='lower_left', material='wax', transparency=0.7) + + # true floating adder + ps.add_scalar_render_image_quantity("render_img3", depths, normals, scalars, enabled=True, image_origin='lower_left', material='wax', transparency=0.7, ) + + ps.show(3) + ps.remove_all_structures() + + if __name__ == '__main__': From 61c3c3d8be9e76139626c8badd4fcdcbfd36e340 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sat, 22 Jul 2023 16:35:00 -0700 Subject: [PATCH 25/88] update dep --- deps/polyscope | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/polyscope b/deps/polyscope index 9296c27..df12e22 160000 --- a/deps/polyscope +++ b/deps/polyscope @@ -1 +1 @@ -Subproject commit 9296c27bc31a94176107ed6aa786c7c691ed1d50 +Subproject commit df12e225d08b7797ab5965b206c30492f50e435d From 7417efba8f4cd871ac7a70e1547c52476a80df2a Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sat, 22 Jul 2023 16:36:41 -0700 Subject: [PATCH 26/88] bindings for implicit helpers --- CMakeLists.txt | 1 + src/cpp/core.cpp | 18 +++++ src/cpp/floating_quantities.cpp | 20 +++-- src/cpp/implicit_helpers.cpp | 128 ++++++++++++++++++++++++++++++ src/polyscope/__init__.py | 3 + src/polyscope/common.py | 50 ++++++++++++ src/polyscope/core.py | 47 +++++++++++ src/polyscope/implicit_helpers.py | 72 +++++++++++++++++ test/polyscope_demo.py | 23 ++++++ test/polyscope_test.py | 93 ++++++++++++++++++++++ 10 files changed, 449 insertions(+), 6 deletions(-) create mode 100644 src/cpp/implicit_helpers.cpp create mode 100644 src/polyscope/implicit_helpers.py diff --git a/CMakeLists.txt b/CMakeLists.txt index feaa79a..c650716 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,6 +19,7 @@ pybind11_add_module(polyscope_bindings src/cpp/volume_mesh.cpp src/cpp/camera_view.cpp src/cpp/floating_quantities.cpp + src/cpp/implicit_helpers.cpp src/cpp/imgui.cpp ) diff --git a/src/cpp/core.cpp b/src/cpp/core.cpp index 2fba0af..70065fd 100644 --- a/src/cpp/core.cpp +++ b/src/cpp/core.cpp @@ -14,6 +14,7 @@ #include "polyscope/point_cloud.h" #include "polyscope/polyscope.h" #include "polyscope/surface_mesh.h" +#include "polyscope/types.h" #include "polyscope/view.h" #include "polyscope/volume_mesh.h" @@ -34,6 +35,7 @@ void bind_curve_network(py::module& m); void bind_volume_mesh(py::module& m); void bind_camera_view(py::module& m); void bind_floating_quantities(py::module& m); +void bind_implicit_helpers(py::module& m); void bind_imgui(py::module& m); // Signal handler (makes ctrl-c work, etc) @@ -246,6 +248,16 @@ PYBIND11_MODULE(polyscope_bindings, m) { ; py::class_(m, "CameraParameters") .def(py::init()) + .def("generate_camera_rays", [](ps::CameraParameters& c, size_t dimX, size_t dimY, ps::ImageOrigin origin) { + std::vector rays = c.generateCameraRays(dimX, dimY, origin); + Eigen::MatrixXf raysOut(rays.size(), 3); + for(size_t i = 0; i < rays.size(); i++) { + raysOut(i,0) = rays[i].x; + raysOut(i,1) = rays[i].y; + raysOut(i,2) = rays[i].z; + } + return raysOut; + }) .def("get_intrinsics", [](ps::CameraParameters& c) { return c.intrinsics; }) .def("get_extrinsics", [](ps::CameraParameters& c) { return c.extrinsics; }) .def("get_T", [](ps::CameraParameters& c) { return glm2eigen(c.getT()); }) @@ -354,6 +366,11 @@ PYBIND11_MODULE(polyscope_bindings, m) { .value("flat", ps::MeshShadeStyle::Flat) .value("tri_flat", ps::MeshShadeStyle::TriFlat) .export_values(); + + py::enum_(m, "ImplicitRenderMode") + .value("sphere_march", ps::ImplicitRenderMode::SphereMarch) + .value("fixed_step", ps::ImplicitRenderMode::FixedStep) + .export_values(); // === Mini bindings for a little bit of glm py::class_(m, "glm_vec3"). @@ -373,6 +390,7 @@ PYBIND11_MODULE(polyscope_bindings, m) { // === Bind structures defined in other files bind_floating_quantities(m); + bind_implicit_helpers(m); bind_point_cloud(m); bind_curve_network(m); bind_surface_mesh(m); diff --git a/src/cpp/floating_quantities.cpp b/src/cpp/floating_quantities.cpp index d845649..02c6ee8 100644 --- a/src/cpp/floating_quantities.cpp +++ b/src/cpp/floating_quantities.cpp @@ -39,9 +39,13 @@ void bind_floating_quantities(py::module& m) { auto qColorImage = bindColorQuantity(m, "ColorImageQuantity"); addImageQuantityBindings(qColorImage); - m.def("add_scalar_image_quantity", &ps::addScalarImageQuantity); - m.def("add_color_image_quantity", &ps::addColorImageQuantity); - m.def("add_color_alpha_image_quantity", &ps::addColorAlphaImageQuantity); + // global / free-floating adders + m.def("add_scalar_image_quantity", &ps::addScalarImageQuantity, + py::return_value_policy::reference); + m.def("add_color_image_quantity", &ps::addColorImageQuantity, + py::return_value_policy::reference); + m.def("add_color_alpha_image_quantity", &ps::addColorAlphaImageQuantity, + py::return_value_policy::reference); // == Render image floating quantities @@ -58,9 +62,13 @@ void bind_floating_quantities(py::module& m) { qScalarRenderImage.def("set_material", &ps::ScalarRenderImageQuantity::setMaterial, "Set material"); qScalarRenderImage.def("set_transparency", &ps::ScalarRenderImageQuantity::setTransparency, "Set transparency"); - m.def("add_depth_render_image_quantity", &ps::addDepthRenderImageQuantity); - m.def("add_color_render_image_quantity", &ps::addColorRenderImageQuantity); - m.def("add_scalar_render_image_quantity", &ps::addScalarRenderImageQuantity); + // global / free-floating adders + m.def("add_depth_render_image_quantity", &ps::addDepthRenderImageQuantity, + py::return_value_policy::reference); + m.def("add_color_render_image_quantity", &ps::addColorRenderImageQuantity, + py::return_value_policy::reference); + m.def("add_scalar_render_image_quantity", &ps::addScalarRenderImageQuantity, + py::return_value_policy::reference); } diff --git a/src/cpp/implicit_helpers.cpp b/src/cpp/implicit_helpers.cpp new file mode 100644 index 0000000..5f4066b --- /dev/null +++ b/src/cpp/implicit_helpers.cpp @@ -0,0 +1,128 @@ +#include +#include +#include +#include +#include + +#include "Eigen/Dense" + +#include "polyscope/polyscope.h" + +#include "polyscope/floating_quantities.h" +#include "polyscope/image_quantity.h" +#include "polyscope/implicit_helpers.h" + +#include "utils.h" + +namespace py = pybind11; +namespace ps = polyscope; + +// For overloaded functions, with C++11 compiler only +template +using overload_cast_ = pybind11::detail::overload_cast_impl; + + +// clang-format off +void bind_implicit_helpers(py::module& m) { + + // == Render implicit surfaces + + py::class_(m, "ImplicitRenderOpts") + .def(py::init<>()) + .def_readwrite("cameraParameters", &ps::ImplicitRenderOpts::cameraParameters) + .def_readwrite("dimX", &ps::ImplicitRenderOpts::dimX) + .def_readwrite("dimY", &ps::ImplicitRenderOpts::dimY) + .def_readwrite("subsampleFactor", &ps::ImplicitRenderOpts::subsampleFactor) + .def("set_missDist", [](ps::ImplicitRenderOpts& o, float val, bool isRelative) { o.missDist.set(val, isRelative); }) + .def("set_hitDist", [](ps::ImplicitRenderOpts& o, float val, bool isRelative) { o.hitDist.set(val, isRelative); }) + .def_readwrite("stepFactor", &ps::ImplicitRenderOpts::stepFactor) + .def_readwrite("normalSampleEps", &ps::ImplicitRenderOpts::normalSampleEps) + .def("set_stepSize", [](ps::ImplicitRenderOpts& o, float val, bool isRelative) { o.stepSize.set(val, isRelative); }) + .def_readwrite("nMaxSteps", &ps::ImplicitRenderOpts::nMaxSteps) + ; + + + // NOTE: we only bind the batch functions, the one-off functions would be brutally slow due to Python overhead + + + m.def("render_implicit_surface_batch", []( + + std::string name, + const std::function)>& func, + ps::ImplicitRenderMode mode, ps::ImplicitRenderOpts opts, ps::CameraView* cameraView + ) { + + // Polyscope's API uses raw buffer pointers, but we use Eigen mats for pybind11. + // Create a wrapper function that goes to/from the Eigen mats + auto wrapped_func = [&](const float* pos_ptr, float* result_ptr, uint64_t size) { + Eigen::Map> mapped_pos(pos_ptr, size, 3); + Eigen::Map mapped_result(result_ptr, size); + mapped_result = func(mapped_pos); + }; + + if(cameraView == nullptr) { + return ps::renderImplicitSurfaceBatch(name, wrapped_func, mode, opts); + } else { + return ps::renderImplicitSurfaceBatch(cameraView, name, wrapped_func, mode, opts); + } + }, py::return_value_policy::reference); + + + m.def("render_implicit_surface_color_batch", []( + std::string name, + const std::function)>& func, + const std::function)>& func_color, + ps::ImplicitRenderMode mode, ps::ImplicitRenderOpts opts, ps::CameraView* cameraView + ) { + + // Polyscope's API uses raw buffer pointers, but we use Eigen mats for pybind11. + // Create a wrapper function that goes to/from the Eigen mats + auto wrapped_func = [&](const float* pos_ptr, float* result_ptr, uint64_t size) { + Eigen::Map> mapped_pos(pos_ptr, size, 3); + Eigen::Map mapped_result(result_ptr, size); + mapped_result = func(mapped_pos); + }; + auto wrapped_func_color = [&](const float* pos_ptr, float* result_ptr, uint64_t size) { + Eigen::Map> mapped_pos(pos_ptr, size, 3); + Eigen::Map> mapped_result(result_ptr, size, 3); + mapped_result = func_color(mapped_pos); + }; + + if(cameraView == nullptr) { + return ps::renderImplicitSurfaceColorBatch(name, wrapped_func, wrapped_func_color, mode, opts); + } else { + return ps::renderImplicitSurfaceColorBatch(cameraView, name, wrapped_func, wrapped_func_color, mode, opts); + } + }, py::return_value_policy::reference); + + + m.def("render_implicit_surface_scalar_batch", []( + std::string name, + const std::function)>& func, + const std::function)>& func_scalar, + ps::ImplicitRenderMode mode, ps::ImplicitRenderOpts opts, ps::CameraView* cameraView + ) { + + // Polyscope's API uses raw buffer pointers, but we use Eigen mats for pybind11. + // Create a wrapper function that goes to/from the Eigen mats + auto wrapped_func = [&](const float* pos_ptr, float* result_ptr, uint64_t size) { + Eigen::Map> mapped_pos(pos_ptr, size, 3); + Eigen::Map mapped_result(result_ptr, size); + mapped_result = func(mapped_pos); + }; + auto wrapped_func_scalar = [&](const float* pos_ptr, float* result_ptr, uint64_t size) { + Eigen::Map> mapped_pos(pos_ptr, size, 3); + Eigen::Map mapped_result(result_ptr, size); + mapped_result = func_scalar(mapped_pos); + }; + + if(cameraView == nullptr) { + return ps::renderImplicitSurfaceScalarBatch(name, wrapped_func, wrapped_func_scalar, mode, opts); + } else { + return ps::renderImplicitSurfaceScalarBatch(cameraView, name, wrapped_func, wrapped_func_scalar, mode, opts); + } + }, py::return_value_policy::reference); + +} + +// clang-format on diff --git a/src/polyscope/__init__.py b/src/polyscope/__init__.py index df6f7b9..d9e86f8 100644 --- a/src/polyscope/__init__.py +++ b/src/polyscope/__init__.py @@ -1,6 +1,9 @@ from polyscope.core import * + from polyscope.structure import * from polyscope.floating_quantities import * +from polyscope.implicit_helpers import * + from polyscope.surface_mesh import * from polyscope.point_cloud import * from polyscope.curve_network import * diff --git a/src/polyscope/common.py b/src/polyscope/common.py index e7d80aa..f0fe265 100644 --- a/src/polyscope/common.py +++ b/src/polyscope/common.py @@ -133,7 +133,57 @@ def process_render_image_args(structure, quantity, image_args): if val is not None: quantity.set_material(val) +# Process args, removing them from the dict if they are present +def process_implicit_render_args(opts, implicit_args): + + val = check_and_pop_arg(implicit_args, 'camera_parameters') + if val is not None: + opts.cameraParameters = val.instance + + val = check_and_pop_arg(implicit_args, 'dim') + if val is not None: + opts.dimX = int(val[0]) + opts.dimY = int(val[1]) + + val = check_and_pop_arg(implicit_args, 'subsample_factor') + if val is not None: + opts.subsampleFactor = int(val) + + val = check_and_pop_arg(implicit_args, 'miss_dist') + val_relative = check_and_pop_arg(implicit_args, 'miss_dist_relative') + if val is not None: + if val_relative is None: + val_relative = True + opts.set_missDist(float(val), val_relative) + + val = check_and_pop_arg(implicit_args, 'hit_dist') + val_relative = check_and_pop_arg(implicit_args, 'hit_dist_relative') + if val is not None: + if val_relative is None: + val_relative = True + opts.set_hitDist(float(val), val_relative) + + val = check_and_pop_arg(implicit_args, 'step_factor') + if val is not None: + opts.stepFactor = float(val) + + val = check_and_pop_arg(implicit_args, 'normal_sample_eps') + if val is not None: + opts.normalSampleEps = float(val) + + val = check_and_pop_arg(implicit_args, 'step_size') + val_relative = check_and_pop_arg(implicit_args, 'step_size_relative') + if val is not None: + if val_relative is None: + val_relative = True + opts.set_stepSize(float(val), val_relative) + + val = check_and_pop_arg(implicit_args, 'n_max_steps') + if val is not None: + opts.nMaxSteps= int(val) + return opts + def check_all_args_processed(structure, quantity, args): for arg,val in args.items(): raise ValueError(f"Polyscope: Unrecognized quantity keyword argument {arg}: {val}") diff --git a/src/polyscope/core.py b/src/polyscope/core.py index 5344814..8ced3f8 100644 --- a/src/polyscope/core.py +++ b/src/polyscope/core.py @@ -605,3 +605,50 @@ def image_origin_to_str(val): raise ValueError("Bad image origin specifier '{}', should be one of [{}]".format(val, ",".join(["'{}'".format(x) for x in d_image_origin.values()]))) + +# Shade style to/from string +d_mesh_shade_style = { + "smooth" : psb.MeshShadeStyle.smooth, + "flat" : psb.MeshShadeStyle.flat, + "tri_flat" : psb.MeshShadeStyle.tri_flat, + } + +def str_to_mesh_shade_style(s): + + if s not in d_mesh_shade_style: + raise ValueError("Bad specifier '{}', should be one of [{}]".format(s, + ",".join(["'{}'".format(x) for x in d_mesh_shade_style.keys()]))) + + return d_mesh_shade_style[s] + +def mesh_shade_style_to_str(val): + + for k,v in d_mesh_shade_style.items(): + if v == val: + return k + + raise ValueError("Bad specifier '{}', should be one of [{}]".format(val, + ",".join(["'{}'".format(x) for x in d_mesh_shade_style.values()]))) + +# Implicit render mode to/from string +d_implicit_render_mode = { + "fixed_step" : psb.ImplicitRenderMode.fixed_step, + "sphere_march" : psb.ImplicitRenderMode.sphere_march, + } + +def str_to_implicit_render_mode(s): + + if s not in d_implicit_render_mode: + raise ValueError("Bad specifier '{}', should be one of [{}]".format(s, + ",".join(["'{}'".format(x) for x in d_implicit_render_mode.keys()]))) + + return d_implicit_render_mode[s] + +def implicit_render_mode_to_str(val): + + for k,v in d_implicit_render_mode.items(): + if v == val: + return k + + raise ValueError("Bad specifier '{}', should be one of [{}]".format(val, + ",".join(["'{}'".format(x) for x in d_implicit_render_mode.values()]))) diff --git a/src/polyscope/implicit_helpers.py b/src/polyscope/implicit_helpers.py new file mode 100644 index 0000000..dffca69 --- /dev/null +++ b/src/polyscope/implicit_helpers.py @@ -0,0 +1,72 @@ +import polyscope_bindings as psb + +from polyscope.common import check_is_scalar_image, check_is_image3, check_is_image4, check_image_dims_compatible, process_scalar_args, process_color_args, process_image_args, process_render_image_args, process_quantity_args, process_implicit_render_args, check_all_args_processed + +from polyscope.core import str_to_image_origin, str_to_datatype, str_to_implicit_render_mode, glm3 + + + +def render_implicit_surface(name, func, mode, camera_view=None, color=None, **option_args): + + # prep args + mode_str = str_to_implicit_render_mode(mode) + opts = psb.ImplicitRenderOpts() + opts = process_implicit_render_args(opts, option_args) + + if camera_view is None: + struct_ref = psb.get_global_floating_quantity_structure() + cam = None + else: + struct_ref = camera_view + cam = camera_view.bound_instance + + q = psb.render_implicit_surface_batch(name, func, mode_str, opts, cam) + + if color is not None: + q.set_color(glm3(color)) + + process_quantity_args(struct_ref, q, option_args) + process_render_image_args(struct_ref, q, option_args) + check_all_args_processed(struct_ref, q, option_args) + +def render_implicit_surface_color(name, func, func_color, mode, camera_view=None, **option_args): + + # prep args + mode_str = str_to_implicit_render_mode(mode) + opts = psb.ImplicitRenderOpts() + opts = process_implicit_render_args(opts, option_args) + + if camera_view is None: + struct_ref = psb.get_global_floating_quantity_structure() + cam = None + else: + struct_ref = camera_view + cam = camera_view.bound_instance + + q = psb.render_implicit_surface_color_batch(name, func, func_color, mode_str, opts, cam) + + process_quantity_args(struct_ref, q, option_args) + process_render_image_args(struct_ref, q, option_args) + process_color_args(struct_ref, q, option_args) + check_all_args_processed(struct_ref, q, option_args) + +def render_implicit_surface_scalar(name, func, func_scalar, mode, camera_view=None, **option_args): + + # prep args + mode_str = str_to_implicit_render_mode(mode) + opts = psb.ImplicitRenderOpts() + opts = process_implicit_render_args(opts, option_args) + + if camera_view is None: + struct_ref = psb.get_global_floating_quantity_structure() + cam = None + else: + struct_ref = camera_view + cam = camera_view.bound_instance + + q = psb.render_implicit_surface_scalar_batch(name, func, func_scalar, mode_str, opts, cam) + + process_quantity_args(struct_ref, q, option_args) + process_render_image_args(struct_ref, q, option_args) + process_scalar_args(struct_ref, q, option_args) + check_all_args_processed(struct_ref, q, option_args) diff --git a/test/polyscope_demo.py b/test/polyscope_demo.py index 33dab5f..fb6abf8 100644 --- a/test/polyscope_demo.py +++ b/test/polyscope_demo.py @@ -44,8 +44,12 @@ def my_function(): def callback(): # Executed every frame + if(psim.Button("Test button")): my_function() + + implicit_ui() + polyscope.set_user_callback(callback) @@ -189,11 +193,30 @@ def callback(): # Remove the whole mesh structure polyscope.remove_all_structures() + # Back to empty polyscope.show() # polyscope.clear_user_callback() +def sphere_sdf(pts): + res = np.linalg.norm(pts, axis=-1) - 1. + return res + +def color_func(pts): + # return np.cos(3*pts)**2 + A = np.ones_like(pts) * 0.3 + A[:,0] = np.cos(3*pts[:,0])**2 + return A + +def implicit_ui(): + + if(psim.Button("Render sphere SDF")): + # polyscope.render_implicit_surface("sphere sdf", sphere_sdf, 'sphere_march', enabled=True) + polyscope.render_implicit_surface_color("sphere sdf", sphere_sdf, color_func, 'sphere_march', enabled=True) + + + if __name__ == '__main__': main() diff --git a/test/polyscope_test.py b/test/polyscope_test.py index 45454b4..b38f5c2 100644 --- a/test/polyscope_test.py +++ b/test/polyscope_test.py @@ -1764,6 +1764,99 @@ def test_floating_scalar_render_images(self): ps.show(3) ps.remove_all_structures() + + def test_floating_implicit_surface_render(self): + + # this can be called free-floating or on a camera view, but we will test them here + + def sphere_sdf(pts): return np.linalg.norm(pts, axis=-1) - 1. + + # basic + ps.render_implicit_surface("sphere sdf", sphere_sdf, 'fixed_step', subsample_factor=10, n_max_steps=10, enabled=True) + + # with some args + ps.render_implicit_surface("sphere sdf", sphere_sdf, 'sphere_march', enabled=True, + camera_parameters=self.generate_parameters(), + dim=(50,75), + miss_dist=20.5, miss_dist_relative=True, + hit_dist=0.001, hit_dist_relative=False, + step_factor=0.98, + normal_sample_eps=0.02, + step_size=0.01, step_size_relative=True, + n_max_steps=50, + material='wax', + color=(0.5,0.5,0.5) + ) + + # from this camera view + cam = ps.register_camera_view("cam1", self.generate_parameters()) + ps.render_implicit_surface("sphere sdf", sphere_sdf, 'sphere_march', dim=(50,75), n_max_steps=10, camera_view=cam, enabled=True) + + ps.show(3) + ps.remove_all_structures() + + def test_floating_implicit_surface_color_render(self): + + # this can be called free-floating or on a camera view, but we will test them here + + def sphere_sdf(pts): return np.linalg.norm(pts, axis=-1) - 1. + def color_func(pts): return np.zeros_like(pts) + + # basic + ps.render_implicit_surface_color("sphere sdf", sphere_sdf, color_func, 'fixed_step', subsample_factor=10, n_max_steps=10, enabled=True) + + # with some args + ps.render_implicit_surface_color("sphere sdf", sphere_sdf, color_func, 'sphere_march', enabled=True, + camera_parameters=self.generate_parameters(), + dim=(50,75), + miss_dist=20.5, miss_dist_relative=True, + hit_dist=0.001, hit_dist_relative=False, + step_factor=0.98, + normal_sample_eps=0.02, + step_size=0.01, step_size_relative=True, + n_max_steps=50, + material='wax', + ) + + # from this camera view + cam = ps.register_camera_view("cam1", self.generate_parameters()) + ps.render_implicit_surface_color("sphere sdf", sphere_sdf, color_func, 'sphere_march', dim=(50,75), n_max_steps=10, camera_view=cam, enabled=True) + + ps.show(3) + ps.remove_all_structures() + + + def test_floating_implicit_surface_scalar_render(self): + + # this can be called free-floating or on a camera view, but we will test them here + + def sphere_sdf(pts): return np.linalg.norm(pts, axis=-1) - 1. + def scalar_func(pts): return np.ones_like(pts[:,0]) + + # basic + ps.render_implicit_surface_scalar("sphere sdf", sphere_sdf, scalar_func, 'fixed_step', subsample_factor=10, n_max_steps=10, enabled=True) + + # with some args + ps.render_implicit_surface_scalar("sphere sdf", sphere_sdf, scalar_func, 'sphere_march', enabled=True, + camera_parameters=self.generate_parameters(), + dim=(50,75), + miss_dist=20.5, miss_dist_relative=True, + hit_dist=0.001, hit_dist_relative=False, + step_factor=0.98, + normal_sample_eps=0.02, + step_size=0.01, step_size_relative=True, + n_max_steps=50, + material='wax', + cmap='blues', + vminmax=(0.,1.) + ) + + # from this camera view + cam = ps.register_camera_view("cam1", self.generate_parameters()) + ps.render_implicit_surface_scalar("sphere sdf", sphere_sdf, scalar_func, 'sphere_march', dim=(50,75), n_max_steps=10, camera_view=cam, enabled=True) + + ps.show(3) + ps.remove_all_structures() From b9f60b1c99c7a0c02fc81b3b675817ef54eca5c3 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Wed, 26 Jul 2023 08:14:13 -0700 Subject: [PATCH 27/88] update dep --- deps/polyscope | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/polyscope b/deps/polyscope index df12e22..445f170 160000 --- a/deps/polyscope +++ b/deps/polyscope @@ -1 +1 @@ -Subproject commit df12e225d08b7797ab5965b206c30492f50e435d +Subproject commit 445f1709574292d0b607623f78f958129d5f2905 From c3b4ca1b98f2aec2cfda4fbbd8dba216efc5003a Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sun, 27 Aug 2023 12:04:05 -0400 Subject: [PATCH 28/88] update dep --- deps/polyscope | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/polyscope b/deps/polyscope index 445f170..be14561 160000 --- a/deps/polyscope +++ b/deps/polyscope @@ -1 +1 @@ -Subproject commit 445f1709574292d0b607623f78f958129d5f2905 +Subproject commit be145612f894a365999b413ede4b6e188f8e3e00 From f00401c6f8247d4f04be1f959eab45bfcd6a55cf Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sun, 27 Aug 2023 12:05:38 -0400 Subject: [PATCH 29/88] minor bindings for camera rays and frame tick --- src/polyscope/core.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/polyscope/core.py b/src/polyscope/core.py index 8ced3f8..e4bc8c7 100644 --- a/src/polyscope/core.py +++ b/src/polyscope/core.py @@ -25,6 +25,9 @@ def show(forFrames=None): else: psb.show(forFrames) +def frame_tick(): + psb.frame_tick() + ### Structure management def remove_all_structures(): @@ -333,6 +336,8 @@ def get_right_dir(self): return self.instance.get_right_dir() def get_camera_frame(self): return self.instance.get_camera_frame() def get_fov_vertical_deg(self): return self.instance.get_fov_vertical_deg() def get_aspect(self): return self.instance.get_aspect() + def generate_camera_rays(self): return self.instance.generate_camera_rays() + def generate_camera_ray_corners(self): return self.instance.generate_camera_ray_corners() From f804d73ba63a09b9551dff65e4470eb753d84fe4 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sun, 27 Aug 2023 12:06:13 -0400 Subject: [PATCH 30/88] new bindings for managed buffers and raw color render image --- CMakeLists.txt | 1 + src/cpp/core.cpp | 56 +++++- src/cpp/floating_quantities.cpp | 10 +- src/cpp/implicit_helpers.cpp | 27 +++ src/cpp/managed_buffer.cpp | 171 ++++++++++++++++++ src/cpp/utils.h | 95 +++++++++- src/polyscope/__init__.py | 2 + src/polyscope/floating_quantities.py | 38 +++- .../global_floating_quantity_structure.py | 13 ++ src/polyscope/implicit_helpers.py | 22 +++ src/polyscope/managed_buffer.py | 49 +++++ src/polyscope/structure.py | 63 ++++++- test/polyscope_test.py | 51 ++++++ 13 files changed, 570 insertions(+), 28 deletions(-) create mode 100644 src/cpp/managed_buffer.cpp create mode 100644 src/polyscope/global_floating_quantity_structure.py create mode 100644 src/polyscope/managed_buffer.py diff --git a/CMakeLists.txt b/CMakeLists.txt index c650716..633e6d9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,6 +20,7 @@ pybind11_add_module(polyscope_bindings src/cpp/camera_view.cpp src/cpp/floating_quantities.cpp src/cpp/implicit_helpers.cpp + src/cpp/managed_buffer.cpp src/cpp/imgui.cpp ) diff --git a/src/cpp/core.cpp b/src/cpp/core.cpp index 70065fd..60de38f 100644 --- a/src/cpp/core.cpp +++ b/src/cpp/core.cpp @@ -36,6 +36,7 @@ void bind_volume_mesh(py::module& m); void bind_camera_view(py::module& m); void bind_floating_quantities(py::module& m); void bind_implicit_helpers(py::module& m); +void bind_managed_buffer(py::module& m); void bind_imgui(py::module& m); // Signal handler (makes ctrl-c work, etc) @@ -84,6 +85,8 @@ PYBIND11_MODULE(polyscope_bindings, m) { }, py::arg("forFrames")=std::numeric_limits::max() ); + m.def("frame_tick", &ps::frameTick); + // === Structure management m.def("remove_all_structures", &ps::removeAllStructures, "Remove all structures from polyscope"); @@ -143,6 +146,12 @@ PYBIND11_MODULE(polyscope_bindings, m) { m.def("set_view_from_json", ps::view::setViewFromJson); m.def("get_view_as_json", ps::view::getViewAsJson); + // === "Advanced" UI management + m.def("build_polyscope_gui", &ps::buildPolyscopeGui); + m.def("build_structure_gui", &ps::buildStructureGui); + m.def("build_pick_gui", &ps::buildPickGui); + m.def("build_user_gui_and_invoke_callback", &ps::buildUserGuiAndInvokeCallback); + // === Messages m.def("info", ps::info, "Send an info message"); m.def("warning", ps::warning, "Send a warning message"); @@ -248,16 +257,6 @@ PYBIND11_MODULE(polyscope_bindings, m) { ; py::class_(m, "CameraParameters") .def(py::init()) - .def("generate_camera_rays", [](ps::CameraParameters& c, size_t dimX, size_t dimY, ps::ImageOrigin origin) { - std::vector rays = c.generateCameraRays(dimX, dimY, origin); - Eigen::MatrixXf raysOut(rays.size(), 3); - for(size_t i = 0; i < rays.size(); i++) { - raysOut(i,0) = rays[i].x; - raysOut(i,1) = rays[i].y; - raysOut(i,2) = rays[i].z; - } - return raysOut; - }) .def("get_intrinsics", [](ps::CameraParameters& c) { return c.intrinsics; }) .def("get_extrinsics", [](ps::CameraParameters& c) { return c.extrinsics; }) .def("get_T", [](ps::CameraParameters& c) { return glm2eigen(c.getT()); }) @@ -273,6 +272,26 @@ PYBIND11_MODULE(polyscope_bindings, m) { }) .def("get_fov_vertical_deg", &ps::CameraParameters::getFoVVerticalDegrees) .def("get_aspect", &ps::CameraParameters::getAspectRatioWidthOverHeight) + .def("generate_camera_rays", [](ps::CameraParameters& c, size_t dimX, size_t dimY, ps::ImageOrigin origin) { + std::vector rays = c.generateCameraRays(dimX, dimY, origin); + Eigen::MatrixXf raysOut(rays.size(), 3); + for(size_t i = 0; i < rays.size(); i++) { + raysOut(i,0) = rays[i].x; + raysOut(i,1) = rays[i].y; + raysOut(i,2) = rays[i].z; + } + return raysOut; + }) + .def("generate_camera_ray_corners", [](ps::CameraParameters& c) { + std::array rays = c.generateCameraRayCorners(); + Eigen::MatrixXf raysOut(4, 3); + for(size_t i = 0; i < rays.size(); i++) { + raysOut(i,0) = rays[i].x; + raysOut(i,1) = rays[i].y; + raysOut(i,2) = rays[i].z; + } + return raysOut; + }) ; // TODO test @@ -371,6 +390,22 @@ PYBIND11_MODULE(polyscope_bindings, m) { .value("sphere_march", ps::ImplicitRenderMode::SphereMarch) .value("fixed_step", ps::ImplicitRenderMode::FixedStep) .export_values(); + + py::enum_(m, "ManagedBufferType") + .value(ps::typeName(ps::ManagedBufferType::Float ).c_str(), ps::ManagedBufferType::Float ) + .value(ps::typeName(ps::ManagedBufferType::Double ).c_str(), ps::ManagedBufferType::Double ) + .value(ps::typeName(ps::ManagedBufferType::Vec2 ).c_str(), ps::ManagedBufferType::Vec2 ) + .value(ps::typeName(ps::ManagedBufferType::Vec3 ).c_str(), ps::ManagedBufferType::Vec3 ) + .value(ps::typeName(ps::ManagedBufferType::Vec4 ).c_str(), ps::ManagedBufferType::Vec4 ) + .value(ps::typeName(ps::ManagedBufferType::Arr2Vec3).c_str(), ps::ManagedBufferType::Arr2Vec3) + .value(ps::typeName(ps::ManagedBufferType::Arr3Vec3).c_str(), ps::ManagedBufferType::Arr3Vec3) + .value(ps::typeName(ps::ManagedBufferType::Arr4Vec3).c_str(), ps::ManagedBufferType::Arr4Vec3) + .value(ps::typeName(ps::ManagedBufferType::UInt32 ).c_str(), ps::ManagedBufferType::UInt32 ) + .value(ps::typeName(ps::ManagedBufferType::Int32 ).c_str(), ps::ManagedBufferType::Int32 ) + .value(ps::typeName(ps::ManagedBufferType::UVec2 ).c_str(), ps::ManagedBufferType::UVec2 ) + .value(ps::typeName(ps::ManagedBufferType::UVec3 ).c_str(), ps::ManagedBufferType::UVec3 ) + .value(ps::typeName(ps::ManagedBufferType::UVec4 ).c_str(), ps::ManagedBufferType::UVec4 ) + .export_values(); // === Mini bindings for a little bit of glm py::class_(m, "glm_vec3"). @@ -396,6 +431,7 @@ PYBIND11_MODULE(polyscope_bindings, m) { bind_surface_mesh(m); bind_volume_mesh(m); bind_camera_view(m); + bind_managed_buffer(m); bind_imgui(m); } diff --git a/src/cpp/floating_quantities.cpp b/src/cpp/floating_quantities.cpp index 02c6ee8..de9a6aa 100644 --- a/src/cpp/floating_quantities.cpp +++ b/src/cpp/floating_quantities.cpp @@ -14,11 +14,6 @@ namespace py = pybind11; namespace ps = polyscope; -// For overloaded functions, with C++11 compiler only -template -using overload_cast_ = pybind11::detail::overload_cast_impl; - - // clang-format off void bind_floating_quantities(py::module& m) { @@ -62,6 +57,9 @@ void bind_floating_quantities(py::module& m) { qScalarRenderImage.def("set_material", &ps::ScalarRenderImageQuantity::setMaterial, "Set material"); qScalarRenderImage.def("set_transparency", &ps::ScalarRenderImageQuantity::setTransparency, "Set transparency"); + auto qRawColorRenderImage = bindColorQuantity(m, "RawColorRenderImageQuantity"); + qRawColorRenderImage.def("set_transparency", &ps::RawColorRenderImageQuantity::setTransparency, "Set transparency"); + // global / free-floating adders m.def("add_depth_render_image_quantity", &ps::addDepthRenderImageQuantity, py::return_value_policy::reference); @@ -69,6 +67,8 @@ void bind_floating_quantities(py::module& m) { py::return_value_policy::reference); m.def("add_scalar_render_image_quantity", &ps::addScalarRenderImageQuantity, py::return_value_policy::reference); + m.def("add_raw_color_render_image_quantity", &ps::addRawColorRenderImageQuantity, + py::return_value_policy::reference); } diff --git a/src/cpp/implicit_helpers.cpp b/src/cpp/implicit_helpers.cpp index 5f4066b..7d818de 100644 --- a/src/cpp/implicit_helpers.cpp +++ b/src/cpp/implicit_helpers.cpp @@ -122,6 +122,33 @@ void bind_implicit_helpers(py::module& m) { return ps::renderImplicitSurfaceScalarBatch(cameraView, name, wrapped_func, wrapped_func_scalar, mode, opts); } }, py::return_value_policy::reference); + + m.def("render_implicit_surface_raw_color_batch", []( + std::string name, + const std::function)>& func, + const std::function)>& func_color, + ps::ImplicitRenderMode mode, ps::ImplicitRenderOpts opts, ps::CameraView* cameraView + ) { + + // Polyscope's API uses raw buffer pointers, but we use Eigen mats for pybind11. + // Create a wrapper function that goes to/from the Eigen mats + auto wrapped_func = [&](const float* pos_ptr, float* result_ptr, uint64_t size) { + Eigen::Map> mapped_pos(pos_ptr, size, 3); + Eigen::Map mapped_result(result_ptr, size); + mapped_result = func(mapped_pos); + }; + auto wrapped_func_color = [&](const float* pos_ptr, float* result_ptr, uint64_t size) { + Eigen::Map> mapped_pos(pos_ptr, size, 3); + Eigen::Map> mapped_result(result_ptr, size, 3); + mapped_result = func_color(mapped_pos); + }; + + if(cameraView == nullptr) { + return ps::renderImplicitSurfaceRawColorBatch(name, wrapped_func, wrapped_func_color, mode, opts); + } else { + return ps::renderImplicitSurfaceRawColorBatch(cameraView, name, wrapped_func, wrapped_func_color, mode, opts); + } + }, py::return_value_policy::reference); } diff --git a/src/cpp/managed_buffer.cpp b/src/cpp/managed_buffer.cpp new file mode 100644 index 0000000..828e985 --- /dev/null +++ b/src/cpp/managed_buffer.cpp @@ -0,0 +1,171 @@ +#include +#include +#include + +#include "Eigen/Dense" + +#include "polyscope/polyscope.h" + +#include "polyscope/floating_quantities.h" +#include "polyscope/image_quantity.h" + +#include "utils.h" + +namespace py = pybind11; +namespace ps = polyscope; + +template +py::class_> bind_managed_buffer_T(py::module& m, ps::ManagedBufferType t) { + + return py::class_>(m, ("ManagedBuffer_" + ps::typeName(t)).c_str()) + .def("size", &ps::render::ManagedBuffer::size) + .def("get_texture_size", &ps::render::ManagedBuffer::getTextureSize) + .def("has_data", &ps::render::ManagedBuffer::hasData) + .def("summary_string", &ps::render::ManagedBuffer::summaryString) + .def("get_value", overload_cast_()(&ps::render::ManagedBuffer::getValue)) + .def("get_value", overload_cast_()(&ps::render::ManagedBuffer::getValue)) + .def("get_value", overload_cast_()(&ps::render::ManagedBuffer::getValue)) + .def("mark_host_buffer_updated", &ps::render::ManagedBuffer::markHostBufferUpdated) + .def("get_native_render_attribute_buffer_ID", + [](ps::render::ManagedBuffer& s) { + return s.getRenderAttributeBuffer()->getNativeBufferID(); + }) + .def("mark_render_attribute_buffer_updated", &ps::render::ManagedBuffer::markRenderAttributeBufferUpdated) + .def("get_native_render_texture_buffer_ID", + [](ps::render::ManagedBuffer& s) { + return s.getRenderTextureBuffer()->getNativeBufferID(); + }) + .def("mark_render_texture_buffer_updated", &ps::render::ManagedBuffer::markRenderTextureBufferUpdated) + ; +} + +// clang-format off +void bind_managed_buffer(py::module& m) { + + // explicit template instantiations + + bind_managed_buffer_T(m, ps::ManagedBufferType::Float) + .def("update_data", [](ps::render::ManagedBuffer& s, Eigen::VectorXf& d) { + if(d.rows() != s.size()) ps::exception("bad update size, should be " + std::to_string(s.size())); + for(uint32_t i = 0; i < s.size(); i++) s.data[i] = d(i); + s.markHostBufferUpdated(); + }) + ; + + bind_managed_buffer_T(m, ps::ManagedBufferType::Double) + .def("update_data", [](ps::render::ManagedBuffer& s, Eigen::VectorXd& d) { + if(d.rows() != s.size()) ps::exception("bad update size, should be " + std::to_string(s.size())); + for(uint32_t i = 0; i < s.size(); i++) s.data[i] = d(i); + s.markHostBufferUpdated(); + }) + ; + + bind_managed_buffer_T(m, ps::ManagedBufferType::Vec2) + .def("update_data", [](ps::render::ManagedBuffer& s, Eigen::Matrix& d) { + if(d.rows() != s.size()) ps::exception("bad update size, should be " + std::to_string(s.size()) + " x 2"); + for(uint32_t i = 0; i < s.size(); i++) s.data[i] = {d(i,0), d(i,1)}; + s.markHostBufferUpdated(); + }) + ; + + bind_managed_buffer_T(m, ps::ManagedBufferType::Vec3) + .def("update_data", [](ps::render::ManagedBuffer& s, Eigen::Matrix& d) { + if(d.rows() != s.size()) ps::exception("bad update size, should be " + std::to_string(s.size()) + " x 3"); + for(uint32_t i = 0; i < s.size(); i++) s.data[i] = {d(i,0), d(i,1), d(i,2)}; + s.markHostBufferUpdated(); + }) + ; + + bind_managed_buffer_T(m, ps::ManagedBufferType::Vec4) + .def("update_data", [](ps::render::ManagedBuffer& s, Eigen::Matrix& d) { + if(d.rows() != s.size() || d.cols() != 4) ps::exception("bad update size, should be " + std::to_string(s.size()) + " x 4"); + for(uint32_t i = 0; i < s.size(); i++) s.data[i] = {d(i,0), d(i,1), d(i,2), d(i,3)}; + s.markHostBufferUpdated(); + }) + ; + + bind_managed_buffer_T>(m, ps::ManagedBufferType::Arr2Vec3) + .def("update_data", [](ps::render::ManagedBuffer>& s, std::array,2>& d) { + for(uint32_t k = 0; k < 2; k++) { + if(d[k].rows() != s.size()) ps::exception("bad update size, should be " + std::to_string(s.size()) + " x 3"); + } + for(uint32_t i = 0; i < s.size(); i++) { + for(uint32_t k = 0; k < 2; k++) { + s.data[i][k] = {d[k](i,0), d[k](i,1), d[k](i,2)}; + } + } + s.markHostBufferUpdated(); + }) + ; + + + bind_managed_buffer_T>(m, ps::ManagedBufferType::Arr3Vec3) + .def("update_data", [](ps::render::ManagedBuffer>& s, std::array,3>& d) { + for(uint32_t k = 0; k < 3; k++) { + if(d[k].rows() != s.size()) ps::exception("bad update size, should be " + std::to_string(s.size()) + " x 3"); + } + for(uint32_t i = 0; i < s.size(); i++) { + for(uint32_t k = 0; k < 3; k++) { + s.data[i][k] = {d[k](i,0), d[k](i,1), d[k](i,2)}; + } + } + s.markHostBufferUpdated(); + }) + ; + + bind_managed_buffer_T>(m, ps::ManagedBufferType::Arr4Vec3) + .def("update_data", [](ps::render::ManagedBuffer>& s, std::array,4>& d) { + for(uint32_t k = 0; k < 4; k++) { + if(d[k].rows() != s.size()) ps::exception("bad update size, should be " + std::to_string(s.size()) + " x 3"); + } + for(uint32_t i = 0; i < s.size(); i++) { + for(uint32_t k = 0; k < 4; k++) { + s.data[i][k] = {d[k](i,0), d[k](i,1), d[k](i,2)}; + } + } + s.markHostBufferUpdated(); + }) + ; + + bind_managed_buffer_T(m, ps::ManagedBufferType::UInt32) + .def("update_data", [](ps::render::ManagedBuffer& s, Eigen::Matrix& d) { + if(d.rows() != s.size()) ps::exception("bad update size, should be " + std::to_string(s.size())); + for(uint32_t i = 0; i < s.size(); i++) s.data[i] = d(i); + s.markHostBufferUpdated(); + }) + ; + + bind_managed_buffer_T(m, ps::ManagedBufferType::Int32) + .def("update_data", [](ps::render::ManagedBuffer& s, Eigen::Matrix& d) { + if(d.rows() != s.size()) ps::exception("bad update size, should be " + std::to_string(s.size())); + for(uint32_t i = 0; i < s.size(); i++) s.data[i] = d(i); + s.markHostBufferUpdated(); + }) + ; + + bind_managed_buffer_T(m, ps::ManagedBufferType::UVec2) + .def("update_data", [](ps::render::ManagedBuffer& s, Eigen::Matrix& d) { + if(d.rows() != s.size()) ps::exception("bad update size, should be " + std::to_string(s.size()) + " x 2"); + for(uint32_t i = 0; i < s.size(); i++) s.data[i] = {d(i,0), d(i,1)}; + s.markHostBufferUpdated(); + }) + ; + + + bind_managed_buffer_T(m, ps::ManagedBufferType::UVec3) + .def("update_data", [](ps::render::ManagedBuffer& s, Eigen::Matrix& d) { + if(d.rows() != s.size()) ps::exception("bad update size, should be " + std::to_string(s.size()) + " x 3"); + for(uint32_t i = 0; i < s.size(); i++) s.data[i] = {d(i,0), d(i,1), d(i,2)}; + s.markHostBufferUpdated(); + }) + ; + + bind_managed_buffer_T(m, ps::ManagedBufferType::UVec4) + .def("update_data", [](ps::render::ManagedBuffer& s, Eigen::Matrix& d) { + if(d.rows() != s.size()) ps::exception("bad update size, should be " + std::to_string(s.size()) + " x 4"); + for(uint32_t i = 0; i < s.size(); i++) s.data[i] = {d(i,0), d(i,1), d(i,2), d(i,3)}; + s.markHostBufferUpdated(); + }) + ; + +} diff --git a/src/cpp/utils.h b/src/cpp/utils.h index 43f65a7..fcda527 100644 --- a/src/cpp/utils.h +++ b/src/cpp/utils.h @@ -55,13 +55,49 @@ inline Eigen::Matrix glm2eigen(const glm::vec& vec_glm) { return vec_eigen; } +template +void def_get_managed_buffer(C& c, std::string postfix) { + c.def(("get_buffer_" + postfix).c_str(), + [](StructureT& s, std::string buffer_name) -> ps::render::ManagedBuffer& { + return s.template getManagedBuffer(buffer_name); + }, + "get managed buffer", py::return_value_policy::reference); +} + +template +void def_get_quantity_managed_buffer(C& c, std::string postfix) { + c.def(("get_quantity_buffer_" + postfix).c_str(), + [](StructureT& s, std::string quantity_name, std::string buffer_name) -> ps::render::ManagedBuffer& { + ps::Quantity* qPtr = s.getQuantity(quantity_name); + if (qPtr) { + return qPtr->template getManagedBuffer(buffer_name); + } + ps::FloatingQuantity* fqPtr = s.getFloatingQuantity(quantity_name); + if (fqPtr) { + return fqPtr->template getManagedBuffer(buffer_name); + } + ps::exception("structure " + s.name + " has no quantity " + quantity_name); + return *static_cast*>(nullptr); // invalid, never executed + }, + "get quantity managed buffer", py::return_value_policy::reference); +} + + +template +void def_all_managed_buffer_funcs(C& c, ps::ManagedBufferType t) { + def_get_managed_buffer(c, ps::typeName(t)); + def_get_quantity_managed_buffer(c, ps::typeName(t)); +} + + // Add common bindings for structures template py::class_ bindStructure(py::module& m, std::string name) { - return py::class_(m, name.c_str()) - // structure basics - .def("remove", &StructureT::remove, "Remove the structure") + py::class_ s(m, name.c_str()); + + // structure basics + s.def("remove", &StructureT::remove, "Remove the structure") .def("set_enabled", &StructureT::setEnabled, "Enable the structure") .def("enable_isolate", &StructureT::enableIsolate, "Enable the structure, disable all of same type") .def("is_enabled", &StructureT::isEnabled, "Check if the structure is enabled") @@ -73,7 +109,7 @@ py::class_ bindStructure(py::module& m, std::string name) { .def("get_ignore_slice_plane", &StructureT::getIgnoreSlicePlane, "Get ignore slice plane") .def("set_cull_whole_elements", &StructureT::setCullWholeElements, "Set cull whole elements") .def("get_cull_whole_elements", &StructureT::getCullWholeElements, "Get cull whole elememts") - + // quantites .def("remove_all_quantities", &StructureT::removeAllQuantities, "Remove all quantities") .def("remove_quantity", &StructureT::removeQuantity, py::arg("name"), py::arg("errorIfAbsent") = false, @@ -90,6 +126,13 @@ py::class_ bindStructure(py::module& m, std::string name) { .def("get_transform", [](StructureT& s) { return glm2eigen(s.getTransform()); }, "get the current 4x4 transform matrix") .def("get_position", [](StructureT& s) { return glm2eigen(s.getPosition()); }, "get the position of the shape origin after transform") + // managed buffers + .def("get_buffer", [](StructureT& s, std::string name) { + + + + }, "Get a buffer by name") + // floating quantites .def("add_scalar_image_quantity", &StructureT::template addScalarImageQuantity, py::arg("name"), py::arg("dimX"), py::arg("dimY"), py::arg("values"), py::arg("imageOrigin")=ps::ImageOrigin::UpperLeft, py::arg("type")=ps::DataType::STANDARD, py::return_value_policy::reference) .def("add_color_image_quantity", &StructureT::template addColorImageQuantity, py::arg("name"), py::arg("dimX"), py::arg("dimY"), py::arg("values_rgb"), py::arg("imageOrigin")=ps::ImageOrigin::UpperLeft, py::return_value_policy::reference) @@ -97,17 +140,52 @@ py::class_ bindStructure(py::module& m, std::string name) { .def("add_depth_render_image_quantity", &StructureT::template addDepthRenderImageQuantity, py::arg("name"), py::arg("dimX"), py::arg("dimY"), py::arg("depthData"), py::arg("normalData"), py::arg("imageOrigin")=ps::ImageOrigin::UpperLeft, py::return_value_policy::reference) .def("add_color_render_image_quantity", &StructureT::template addColorRenderImageQuantity, py::arg("name"), py::arg("dimX"), py::arg("dimY"), py::arg("depthData"), py::arg("normalData"), py::arg("colorData"), py::arg("imageOrigin")=ps::ImageOrigin::UpperLeft, py::return_value_policy::reference) .def("add_scalar_render_image_quantity", &StructureT::template addScalarRenderImageQuantity, py::arg("name"), py::arg("dimX"), py::arg("dimY"), py::arg("depthData"), py::arg("normalData"), py::arg("scalarData"), py::arg("imageOrigin")=ps::ImageOrigin::UpperLeft, py::arg("type")=ps::DataType::STANDARD, py::return_value_policy::reference) + .def("add_raw_color_render_image_quantity", &StructureT::template addRawColorRenderImageQuantity, py::arg("name"), py::arg("dimX"), py::arg("dimY"), py::arg("depthData"), py::arg("colorData"), py::arg("imageOrigin")=ps::ImageOrigin::UpperLeft, py::return_value_policy::reference) ; + // managed buffer things + def_all_managed_buffer_funcs (s, ps::ManagedBufferType::Float); + def_all_managed_buffer_funcs(s, ps::ManagedBufferType::Double); + + def_all_managed_buffer_funcs(s, ps::ManagedBufferType::Vec2); + def_all_managed_buffer_funcs(s, ps::ManagedBufferType::Vec3); + def_all_managed_buffer_funcs(s, ps::ManagedBufferType::Vec4); + + def_all_managed_buffer_funcs>(s, ps::ManagedBufferType::Arr2Vec3); + def_all_managed_buffer_funcs>(s, ps::ManagedBufferType::Arr3Vec3); + def_all_managed_buffer_funcs>(s, ps::ManagedBufferType::Arr4Vec3); + + def_all_managed_buffer_funcs (s, ps::ManagedBufferType::UInt32); + def_all_managed_buffer_funcs (s, ps::ManagedBufferType::Int32); + + def_all_managed_buffer_funcs(s, ps::ManagedBufferType::UVec2); + def_all_managed_buffer_funcs(s, ps::ManagedBufferType::UVec3); + def_all_managed_buffer_funcs(s, ps::ManagedBufferType::UVec4); + + s.def("has_buffer_type", &StructureT::hasManagedBufferType, "has managed buffer type"); + s.def("has_quantity_buffer_type", [](StructureT& s, std::string quantity_name, std::string buffer_name) { + ps::Quantity* qPtr = s.getQuantity(quantity_name); + if (qPtr) { + return qPtr->hasManagedBufferType(buffer_name); + } + ps::FloatingQuantity* fqPtr = s.getFloatingQuantity(quantity_name); + if (fqPtr) { + return fqPtr->hasManagedBufferType(buffer_name); + } + ps::exception("structure " + s.name + " has no quantity " + quantity_name); + return std::make_tuple(false, ps::ManagedBufferType::Float); // invalid, never executed + }, "has quantity managed buffer type"); + + // clang-format on + return s; } // Common bindings for quantities that do not fall in to a more specific quantity below template py::class_ bindQuantity(py::module& m, std::string name) { - return py::class_(m, name.c_str()) - .def("set_enabled", &Q::setEnabled, "Set enabled"); + return py::class_(m, name.c_str()).def("set_enabled", &Q::setEnabled, "Set enabled"); } @@ -158,11 +236,10 @@ void addImageQuantityBindings(py::class_& imageQ) { imageQ.def("set_show_in_imgui_window", &ImageQ::setShowInImGuiWindow); imageQ.def("get_show_in_imgui_window", &ImageQ::getShowInImGuiWindow); - + imageQ.def("set_show_in_camera_billboard", &ImageQ::setShowInCameraBillboard); imageQ.def("get_show_in_camera_billboard", &ImageQ::getShowInCameraBillboard); - + imageQ.def("set_transparency", &ImageQ::setTransparency); imageQ.def("get_transparency", &ImageQ::getTransparency); - } diff --git a/src/polyscope/__init__.py b/src/polyscope/__init__.py index d9e86f8..1b9403a 100644 --- a/src/polyscope/__init__.py +++ b/src/polyscope/__init__.py @@ -2,6 +2,7 @@ from polyscope.structure import * from polyscope.floating_quantities import * +from polyscope.managed_buffer import * from polyscope.implicit_helpers import * from polyscope.surface_mesh import * @@ -9,3 +10,4 @@ from polyscope.curve_network import * from polyscope.volume_mesh import * from polyscope.camera_view import * +from polyscope.global_floating_quantity_structure import * diff --git a/src/polyscope/floating_quantities.py b/src/polyscope/floating_quantities.py index 2717cc0..ee314b4 100644 --- a/src/polyscope/floating_quantities.py +++ b/src/polyscope/floating_quantities.py @@ -1,7 +1,6 @@ import polyscope_bindings as psb from polyscope.common import check_is_scalar_image, check_is_image3, check_is_image4, check_image_dims_compatible, process_scalar_args, process_color_args, process_image_args, process_render_image_args, process_quantity_args, check_all_args_processed - from polyscope.core import str_to_image_origin, str_to_datatype, glm3 @@ -11,6 +10,13 @@ def _resolve_floating_struct_instance(struct_ref): else: return struct_ref.bound_instance + +def get_quantity_buffer(quantity_name, buffer_name): + from polyscope.global_floating_quantity_structure import FloatingQuantityStructure + + floating_struct = FloatingQuantityStructure() + return floating_struct.get_quantity_buffer(quantity_name, buffer_name) + def add_scalar_image_quantity(name, values, image_origin="upper_left", datatype="standard", struct_ref=None, **option_args): @@ -108,7 +114,7 @@ def add_color_render_image_quantity(name, depth_values, normal_values, color_val check_is_scalar_image(depth_values) check_is_image3(normal_values) check_is_image3(color_values) - check_image_dims_compatible([depth_values, normal_values]) + check_image_dims_compatible([depth_values, normal_values, color_values]) dimY = depth_values.shape[0] dimX = depth_values.shape[1] @@ -136,7 +142,7 @@ def add_scalar_render_image_quantity(name, depth_values, normal_values, scalar_v check_is_scalar_image(depth_values) check_is_image3(normal_values) check_is_scalar_image(scalar_values) - check_image_dims_compatible([depth_values, normal_values]) + check_image_dims_compatible([depth_values, normal_values, scalar_values]) dimY = depth_values.shape[0] dimX = depth_values.shape[1] @@ -155,3 +161,29 @@ def add_scalar_render_image_quantity(name, depth_values, normal_values, scalar_v process_scalar_args(struct_ref, q, option_args) process_render_image_args(struct_ref, q, option_args) check_all_args_processed(struct_ref, q, option_args) + + +def add_raw_color_render_image_quantity(name, depth_values, color_values, image_origin="upper_left", struct_ref=None, **option_args): + + struct_instance_ref = _resolve_floating_struct_instance(struct_ref) + + check_is_scalar_image(depth_values) + check_is_image3(color_values) + check_image_dims_compatible([depth_values, color_values]) + dimY = depth_values.shape[0] + dimX = depth_values.shape[1] + + depth_values_flat = depth_values.flatten() + color_values_flat = color_values.reshape(-1,3) + + q = struct_instance_ref.add_raw_color_render_image_quantity(name, dimX, dimY, + depth_values_flat, color_values_flat, + str_to_image_origin(image_origin)) + + + # process and act on additional arguments + # note: each step modifies the option_args dict and removes processed args + process_quantity_args(struct_ref, q, option_args) + process_color_args(struct_ref, q, option_args) + process_render_image_args(struct_ref, q, option_args) + check_all_args_processed(struct_ref, q, option_args) diff --git a/src/polyscope/global_floating_quantity_structure.py b/src/polyscope/global_floating_quantity_structure.py new file mode 100644 index 0000000..ef87759 --- /dev/null +++ b/src/polyscope/global_floating_quantity_structure.py @@ -0,0 +1,13 @@ +import polyscope_bindings as psb + +from polyscope.core import str_to_datatype, str_to_vectortype, glm3, str_to_point_render_mode, point_render_mode_to_str +from polyscope.structure import Structure + +class FloatingQuantityStructure(Structure): + + # This class wraps a _reference_ to the underlying object, whose lifetime is managed by Polyscope + + def __init__(self): + super().__init__() + self.bound_instance = psb.get_global_floating_quantity_structure() + diff --git a/src/polyscope/implicit_helpers.py b/src/polyscope/implicit_helpers.py index dffca69..b9d8af2 100644 --- a/src/polyscope/implicit_helpers.py +++ b/src/polyscope/implicit_helpers.py @@ -70,3 +70,25 @@ def render_implicit_surface_scalar(name, func, func_scalar, mode, camera_view=No process_render_image_args(struct_ref, q, option_args) process_scalar_args(struct_ref, q, option_args) check_all_args_processed(struct_ref, q, option_args) + +def render_implicit_surface_raw_color(name, func, func_color, mode, camera_view=None, **option_args): + + # prep args + mode_str = str_to_implicit_render_mode(mode) + opts = psb.ImplicitRenderOpts() + opts = process_implicit_render_args(opts, option_args) + + if camera_view is None: + struct_ref = psb.get_global_floating_quantity_structure() + cam = None + else: + struct_ref = camera_view + cam = camera_view.bound_instance + + q = psb.render_implicit_surface_raw_color_batch(name, func, func_color, mode_str, opts, cam) + + process_quantity_args(struct_ref, q, option_args) + process_render_image_args(struct_ref, q, option_args) + process_color_args(struct_ref, q, option_args) + check_all_args_processed(struct_ref, q, option_args) + diff --git a/src/polyscope/managed_buffer.py b/src/polyscope/managed_buffer.py new file mode 100644 index 0000000..c2ddb1a --- /dev/null +++ b/src/polyscope/managed_buffer.py @@ -0,0 +1,49 @@ +import polyscope_bindings as psb + +import numpy as np + + +class ManagedBuffer: + # This class wraps a _reference_ to the underlying object, whose lifetime is managed by Polyscope + + # End users should not call this constructor, structure.get_buffer("name") or something like it + def __init__(self, instance, buffer_type): + + # Wrap an existing instance + self.bound_buffer = instance + self.buffer_type = buffer_type + + def size(self): + return self.bound_buffer.size() + + def get_texture_size(self): + return self.bound_buffer.get_texture_size() + + def has_data(self): + return self.bound_buffer.has_data() + + def summary_string(self): + return self.bound_buffer.summary_string() + + def get_value(self, ind, indY=None, indZ=None): + # warning: expensive, don't call it in a loop + if indZ is not None: + return self.bound_buffer.get_value(ind, indY, indZ) + if indY is not None: + return self.bound_buffer.get_value(ind, indY) + return self.bound_buffer.get_value(ind) + + + def update_data(self, new_vals): + # TODO: this actually calls functions with different signatures based on + # what the underlying kind of buffer is. We should probably document it + # better or provide some error checking at the Python level. + # NOTE: this method calss mark_host_buffer_updated() internally, so there is no need to call it again + self.bound_buffer.update_data(new_vals); + + def mark_host_buffer_updated(self): + # note: update_data() calls this internally, so currently there is no need to call this + return self.bound_buffer.mark_host_buffer_updated() + + def mark_device_buffer_updated(self): + return self.bound_buffer.mark_device_buffer_updated() diff --git a/src/polyscope/structure.py b/src/polyscope/structure.py index be8c074..52bf508 100644 --- a/src/polyscope/structure.py +++ b/src/polyscope/structure.py @@ -1,6 +1,7 @@ import polyscope_bindings as psb -from polyscope.floating_quantities import add_scalar_image_quantity, add_color_image_quantity, add_color_alpha_image_quantity, add_depth_render_image_quantity, add_color_render_image_quantity, add_scalar_render_image_quantity +from polyscope.floating_quantities import add_scalar_image_quantity, add_color_image_quantity, add_color_alpha_image_quantity, add_depth_render_image_quantity, add_color_render_image_quantity, add_scalar_render_image_quantity, add_raw_color_render_image_quantity +from polyscope.managed_buffer import ManagedBuffer # Base class for common properties and methods on structures class Structure: @@ -55,6 +56,58 @@ def get_transform(self): def get_position(self): return self.bound_instance.get_position() + ## Managed Buffers + + def get_buffer(self, buffer_name): + + present, buffer_type = self.bound_instance.has_buffer_type(buffer_name) + + if not present: raise ValueError("structure has no buffer named " + name) + + return { + psb.ManagedBufferType.Float : lambda n,t : ManagedBuffer(self.bound_instance.get_buffer_Float (n), t), + psb.ManagedBufferType.Double : lambda n,t : ManagedBuffer(self.bound_instance.get_buffer_Double (n), t), + psb.ManagedBufferType.Vec2 : lambda n,t : ManagedBuffer(self.bound_instance.get_buffer_Vec2 (n), t), + psb.ManagedBufferType.Vec3 : lambda n,t : ManagedBuffer(self.bound_instance.get_buffer_Vec3 (n), t), + psb.ManagedBufferType.Vec4 : lambda n,t : ManagedBuffer(self.bound_instance.get_buffer_Vec4 (n), t), + psb.ManagedBufferType.Arr2Vec3 : lambda n,t : ManagedBuffer(self.bound_instance.get_buffer_Arr2Vec4(n), t), + psb.ManagedBufferType.Arr3Vec3 : lambda n,t : ManagedBuffer(self.bound_instance.get_buffer_Arr3Vec4(n), t), + psb.ManagedBufferType.Arr4Vec3 : lambda n,t : ManagedBuffer(self.bound_instance.get_buffer_Arr4Vec4(n), t), + psb.ManagedBufferType.UInt32 : lambda n,t : ManagedBuffer(self.bound_instance.get_buffer_UInt32 (n), t), + psb.ManagedBufferType.Int32 : lambda n,t : ManagedBuffer(self.bound_instance.get_buffer_Int32 (n), t), + psb.ManagedBufferType.UVec2 : lambda n,t : ManagedBuffer(self.bound_instance.get_buffer_UVec2 (n), t), + psb.ManagedBufferType.UVec3 : lambda n,t : ManagedBuffer(self.bound_instance.get_buffer_UVec3 (n), t), + psb.ManagedBufferType.UVec4 : lambda n,t : ManagedBuffer(self.bound_instance.get_buffer_UVec4 (n), t), + }[buffer_type](buffer_name, buffer_type) + + def get_quantity_buffer(self, quantity_name, buffer_name): + + present, buffer_type = self.bound_instance.has_quantity_buffer_type(quantity_name, buffer_name) + + if not present: + if self == psb.get_global_floating_quantity_structure(): + # give a more informative error if this was called on the global floating quantity, becuase it is particularly easy for users to get confused and call this function at the global scope rather than on a quantity + raise ValueError("Quantity has no buffer named " + name + ". NOTE: calling polyscope.get_quantity_buffer() is for global floating quantities only, call structure.get_quantity_buffer() to get buffers for a quantity added to some structure.") + else: + raise ValueError("quantity has no buffer named " + name) + + return { + psb.ManagedBufferType.Float : lambda q,n,t : ManagedBuffer(self.bound_instance.get_quantity_buffer_Float (q,n), t), + psb.ManagedBufferType.Double : lambda q,n,t : ManagedBuffer(self.bound_instance.get_quantity_buffer_Double (q,n), t), + psb.ManagedBufferType.Vec2 : lambda q,n,t : ManagedBuffer(self.bound_instance.get_quantity_buffer_Vec2 (q,n), t), + psb.ManagedBufferType.Vec3 : lambda q,n,t : ManagedBuffer(self.bound_instance.get_quantity_buffer_Vec3 (q,n), t), + psb.ManagedBufferType.Vec4 : lambda q,n,t : ManagedBuffer(self.bound_instance.get_quantity_buffer_Vec4 (q,n), t), + psb.ManagedBufferType.Arr2Vec3 : lambda q,n,t : ManagedBuffer(self.bound_instance.get_quantity_buffer_Arr2Vec4(q,n), t), + psb.ManagedBufferType.Arr3Vec3 : lambda q,n,t : ManagedBuffer(self.bound_instance.get_quantity_buffer_Arr3Vec4(q,n), t), + psb.ManagedBufferType.Arr4Vec3 : lambda q,n,t : ManagedBuffer(self.bound_instance.get_quantity_buffer_Arr4Vec4(q,n), t), + psb.ManagedBufferType.UInt32 : lambda q,n,t : ManagedBuffer(self.bound_instance.get_quantity_buffer_UInt32 (q,n), t), + psb.ManagedBufferType.Int32 : lambda q,n,t : ManagedBuffer(self.bound_instance.get_quantity_buffer_Int32 (q,n), t), + psb.ManagedBufferType.UVec2 : lambda q,n,t : ManagedBuffer(self.bound_instance.get_quantity_buffer_UVec2 (q,n), t), + psb.ManagedBufferType.UVec3 : lambda q,n,t : ManagedBuffer(self.bound_instance.get_quantity_buffer_UVec3 (q,n), t), + psb.ManagedBufferType.UVec4 : lambda q,n,t : ManagedBuffer(self.bound_instance.get_quantity_buffer_UVec4 (q,n), t), + }[buffer_type](quantity_name, buffer_name, buffer_type) + + ## Slice planes def set_cull_whole_elements(self, val): @@ -126,4 +179,12 @@ def add_scalar_render_image_quantity(self, name, depth_values, normal_values, sc # Call the general version (this abstraction allows us to handle the free-floating case via the same code) return add_scalar_render_image_quantity(name, depth_values, normal_values, scalar_values, image_origin=image_origin, struct_ref=self, **option_args) + + def add_raw_color_render_image_quantity(self, name, depth_values, color_values, image_origin="upper_left", **option_args): + """ + Add a "floating" render image quantity to the structure + """ + + # Call the general version (this abstraction allows us to handle the free-floating case via the same code) + return add_raw_color_render_image_quantity(name, depth_values, color_values, image_origin=image_origin, struct_ref=self, **option_args) diff --git a/test/polyscope_test.py b/test/polyscope_test.py index b38f5c2..7c31f69 100644 --- a/test/polyscope_test.py +++ b/test/polyscope_test.py @@ -1858,6 +1858,57 @@ def scalar_func(pts): return np.ones_like(pts[:,0]) ps.show(3) ps.remove_all_structures() +class TestManagedBuffers(unittest.TestCase): + + def test_managed_buffer_basics(self): + + # NOTE: this only tests the float & vec3 versions, really there are variant methods for each type + + def generate_points(n_pts=10): + np.random.seed(777) + return np.random.rand(n_pts, 3) + + def generate_scalar(n_pts=10): + np.random.seed(777) + return np.random.rand(n_pts) + + + # create a dummy point cloud; + ps_cloud = ps.register_point_cloud("test_cloud", generate_points()) + ps_scalar = ps_cloud.add_scalar_quantity("test_vals", generate_scalar()) + ps.show(3) + + # test a structure buffer of vec3 + pos_buf = ps_cloud.get_buffer("points") + self.assertEqual(pos_buf.size(), 10) + self.assertTrue(pos_buf.has_data()) + pos_buf.summary_string() + pos_buf.get_value(3) + pos_buf.update_data(generate_points()) + + # test a quantity buffer of float + scalar_buf = ps_cloud.get_quantity_buffer("test_vals", "values") + self.assertEqual(scalar_buf.size(), 10) + self.assertTrue(scalar_buf.has_data()) + scalar_buf.summary_string() + scalar_buf.get_value(3) + scalar_buf.update_data(generate_scalar()) + + ps.show(3) + + # test a free-floating quantity buffer + dimX = 200 + dimY = 300 + ps.add_scalar_image_quantity("test_float_img", np.zeros((dimX, dimY))) + img_buf = ps.get_quantity_buffer("test_float_img", "values") + self.assertEqual(img_buf.size(), dimX*dimY) + self.assertTrue(img_buf.has_data()) + img_buf.summary_string() + img_buf.get_value(3) + img_buf.update_data(np.zeros(dimX*dimY)) + + + ps.show(3) if __name__ == '__main__': From c19ffb2e94521e8d1fc6f565567efe6d52da7a86 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sat, 16 Sep 2023 16:27:54 -0400 Subject: [PATCH 31/88] update deps --- deps/polyscope | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/polyscope b/deps/polyscope index be14561..e322661 160000 --- a/deps/polyscope +++ b/deps/polyscope @@ -1 +1 @@ -Subproject commit be145612f894a365999b413ede4b6e188f8e3e00 +Subproject commit e3226618ab7654161e61f2828d3750e170f1e6df From 5097d797e1a66cf1ef7ecf1fddeea796409d2787 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sat, 16 Sep 2023 19:41:33 -0400 Subject: [PATCH 32/88] update new buffer device updates --- src/cpp/core.cpp | 18 +- src/cpp/managed_buffer.cpp | 17 +- src/cpp/utils.h | 1 + src/polyscope/core.py | 7 +- src/polyscope/device_interop.py | 308 ++++++++++++++++++++++++++++++++ src/polyscope/managed_buffer.py | 81 ++++++++- test/fast_update_demo.py | 112 ++++++++++++ 7 files changed, 525 insertions(+), 19 deletions(-) create mode 100644 src/polyscope/device_interop.py create mode 100644 test/fast_update_demo.py diff --git a/src/cpp/core.cpp b/src/cpp/core.cpp index 60de38f..b63f848 100644 --- a/src/cpp/core.cpp +++ b/src/cpp/core.cpp @@ -87,6 +87,8 @@ PYBIND11_MODULE(polyscope_bindings, m) { ); m.def("frame_tick", &ps::frameTick); + // === Render engine related things + m.def("get_render_engine_backend_name", &ps::render::getRenderEngineBackendName); // === Structure management m.def("remove_all_structures", &ps::removeAllStructures, "Remove all structures from polyscope"); @@ -293,7 +295,14 @@ PYBIND11_MODULE(polyscope_bindings, m) { return raysOut; }) ; - // TODO test + + // === Weak Handles + // (used for lifetime tracking tricks) + py::class_(m, "GenericWeakHandle") + .def(py::init<>()) + .def("is_valid", &ps::GenericWeakHandle::isValid) + .def("get_unique_ID", &ps::GenericWeakHandle::getUniqueID) + ; // === Enums @@ -406,6 +415,13 @@ PYBIND11_MODULE(polyscope_bindings, m) { .value(ps::typeName(ps::ManagedBufferType::UVec3 ).c_str(), ps::ManagedBufferType::UVec3 ) .value(ps::typeName(ps::ManagedBufferType::UVec4 ).c_str(), ps::ManagedBufferType::UVec4 ) .export_values(); + + py::enum_(m, "DeviceBufferType") + .value("attribute", ps::DeviceBufferType::Attribute) + .value("texture1d", ps::DeviceBufferType::Texture1d) + .value("texture2d", ps::DeviceBufferType::Texture2d) + .value("texture3d", ps::DeviceBufferType::Texture3d) + .export_values(); // === Mini bindings for a little bit of glm py::class_(m, "glm_vec3"). diff --git a/src/cpp/managed_buffer.cpp b/src/cpp/managed_buffer.cpp index 828e985..7555d03 100644 --- a/src/cpp/managed_buffer.cpp +++ b/src/cpp/managed_buffer.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include "Eigen/Dense" @@ -22,21 +23,21 @@ py::class_> bind_managed_buffer_T(py::module& m, ps .def("get_texture_size", &ps::render::ManagedBuffer::getTextureSize) .def("has_data", &ps::render::ManagedBuffer::hasData) .def("summary_string", &ps::render::ManagedBuffer::summaryString) + .def("get_device_buffer_type", &ps::render::ManagedBuffer::getDeviceBufferType) + .def("get_generic_weak_handle", + [](ps::render::ManagedBuffer& s) { + return s.getGenericWeakHandle(); + } /* intentionally let Python manage ownership */) .def("get_value", overload_cast_()(&ps::render::ManagedBuffer::getValue)) .def("get_value", overload_cast_()(&ps::render::ManagedBuffer::getValue)) .def("get_value", overload_cast_()(&ps::render::ManagedBuffer::getValue)) .def("mark_host_buffer_updated", &ps::render::ManagedBuffer::markHostBufferUpdated) .def("get_native_render_attribute_buffer_ID", - [](ps::render::ManagedBuffer& s) { - return s.getRenderAttributeBuffer()->getNativeBufferID(); - }) + [](ps::render::ManagedBuffer& s) { return s.getRenderAttributeBuffer()->getNativeBufferID(); }) .def("mark_render_attribute_buffer_updated", &ps::render::ManagedBuffer::markRenderAttributeBufferUpdated) .def("get_native_render_texture_buffer_ID", - [](ps::render::ManagedBuffer& s) { - return s.getRenderTextureBuffer()->getNativeBufferID(); - }) - .def("mark_render_texture_buffer_updated", &ps::render::ManagedBuffer::markRenderTextureBufferUpdated) - ; + [](ps::render::ManagedBuffer& s) { return s.getRenderTextureBuffer()->getNativeBufferID(); }) + .def("mark_render_texture_buffer_updated", &ps::render::ManagedBuffer::markRenderTextureBufferUpdated); } // clang-format off diff --git a/src/cpp/utils.h b/src/cpp/utils.h index fcda527..5cf6585 100644 --- a/src/cpp/utils.h +++ b/src/cpp/utils.h @@ -98,6 +98,7 @@ py::class_ bindStructure(py::module& m, std::string name) { // structure basics s.def("remove", &StructureT::remove, "Remove the structure") + .def("get_unique_prefix", &StructureT::uniquePrefix, "Get unique prefix") .def("set_enabled", &StructureT::setEnabled, "Enable the structure") .def("enable_isolate", &StructureT::enableIsolate, "Enable the structure, disable all of same type") .def("is_enabled", &StructureT::isEnabled, "Check if the structure is enabled") diff --git a/src/polyscope/core.py b/src/polyscope/core.py index e4bc8c7..3a10213 100644 --- a/src/polyscope/core.py +++ b/src/polyscope/core.py @@ -28,6 +28,12 @@ def show(forFrames=None): def frame_tick(): psb.frame_tick() +### Render engine + +def get_render_engine_backend_name(): + return psb.get_render_engine_backend_name() + + ### Structure management def remove_all_structures(): @@ -340,7 +346,6 @@ def generate_camera_rays(self): return self.instance.generate_camera_rays() def generate_camera_ray_corners(self): return self.instance.generate_camera_ray_corners() - ## Small utilities def glm3(vals): return psb.glm_vec3(vals[0], vals[1], vals[2]) diff --git a/src/polyscope/device_interop.py b/src/polyscope/device_interop.py new file mode 100644 index 0000000..a02b675 --- /dev/null +++ b/src/polyscope/device_interop.py @@ -0,0 +1,308 @@ +import polyscope_bindings as psb + +import os + +import numpy as np + +from polyscope.core import get_render_engine_backend_name + + +# Dependencies which are needed for interop but otherwise optional for polyscope +# +# Try both of these imports, but fail silently if they don't work (we will try +# again and print an informative error below only if a relevant function is called) +try: + import cuda + from cuda import cudart + import cupy +except ImportError: + _has_cuda_libs = False +else: + _has_cuda_libs = True + +# This seems absurd! These constants are needed for cuda.cudart calls, but there is apparently no canonical way to get them without introducing an additional dependency. +# See discussion https://discourse.panda3d.org/t/allowing-torch-tensorflow-to-directly-access-rendered-image-in-gpu/29119/3 +# As a workaround, we hardcord their constant values, fetched from https://cvs.khronos.org/svn/repos/ogl/trunk/doc/registry/public/api/GL/glcorearb.h +_CONSTANT_GL_TEXTURE_2D = int('0DE1',16) +_CONSTANT_GL_TEXTURE_3D = int('806F',16) + +## Experimental things + +def check_device_module_availibility(): + + supported_backends = ["openGL3_glfw"] + if get_render_engine_backend_name() not in supported_backends: + raise ValueError(f"This Polyscope functionality is not supported by the current rendering backend ({get_render_engine_backend_name()}. Supported backends: {','.join(supported_backends)}.") + + if not _has_cuda_libs: + raise ImportError('This Polyscope functionality requires cuda bindings to be installed. Please install the packages `cuda` and `cupy`. https://nvidia.github.io/cuda-python/ & https://cupy.dev/') + + +# TODO is it possible to implement this without relying on exceptions? +''' +def is_dlpack(obj): + return hasattr(obj, '__dlpack__') and hasattr(obj, '__dlpack_device__') +''' + +def is_cuda_array_interface(obj): + return hasattr(obj, '__cuda_array_interface__') + + +def format_cudart_err(err): + return ( + f"{cudart.cudaGetErrorName(err)[1].decode('utf-8')}({int(err)}): " + f"{cudart.cudaGetErrorString(err)[1].decode('utf-8')}" + ) + + +def check_cudart_err(args): + if isinstance(args, tuple): + assert len(args) >= 1 + err = args[0] + if len(args) == 1: + ret = None + elif len(args) == 2: + ret = args[1] + else: + ret = args[1:] + else: + err = args + ret = None + + assert isinstance(err, cudart.cudaError_t), type(err) + if err != cudart.cudaError_t.cudaSuccess: + raise RuntimeError(format_cudart_err(err)) + + return ret + +class CUDAOpenGLMappedAttributeBuffer: + + # Roughly based on this, see for more goodies: https://gist.github.com/keckj/e37d312128eac8c5fca790ce1e7fc437 + + def __init__(self, gl_attribute_native_id, buffer_type): + check_device_module_availibility() + + self.gl_attribute_native_id = gl_attribute_native_id + self.buffer_type = buffer_type + self.resource_handle = None + self.cuda_buffer_ptr = None + self.cuda_buffer_size = -1 + + + # Sanity checks + if self.buffer_type != psb.DeviceBufferType.attribute: + raise ValueError("device buffer type should be attribute") + + # Register the buffer + self.resource_handle = check_cudart_err( + cudart.cudaGraphicsGLRegisterBuffer( + self.gl_attribute_native_id, + # NOTE: setting write-only flag + cudart.cudaGraphicsRegisterFlags.cudaGraphicsRegisterFlagsWriteDiscard + # cudart.cudaGraphicsRegisterFlags.cudaGraphicsRegisterFlagsNone + ) + ) + + def __del__(self): + self.unregister() + + def unregister(self): + self.unmap() + self.resource_handle = check_cudart_err( + cudart.cudaGraphicsUnregisterResource(self.resource_handle)) + + def map(self): + """ + Returns a cupy memory pointer to the buffer + """ + + if self.cuda_buffer_ptr is not None: + return + + check_cudart_err(cudart.cudaGraphicsMapResources(1, self.resource_handle, None)) + + ptr, size = check_cudart_err(cudart.cudaGraphicsResourceGetMappedPointer(self.resource_handle)) + + self.cuda_buffer_ptr = cupy.cuda.MemoryPointer(cupy.cuda.UnownedMemory(ptr, size, self), 0) + self.cuda_buffer_size = size + + + def unmap(self): + if self.cuda_buffer_ptr is None: + return + + check_cudart_err(cudart.cudaGraphicsUnmapResources(1, self.resource_handle, None)) + + self.cuda_buffer_ptr = None + self.cuda_buffer_size = -1 + + def set_data_from_array(self, arr, expected_shape=None, expected_dtype=None): + + self.map() + + cupy_arr = self.get_array_from_unknown_data(arr) + + # do some shape & type checks + if expected_dtype is not None and cupy_arr.dtype != expected_dtype: + raise ValueError(f"dlpack array has wrong dtype, expected {expected_dtype} but got {cupy_arr.dtype}") + + if expected_shape is not None and cupy_arr.shape != expected_shape: + raise ValueError(f"dlpack array has wrong shape, expected {expected_shape} but got {cupy_arr.shape}") + + if cupy_arr.nbytes != self.cuda_buffer_size: + # if cupy_arr has the right size/dtype, it should have exactly the same + # number of bytes as the destination. This is just lazily saving us + # from repeating the math, and also directly validates the copy we + # are about to do below. + raise ValueError(f"Mapped buffer write has wrong size, expected {cupy_arr.nbytes} bytes but got {self.cuda_buffer_size} bytes. Could it be the wrong size/shape or wrong dtype?") + + + # perform the actualy copy + self.cuda_buffer_ptr.copy_from_device(cupy_arr.data, self.cuda_buffer_size) + + self.unmap() + + + def get_array_from_unknown_data(self, arr): + + # Dispatch to one of the kinds of objects that we can read from + + # __cuda_array_interface__ + if is_cuda_array_interface(arr): + return cupy.ascontiguousarray(cupy.asarray(arr)) + + # __dlpack__ + # (I can't figure out any way to check this except try-catch) + try: + return cupy.ascontiguousarray(cupy.from_dlpack(arr)) + except ValueError: + pass + + raise ValueError("Cannot read from device data object. Must be a _dlpack_ array or implement the __cuda_array_interface__.") + + +class CUDAOpenGLMappedTextureBuffer: + + + def __init__(self, gl_attribute_native_id, buffer_type): + check_device_module_availibility() + + self.gl_attribute_native_id = gl_attribute_native_id + self.buffer_type = buffer_type + self.resource_handle = None + self.cuda_buffer_array = None # NOTE: 'array' has a special cuda meaning here relating to texture memory + + # Register the buffer + + if self.buffer_type == psb.DeviceBufferType.attribute: + raise ValueError("type should be texture*") + + elif self.buffer_type == psb.DeviceBufferType.texture1d: + raise ValueError("1d texture writes are not supported") + # apparently not supported (?!) + # see cudaGraphicsGLRegisterImage in these docs https://docs.nvidia.com/cuda/cuda-runtime-api/group__CUDART__OPENGL.html + + elif self.buffer_type == psb.DeviceBufferType.texture2d: + + self.resource_handle = check_cudart_err( + cudart.cudaGraphicsGLRegisterImage( + self.gl_attribute_native_id, + _CONSTANT_GL_TEXTURE_2D, + cudart.cudaGraphicsRegisterFlags.cudaGraphicsRegisterFlagsWriteDiscard + ) + ) + + elif self.buffer_type == psb.DeviceBufferType.texture3d: + raise ValueError("3d texture writes are not implemented") + + + def __del__(self): + self.unregister() + + def unregister(self): + self.unmap() + self.resource_handle = check_cudart_err( + cudart.cudaGraphicsUnregisterResource(self.resource_handle)) + + def map(self): + if self.cuda_buffer_array is not None: + return + + check_cudart_err(cudart.cudaGraphicsMapResources(1, self.resource_handle, None)) + + self.cuda_buffer_array = check_cudart_err(cudart.cudaGraphicsSubResourceGetMappedArray(self.resource_handle, 0, 0)) + + + def unmap(self): + if self.cuda_buffer_array is None: + return + + check_cudart_err(cudart.cudaGraphicsUnmapResources(1, self.resource_handle, None)) + + self.cuda_buffer_array = None + + def set_data_from_array(self, arr, expected_shape=None, expected_dtype=None): + + self.map() + + # get some info about the polyscope buffer we just mapped + desc, extent, flags = check_cudart_err(cudart.cudaArrayGetInfo(self.cuda_buffer_array)) + + # find a way to access the input array + cupy_arr = self.get_array_from_unknown_data(arr) + + # do some shape & type checks + if expected_dtype is not None and cupy_arr.dtype != expected_dtype: + raise ValueError(f"dlpack array has wrong dtype, expected {expected_dtype} but got {cupy_arr.dtype}") + + if expected_shape is not None and cupy_arr.shape != expected_shape: + raise ValueError(f"dlpack array has wrong shape, expected {expected_shape} but got {cupy_arr.shape}") + + + if self.buffer_type == psb.DeviceBufferType.texture2d: + + buff_width = extent.width + buff_height = extent.height + buff_bytes_per = (desc.x + desc.y + desc.z + desc.w) // 8 + + # do some shape & type checks + expected_bytes = buff_width * buff_height * buff_bytes_per + if cupy_arr.nbytes != expected_bytes: + # if cupy_arr has the right size/dtype, it should have exactly the same + # number of bytes as the destination. This is just lazily saving us + # from repeating the math, and also directly validates the copy we + # are about to do below. + raise ValueError(f"Mapped buffer write has wrong size, expected {cupy_arr.nbytes} bytes but got {expected_bytes} bytes. Could it be the wrong size/shape or wrong dtype?") + + + # perform the actual copy + check_cudart_err( + cuda.cudart.cudaMemcpy2DToArray( + self.cuda_buffer_array, 0, 0, + cupy_arr.data.ptr, buff_width * buff_bytes_per, buff_width * buff_bytes_per, buff_height, + cudart.cudaMemcpyKind.cudaMemcpyDeviceToDevice + ) + ) + + else: + raise ValueError("not implemented") + + self.unmap() + + + def get_array_from_unknown_data(self, arr): + + # Dispatch to one of the kinds of objects that we can read from + + # __cuda_array_interface__ + if is_cuda_array_interface(arr): + return cupy.ascontiguousarray(cupy.asarray(arr)) + + # __dlpack__ + # (I can't figure out any way to check this except try-catch) + try: + return cupy.ascontiguousarray(cupy.from_dlpack(arr)) + except ValueError: + pass + + raise ValueError("Cannot read from device data object. Must be a _dlpack_ array or implement the __cuda_array_interface__.") diff --git a/src/polyscope/managed_buffer.py b/src/polyscope/managed_buffer.py index c2ddb1a..42d8f5d 100644 --- a/src/polyscope/managed_buffer.py +++ b/src/polyscope/managed_buffer.py @@ -2,30 +2,53 @@ import numpy as np +from polyscope.device_interop import CUDAOpenGLMappedAttributeBuffer, CUDAOpenGLMappedTextureBuffer + +# A cache of mapped buffers +_mapped_buffer_cache_CUDAOpenGL = {} # maps uniqueID --> buffer object class ManagedBuffer: + # This class wraps a _reference_ to the underlying object, whose lifetime is managed by Polyscope - # End users should not call this constructor, structure.get_buffer("name") or something like it + # End users should not call this constructor, use structure.get_buffer("name") or another like it def __init__(self, instance, buffer_type): - # Wrap an existing instance self.bound_buffer = instance self.buffer_type = buffer_type + self.device_buffer_type = self.bound_buffer.get_device_buffer_type() + + self.buffer_weak_ref = self.bound_buffer.get_generic_weak_handle() + self.uniqueID = self.buffer_weak_ref.get_unique_ID() + + def check_ref_still_valid(self): + if not self.buffer_weak_ref.is_valid(): + raise ValueError("[polyscope] lifetime of underlying buffer has expired") def size(self): + self.check_ref_still_valid() + return self.bound_buffer.size() def get_texture_size(self): + self.check_ref_still_valid() + return self.bound_buffer.get_texture_size() def has_data(self): + self.check_ref_still_valid() + return self.bound_buffer.has_data() def summary_string(self): + self.check_ref_still_valid() + + return self.bound_buffer.summary_string() def get_value(self, ind, indY=None, indZ=None): + self.check_ref_still_valid() + # warning: expensive, don't call it in a loop if indZ is not None: return self.bound_buffer.get_value(ind, indY, indZ) @@ -33,17 +56,57 @@ def get_value(self, ind, indY=None, indZ=None): return self.bound_buffer.get_value(ind, indY) return self.bound_buffer.get_value(ind) - def update_data(self, new_vals): + self.check_ref_still_valid() + + self.update_data_from_host(new_vals); + + def update_data_from_host(self, new_vals): + self.check_ref_still_valid() + # TODO: this actually calls functions with different signatures based on # what the underlying kind of buffer is. We should probably document it # better or provide some error checking at the Python level. - # NOTE: this method calss mark_host_buffer_updated() internally, so there is no need to call it again + # NOTE: this method calls mark_host_buffer_updated() internally, so there is no need to call it again self.bound_buffer.update_data(new_vals); - def mark_host_buffer_updated(self): - # note: update_data() calls this internally, so currently there is no need to call this - return self.bound_buffer.mark_host_buffer_updated() + def update_data_from_device(self, new_vals_device): + self.check_ref_still_valid() + + mapped_buffer = self.get_mapped_buffer_CUDAOpenGL() + mapped_buffer.set_data_from_array(new_vals_device) + + if self.device_buffer_type == psb.DeviceBufferType.attribute: + self.bound_buffer.mark_render_attribute_buffer_updated() + else: # texture + self.bound_buffer.mark_render_texture_buffer_updated() + + def get_mapped_buffer_CUDAOpenGL(self): + self.check_ref_still_valid() + + if self.uniqueID not in _mapped_buffer_cache_CUDAOpenGL: + # create a new one + if self.device_buffer_type == psb.DeviceBufferType.attribute: + nativeID = self.bound_buffer.get_native_render_attribute_buffer_ID() + + _mapped_buffer_cache_CUDAOpenGL[self.uniqueID] = \ + CUDAOpenGLMappedAttributeBuffer( + nativeID, + self.device_buffer_type + ) + + else: # texture + nativeID = self.bound_buffer.get_native_render_texture_buffer_ID() + + _mapped_buffer_cache_CUDAOpenGL[self.uniqueID] = \ + CUDAOpenGLMappedTextureBuffer( + nativeID, + self.device_buffer_type + ) + + + return _mapped_buffer_cache_CUDAOpenGL[self.uniqueID] - def mark_device_buffer_updated(self): - return self.bound_buffer.mark_device_buffer_updated() + def release_mapped_buffer_CUDAOpenGL(self): + if self.uniqueID in _mapped_buffer_cache_CUDAOpenGL: + del _mapped_buffer_cache_CUDAOpenGL[self.uniqueID] diff --git a/test/fast_update_demo.py b/test/fast_update_demo.py new file mode 100644 index 0000000..6f9ebb3 --- /dev/null +++ b/test/fast_update_demo.py @@ -0,0 +1,112 @@ +import os +import sys +import os.path as path + +# Path to where the bindings live +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src"))) +if os.name == 'nt': # if Windows + # handle default location where VS puts binary + sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "build", "Debug"))) +else: + # normal / unix case + sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "build"))) + +import polyscope as ps +import polyscope.imgui as psim +from polyscope import imgui as psim +import potpourri3d as pp3d + +import sys +import argparse +import numpy as np + +import torch as torch + +def toNP(arr): + return arr.detach().cpu().numpy() + +def main(): + + parser = argparse.ArgumentParser() + + # Build arguments + + # Parse arguments + args = parser.parse_args() + + device = torch.device('cuda:0') + #device = torch.device('cpu') + dtype = torch.float32 + + ui_nPts = 10000 + ui_gpu_update = True + ui_step_size = 0.01 + + nPts = ui_nPts + step_size = ui_step_size + gpu_update = ui_gpu_update + + pos = torch.zeros((nPts,3), device=device, dtype=dtype) + ps_pts = None + + def reinitialize(): + nonlocal nPts, ui_nPts, step_size, ui_step_size, gpu_update, ui_gpu_update, pos, ps_pts + + nPts = ui_nPts + step_size = ui_step_size + gpu_update = ui_gpu_update + + pos = torch.zeros((nPts,3), device=device, dtype=dtype) + + ps_pts = ps.register_point_cloud("points", toNP(pos), point_render_mode='quad') + + def step_simulation(): + nonlocal pos + + steps = torch.randn((nPts, 3), dtype=pos.dtype, device=pos.device) * step_size + pos += steps + + def update_viz(): + nonlocal pos + + if gpu_update: + ps_pts.get_buffer('points').update_data_from_device(pos) + + else: + ps_pts.update_point_positions(toNP(pos)) + + def callback(): + nonlocal ui_nPts, ui_step_size, ui_gpu_update + + _, ui_nPts = psim.InputInt("nPts", ui_nPts) + _, ui_step_size = psim.InputFloat("step size", ui_step_size) + _, ui_gpu_update = psim.Checkbox("gpu update", ui_gpu_update) + + # Executed every frame + if(psim.Button("Re-initialize")): + reinitialize() + + step_simulation() + update_viz() + + + ps.set_user_callback(callback) + + ps.set_automatically_compute_scene_extents(False) + ps.set_length_scale(1.) + low = np.array((-1, -1., -1.)) + high = np.array((1., 1., 1.)) + ps.set_bounding_box(low, high) + + # Always initialize exactly once + ps.init() + + reinitialize() + + ps.set_ground_plane_mode("shadow_only") + + ps.show() + + +if __name__ == '__main__': + main() From a6b06666dafd45037d27ea9049cf51f352988577 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sun, 8 Oct 2023 18:52:42 -0700 Subject: [PATCH 33/88] point dep back at main branch --- deps/polyscope | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/polyscope b/deps/polyscope index e322661..4449c87 160000 --- a/deps/polyscope +++ b/deps/polyscope @@ -1 +1 @@ -Subproject commit e3226618ab7654161e61f2828d3750e170f1e6df +Subproject commit 4449c8739f971779aea562eeee54d65fc92d3feb From 0905571b98a203976f976a987704f84c01fb2cbe Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sun, 8 Oct 2023 22:47:04 -0700 Subject: [PATCH 34/88] add bindings for additional options --- src/cpp/core.cpp | 4 +++- src/polyscope/core.py | 6 ++++++ test/polyscope_test.py | 2 ++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/cpp/core.cpp b/src/cpp/core.cpp index b63f848..6ba631e 100644 --- a/src/cpp/core.cpp +++ b/src/cpp/core.cpp @@ -102,14 +102,16 @@ PYBIND11_MODULE(polyscope_bindings, m) { m.def("set_program_name", [](std::string x) { ps::options::programName = x; }); m.def("set_verbosity", [](int x) { ps::options::verbosity = x; }); m.def("set_print_prefix", [](std::string x) { ps::options::printPrefix = x; }); - m.def("set_max_fps", [](int x) { ps::options::maxFPS = x; }); m.def("set_errors_throw_exceptions", [](bool x) { ps::options::errorsThrowExceptions = x; }); + m.def("set_max_fps", [](int x) { ps::options::maxFPS = x; }); + m.def("set_enable_vsync", [](bool x) { ps::options::enableVSync = x; }); m.def("set_use_prefs_file", [](bool x) { ps::options::usePrefsFile = x; }); m.def("set_always_redraw", [](bool x) { ps::options::alwaysRedraw = x; }); m.def("set_enable_render_error_checks", [](bool x) { ps::options::enableRenderErrorChecks = x; }); m.def("set_autocenter_structures", [](bool x) { ps::options::autocenterStructures = x; }); m.def("set_autoscale_structures", [](bool x) { ps::options::autoscaleStructures = x; }); m.def("set_build_gui", [](bool x) { ps::options::buildGui = x; }); + m.def("set_render_scene", [](bool x) { ps::options::renderScene = x; }); m.def("set_open_imgui_window_for_user_callback", [](bool x) { ps::options::openImGuiWindowForUserCallback= x; }); m.def("set_invoke_user_callback_for_nested_show", [](bool x) { ps::options::invokeUserCallbackForNestedShow = x; }); m.def("set_give_focus_on_show", [](bool x) { ps::options::giveFocusOnShow = x; }); diff --git a/src/polyscope/core.py b/src/polyscope/core.py index 3a10213..ed2777b 100644 --- a/src/polyscope/core.py +++ b/src/polyscope/core.py @@ -70,6 +70,9 @@ def set_errors_throw_exceptions(val): def set_max_fps(f): psb.set_max_fps(f) +def set_enable_vsync(b): + psb.set_enable_vsync(b) + def set_use_prefs_file(v): psb.set_use_prefs_file(v) @@ -88,6 +91,9 @@ def set_autoscale_structures(b): def set_build_gui(b): psb.set_build_gui(b) +def set_render_scene(b): + psb.set_render_scene(b) + def set_open_imgui_window_for_user_callback(b): psb.set_open_imgui_window_for_user_callback(b) diff --git a/test/polyscope_test.py b/test/polyscope_test.py index 7c31f69..cc48bbb 100644 --- a/test/polyscope_test.py +++ b/test/polyscope_test.py @@ -41,6 +41,7 @@ def test_options(self): ps.set_print_prefix("polyscope test") ps.set_errors_throw_exceptions(True) ps.set_max_fps(60) + ps.set_enable_vsync(True) ps.set_use_prefs_file(True) ps.set_always_redraw(False) @@ -50,6 +51,7 @@ def test_options(self): ps.set_autoscale_structures(False) ps.set_build_gui(True) + ps.set_render_scene(True) ps.set_open_imgui_window_for_user_callback(True) ps.set_invoke_user_callback_for_nested_show(False) ps.set_give_focus_on_show(True) From d7d93c5afd21d975a90aeb26e9609466e529c198 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sun, 8 Oct 2023 22:47:15 -0700 Subject: [PATCH 35/88] bind ImGuiIO --- src/cpp/imgui.cpp | 93 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 92 insertions(+), 1 deletion(-) diff --git a/src/cpp/imgui.cpp b/src/cpp/imgui.cpp index 76bc8c4..6589b42 100644 --- a/src/cpp/imgui.cpp +++ b/src/cpp/imgui.cpp @@ -1,7 +1,9 @@ #include "imgui.h" + #include #include #include +#include namespace py = pybind11; @@ -50,17 +52,106 @@ static int input_text_callback(ImGuiInputTextCallbackData* data) { } +void bind_imgui_structs(py::module& m); void bind_imgui_methods(py::module& m); void bind_imgui_enums(py::module& m); void bind_imgui(py::module& m) { auto imgui_module = m.def_submodule("imgui", "ImGui bindings"); + bind_imgui_structs(imgui_module); bind_imgui_methods(imgui_module); bind_imgui_enums(imgui_module); } // clang-format off + + +// clang-format off +void bind_imgui_structs(py::module& m) { + + // ImGuiIO + py::class_(m, "ImGuiIO") + // .def(py::init()) + .def_readwrite("DisplaySize" ,&ImGuiIO::DisplaySize ) + .def_readwrite("DeltaTime" ,&ImGuiIO::DeltaTime ) + .def_readwrite("IniSavingRate" ,&ImGuiIO::IniSavingRate ) + .def_readwrite("IniFilename" ,&ImGuiIO::IniFilename ) + .def_readwrite("MouseDoubleClickTime" ,&ImGuiIO::MouseDoubleClickTime ) + .def_readwrite("MouseDoubleClickMaxDist" ,&ImGuiIO::MouseDoubleClickMaxDist ) + .def_readwrite("MouseDragThreshold" ,&ImGuiIO::MouseDragThreshold ) + .def_property_readonly("KeyMap" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{ImGuiKey_COUNT, o.KeyMap, ob};}) + .def_readwrite("KeyRepeatDelay" ,&ImGuiIO::KeyRepeatDelay ) + .def_readwrite("KeyRepeatRate" ,&ImGuiIO::KeyRepeatRate ) + .def_readwrite("Fonts" ,&ImGuiIO::Fonts ) + .def_readwrite("FontGlobalScale" ,&ImGuiIO::FontGlobalScale ) + .def_readwrite("FontAllowUserScaling" ,&ImGuiIO::FontAllowUserScaling ) + .def_readwrite("FontDefault" ,&ImGuiIO::FontDefault ) + .def_readwrite("DisplayFramebufferScale" ,&ImGuiIO::DisplayFramebufferScale ) + .def_readwrite("MouseDrawCursor" ,&ImGuiIO::MouseDrawCursor ) + .def_readwrite("ConfigMacOSXBehaviors" ,&ImGuiIO::ConfigMacOSXBehaviors ) + .def_readwrite("ConfigInputTextCursorBlink" ,&ImGuiIO::ConfigInputTextCursorBlink ) + .def_readwrite("ConfigDragClickToInputText" ,&ImGuiIO::ConfigDragClickToInputText ) + .def_readwrite("ConfigWindowsResizeFromEdges" ,&ImGuiIO::ConfigWindowsResizeFromEdges ) + .def_readwrite("ConfigWindowsMoveFromTitleBarOnly" ,&ImGuiIO::ConfigWindowsMoveFromTitleBarOnly ) + .def_readwrite("ConfigMemoryCompactTimer" ,&ImGuiIO::ConfigMemoryCompactTimer ) + .def_readwrite("MousePos" ,&ImGuiIO::MousePos ) + .def_property_readonly("MouseDown" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{5, o.MouseDown , ob};}) + .def_readwrite("MouseWheel" ,&ImGuiIO::MouseWheel ) + .def_readwrite("MouseWheelH" ,&ImGuiIO::MouseWheelH ) + .def_readwrite("KeyCtrl" ,&ImGuiIO::KeyCtrl ) + .def_readwrite("KeyShift" ,&ImGuiIO::KeyShift ) + .def_readwrite("KeyAlt" ,&ImGuiIO::KeyAlt ) + .def_readwrite("KeySuper" ,&ImGuiIO::KeySuper ) + .def_property_readonly("KeysDown" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{512, o.KeysDown , ob};}) + .def_property_readonly("NavInputs" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{ImGuiNavInput_COUNT, o.NavInputs , ob};}) + .def_readwrite("WantCaptureMouse" ,&ImGuiIO::WantCaptureMouse ) + .def_readwrite("WantCaptureKeyboard" ,&ImGuiIO::WantCaptureKeyboard ) + .def_readwrite("WantTextInput" ,&ImGuiIO::WantTextInput ) + .def_readwrite("WantSetMousePos" ,&ImGuiIO::WantSetMousePos ) + .def_readwrite("WantSaveIniSettings" ,&ImGuiIO::WantSaveIniSettings ) + .def_readwrite("NavActive" ,&ImGuiIO::NavActive ) + .def_readwrite("NavVisible" ,&ImGuiIO::NavVisible ) + .def_readwrite("Framerate" ,&ImGuiIO::Framerate ) + .def_readwrite("MetricsRenderVertices" ,&ImGuiIO::MetricsRenderVertices ) + .def_readwrite("MetricsRenderIndices" ,&ImGuiIO::MetricsRenderIndices ) + .def_readwrite("MetricsRenderWindows" ,&ImGuiIO::MetricsRenderWindows ) + .def_readwrite("MetricsActiveWindows" ,&ImGuiIO::MetricsActiveWindows ) + .def_readwrite("MetricsActiveAllocations" ,&ImGuiIO::MetricsActiveAllocations ) + .def_readwrite("MouseDelta" ,&ImGuiIO::MouseDelta ) + .def_readwrite("WantCaptureMouseUnlessPopupClose" ,&ImGuiIO::WantCaptureMouseUnlessPopupClose ) + .def_readwrite("KeyMods" ,&ImGuiIO::KeyMods ) + .def_readwrite("KeyModsPrev" ,&ImGuiIO::KeyModsPrev ) + .def_readwrite("MousePosPrev" ,&ImGuiIO::MousePosPrev ) + .def_property_readonly("MouseClickedPos" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{5, o.MouseClickedPos, ob};}) + .def_property_readonly("MouseClickedTime" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{5, o.MouseClickedTime, ob};}) + .def_property_readonly("MouseClicked" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{5, o.MouseClicked, ob};}) + .def_property_readonly("MouseDoubleClicked" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{5, o.MouseDoubleClicked, ob};}) + .def_property_readonly("MouseClickedCount" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{5, o.MouseClickedCount, ob};}) + .def_property_readonly("MouseClickedLastCount" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{5, o.MouseClickedLastCount, ob};}) + .def_property_readonly("MouseReleased" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{5, o.MouseReleased, ob};}) + .def_property_readonly("MouseDownOwned" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{5, o.MouseDownOwned, ob};}) + .def_property_readonly("MouseDownOwnedUnlessPopupClose" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{5, o.MouseDownOwnedUnlessPopupClose, ob};}) + .def_property_readonly("MouseDownDuration" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{5, o.MouseDownDuration, ob};}) + .def_property_readonly("MouseDownDurationPrev" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{5, o.MouseDownDurationPrev, ob};}) + .def_property_readonly("MouseDragMaxDistanceAbs" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{5, o.MouseDragMaxDistanceAbs, ob};}) + .def_property_readonly("MouseDragMaxDistanceSqr" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{5, o.MouseDragMaxDistanceSqr, ob};}) + .def_property_readonly("KeysDownDuration" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{512, o.KeysDownDuration, ob};}) + .def_property_readonly("KeysDownDurationPrev" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{512, o.KeysDownDurationPrev, ob};}) + .def_property_readonly("NavInputsDownDuration" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{ImGuiNavInput_COUNT, o.NavInputsDownDuration, ob};}) + .def_property_readonly("NavInputsDownDurationPrev" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{ImGuiNavInput_COUNT, o.NavInputsDownDurationPrev, ob};}) + .def_readwrite("PenPressure" ,&ImGuiIO::PenPressure ) + .def_readwrite("AppFocusLost" ,&ImGuiIO::AppFocusLost ) + .def_readwrite("InputQueueSurrogate" ,&ImGuiIO::InputQueueSurrogate ) + .def_readwrite("InputQueueCharacters" ,&ImGuiIO::InputQueueCharacters ) + + ; +} + void bind_imgui_methods(py::module& m) { + + // Main + m.def("GetIO", &ImGui::GetIO, py::return_value_policy::reference); + // Windows m.def( "Begin", @@ -1512,7 +1603,7 @@ void bind_imgui_methods(py::module& m) { // Inputs Utilities: Keyboard m.def("GetKeyIndex", [](ImGuiKey imgui_key) { return ImGui::GetKeyIndex(imgui_key); }, py::arg("imgui_key")); m.def("IsKeyDown", [](ImGuiKey user_key_index) { return ImGui::IsKeyDown(user_key_index); }, py::arg("user_key_index")); - m.def("IsKeyPressed", [](ImGuiKey user_key_index) { return ImGui::IsKeyPressed(user_key_index); }, py::arg("user_key_index")); + m.def("IsKeyPressed", [](ImGuiKey user_key_index, bool repeat) { return ImGui::IsKeyPressed(user_key_index, repeat); }, py::arg("user_key_index"), py::arg("repeat")=true); m.def("IsKeyReleased", [](ImGuiKey user_key_index) { return ImGui::IsKeyReleased(user_key_index); }, py::arg("user_key_index")); m.def( "GetKeyPressedAmount", From f747261db3522b823733f5a8c308a9af836a248c Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Mon, 9 Oct 2023 23:58:42 -0700 Subject: [PATCH 36/88] add get/set background color --- src/cpp/core.cpp | 5 +++++ src/polyscope/core.py | 7 +++++++ test/polyscope_test.py | 4 ++++ 3 files changed, 16 insertions(+) diff --git a/src/cpp/core.cpp b/src/cpp/core.cpp index 6ba631e..0b6184a 100644 --- a/src/cpp/core.cpp +++ b/src/cpp/core.cpp @@ -149,6 +149,11 @@ PYBIND11_MODULE(polyscope_bindings, m) { m.def("get_window_resizable", &ps::view::getWindowResizable); m.def("set_view_from_json", ps::view::setViewFromJson); m.def("get_view_as_json", ps::view::getViewAsJson); + m.def("set_background_color", [](glm::vec4 c) { for(int i = 0; i < 4; i++) ps::view::bgColor[i] = c[i]; }); + m.def("get_background_color", []() { return glm::vec4{ + ps::view::bgColor[0], ps::view::bgColor[1], ps::view::bgColor[2], ps::view::bgColor[3] + }; + }); // === "Advanced" UI management m.def("build_polyscope_gui", &ps::buildPolyscopeGui); diff --git a/src/polyscope/core.py b/src/polyscope/core.py index ed2777b..1aff29f 100644 --- a/src/polyscope/core.py +++ b/src/polyscope/core.py @@ -173,6 +173,13 @@ def set_view_from_json(json_str, fly_to=False): def get_view_as_json(): return psb.get_view_as_json() +def set_background_color(c): + if len(c) == 3: c = (c[0], c[1], c[2], 1.0) + psb.set_background_color(glm4(c)) + +def get_background_color(): + return psb.get_background_color() + def get_view_camera_parameters(): return CameraParameters(instance=psb.get_view_camera_parameters()) diff --git a/test/polyscope_test.py b/test/polyscope_test.py index cc48bbb..21e8a0f 100644 --- a/test/polyscope_test.py +++ b/test/polyscope_test.py @@ -56,6 +56,10 @@ def test_options(self): ps.set_invoke_user_callback_for_nested_show(False) ps.set_give_focus_on_show(True) + ps.set_background_color((0.7, 0.8, 0.9)) + ps.set_background_color((0.7, 0.8, 0.9, 0.9)) + ps.get_background_color() + ps.show(3) def test_callbacks(self): From 49ea6e671b76b2061b277d750eab05987a0b59dc Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Tue, 10 Oct 2023 15:52:49 -0700 Subject: [PATCH 37/88] finish bindings and tests for raw render image --- src/cpp/floating_quantities.cpp | 5 ++++ src/cpp/utils.h | 1 + src/polyscope/floating_quantities.py | 25 ++++++++++++++++ src/polyscope/structure.py | 10 ++++++- test/polyscope_test.py | 44 +++++++++++++++++++++++++++- 5 files changed, 83 insertions(+), 2 deletions(-) diff --git a/src/cpp/floating_quantities.cpp b/src/cpp/floating_quantities.cpp index de9a6aa..b9d015e 100644 --- a/src/cpp/floating_quantities.cpp +++ b/src/cpp/floating_quantities.cpp @@ -60,6 +60,9 @@ void bind_floating_quantities(py::module& m) { auto qRawColorRenderImage = bindColorQuantity(m, "RawColorRenderImageQuantity"); qRawColorRenderImage.def("set_transparency", &ps::RawColorRenderImageQuantity::setTransparency, "Set transparency"); + auto qRawColorAlphaRenderImage = bindColorQuantity(m, "RawColorAlphaRenderImageQuantity"); + qRawColorAlphaRenderImage.def("set_transparency", &ps::RawColorAlphaRenderImageQuantity::setTransparency, "Set transparency"); + // global / free-floating adders m.def("add_depth_render_image_quantity", &ps::addDepthRenderImageQuantity, py::return_value_policy::reference); @@ -69,6 +72,8 @@ void bind_floating_quantities(py::module& m) { py::return_value_policy::reference); m.def("add_raw_color_render_image_quantity", &ps::addRawColorRenderImageQuantity, py::return_value_policy::reference); + m.def("add_raw_color_alpha_render_image_quantity", &ps::addRawColorAlphaRenderImageQuantity, + py::return_value_policy::reference); } diff --git a/src/cpp/utils.h b/src/cpp/utils.h index 5cf6585..9a5f348 100644 --- a/src/cpp/utils.h +++ b/src/cpp/utils.h @@ -142,6 +142,7 @@ py::class_ bindStructure(py::module& m, std::string name) { .def("add_color_render_image_quantity", &StructureT::template addColorRenderImageQuantity, py::arg("name"), py::arg("dimX"), py::arg("dimY"), py::arg("depthData"), py::arg("normalData"), py::arg("colorData"), py::arg("imageOrigin")=ps::ImageOrigin::UpperLeft, py::return_value_policy::reference) .def("add_scalar_render_image_quantity", &StructureT::template addScalarRenderImageQuantity, py::arg("name"), py::arg("dimX"), py::arg("dimY"), py::arg("depthData"), py::arg("normalData"), py::arg("scalarData"), py::arg("imageOrigin")=ps::ImageOrigin::UpperLeft, py::arg("type")=ps::DataType::STANDARD, py::return_value_policy::reference) .def("add_raw_color_render_image_quantity", &StructureT::template addRawColorRenderImageQuantity, py::arg("name"), py::arg("dimX"), py::arg("dimY"), py::arg("depthData"), py::arg("colorData"), py::arg("imageOrigin")=ps::ImageOrigin::UpperLeft, py::return_value_policy::reference) + .def("add_raw_color_alpha_render_image_quantity", &StructureT::template addRawColorAlphaRenderImageQuantity, py::arg("name"), py::arg("dimX"), py::arg("dimY"), py::arg("depthData"), py::arg("colorData"), py::arg("imageOrigin")=ps::ImageOrigin::UpperLeft, py::return_value_policy::reference) ; diff --git a/src/polyscope/floating_quantities.py b/src/polyscope/floating_quantities.py index ee314b4..f398471 100644 --- a/src/polyscope/floating_quantities.py +++ b/src/polyscope/floating_quantities.py @@ -181,6 +181,31 @@ def add_raw_color_render_image_quantity(name, depth_values, color_values, image_ str_to_image_origin(image_origin)) + # process and act on additional arguments + # note: each step modifies the option_args dict and removes processed args + process_quantity_args(struct_ref, q, option_args) + process_color_args(struct_ref, q, option_args) + process_render_image_args(struct_ref, q, option_args) + check_all_args_processed(struct_ref, q, option_args) + +def add_raw_color_alpha_render_image_quantity(name, depth_values, color_values, image_origin="upper_left", struct_ref=None, **option_args): + + struct_instance_ref = _resolve_floating_struct_instance(struct_ref) + + check_is_scalar_image(depth_values) + check_is_image4(color_values) + check_image_dims_compatible([depth_values, color_values]) + dimY = depth_values.shape[0] + dimX = depth_values.shape[1] + + depth_values_flat = depth_values.flatten() + color_values_flat = color_values.reshape(-1,4) + + q = struct_instance_ref.add_raw_color_alpha_render_image_quantity(name, dimX, dimY, + depth_values_flat, color_values_flat, + str_to_image_origin(image_origin)) + + # process and act on additional arguments # note: each step modifies the option_args dict and removes processed args process_quantity_args(struct_ref, q, option_args) diff --git a/src/polyscope/structure.py b/src/polyscope/structure.py index 52bf508..dbab314 100644 --- a/src/polyscope/structure.py +++ b/src/polyscope/structure.py @@ -1,6 +1,6 @@ import polyscope_bindings as psb -from polyscope.floating_quantities import add_scalar_image_quantity, add_color_image_quantity, add_color_alpha_image_quantity, add_depth_render_image_quantity, add_color_render_image_quantity, add_scalar_render_image_quantity, add_raw_color_render_image_quantity +from polyscope.floating_quantities import add_scalar_image_quantity, add_color_image_quantity, add_color_alpha_image_quantity, add_depth_render_image_quantity, add_color_render_image_quantity, add_scalar_render_image_quantity, add_raw_color_render_image_quantity, add_raw_color_alpha_render_image_quantity from polyscope.managed_buffer import ManagedBuffer # Base class for common properties and methods on structures @@ -188,3 +188,11 @@ def add_raw_color_render_image_quantity(self, name, depth_values, color_values, # Call the general version (this abstraction allows us to handle the free-floating case via the same code) return add_raw_color_render_image_quantity(name, depth_values, color_values, image_origin=image_origin, struct_ref=self, **option_args) + def add_raw_color_alpha_render_image_quantity(self, name, depth_values, color_values, image_origin="upper_left", **option_args): + """ + Add a "floating" render image quantity to the structure + """ + + # Call the general version (this abstraction allows us to handle the free-floating case via the same code) + return add_raw_color_alpha_render_image_quantity(name, depth_values, color_values, image_origin=image_origin, struct_ref=self, **option_args) + diff --git a/test/polyscope_test.py b/test/polyscope_test.py index 21e8a0f..83d8d4f 100644 --- a/test/polyscope_test.py +++ b/test/polyscope_test.py @@ -1766,7 +1766,49 @@ def test_floating_scalar_render_images(self): cam.add_scalar_render_image_quantity("render_img2", depths, normals, scalars, enabled=True, image_origin='lower_left', material='wax', transparency=0.7) # true floating adder - ps.add_scalar_render_image_quantity("render_img3", depths, normals, scalars, enabled=True, image_origin='lower_left', material='wax', transparency=0.7, ) + ps.add_scalar_render_image_quantity("render_img3", depths, normals, scalars, enabled=True, image_origin='lower_left', material='wax', transparency=0.7) + + ps.show(3) + ps.remove_all_structures() + + def test_floating_raw_color_render_images(self): + + # technically these can be added to any structure, but we will test them here + + cam = ps.register_camera_view("cam1", self.generate_parameters()) + + dimX = 300 + dimY = 600 + + depths = np.zeros((dimX, dimY)) + colors = np.ones((dimX, dimY, 3)) + + cam.add_raw_color_render_image_quantity("render_img", depths, colors) + cam.add_raw_color_render_image_quantity("render_img2", depths, colors, enabled=True, image_origin='lower_left', transparency=0.7) + + # true floating adder + ps.add_raw_color_render_image_quantity("render_img3", depths, colors, enabled=True, image_origin='lower_left', transparency=0.7) + + ps.show(3) + ps.remove_all_structures() + + def test_floating_raw_color_alpha_render_images(self): + + # technically these can be added to any structure, but we will test them here + + cam = ps.register_camera_view("cam1", self.generate_parameters()) + + dimX = 300 + dimY = 600 + + depths = np.zeros((dimX, dimY)) + colors = np.ones((dimX, dimY, 4)) + + cam.add_raw_color_alpha_render_image_quantity("render_img", depths, colors) + cam.add_raw_color_alpha_render_image_quantity("render_img2", depths, colors, enabled=True, image_origin='lower_left', transparency=0.7) + + # true floating adder + ps.add_raw_color_alpha_render_image_quantity("render_img3", depths, colors, enabled=True, image_origin='lower_left', transparency=0.7) ps.show(3) ps.remove_all_structures() From 691b06734ff63799d95cc3b6524587bb882d37ae Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Wed, 11 Oct 2023 16:56:44 -0700 Subject: [PATCH 38/88] bump dep --- deps/polyscope | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/polyscope b/deps/polyscope index 4449c87..6ab4b07 160000 --- a/deps/polyscope +++ b/deps/polyscope @@ -1 +1 @@ -Subproject commit 4449c8739f971779aea562eeee54d65fc92d3feb +Subproject commit 6ab4b071df59df2f73b875c04f21ce58024873d6 From 2f2542427ddc086f83a1060a8c01730cd7714171 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Wed, 11 Oct 2023 16:56:59 -0700 Subject: [PATCH 39/88] add bindings --- src/cpp/core.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/cpp/core.cpp b/src/cpp/core.cpp index 0b6184a..c3dd009 100644 --- a/src/cpp/core.cpp +++ b/src/cpp/core.cpp @@ -106,6 +106,8 @@ PYBIND11_MODULE(polyscope_bindings, m) { m.def("set_max_fps", [](int x) { ps::options::maxFPS = x; }); m.def("set_enable_vsync", [](bool x) { ps::options::enableVSync = x; }); m.def("set_use_prefs_file", [](bool x) { ps::options::usePrefsFile = x; }); + m.def("request_redraw", []() { ps::requestRedraw(); }); + m.def("get_redraw_requested", []() { return ps::redrawRequested(); }); m.def("set_always_redraw", [](bool x) { ps::options::alwaysRedraw = x; }); m.def("set_enable_render_error_checks", [](bool x) { ps::options::enableRenderErrorChecks = x; }); m.def("set_autocenter_structures", [](bool x) { ps::options::autocenterStructures = x; }); @@ -235,6 +237,10 @@ PYBIND11_MODULE(polyscope_bindings, m) { // === Rendering m.def("set_SSAA_factor", [](int n) { ps::options::ssaaFactor = n; }); + // === Low-level internals access + // (warning, 'advanced' users only, may change) + m.def("get_final_scene_color_texture_native_handle", []() { ps::render::engine->getFinalSceneColorTexture().getNativeBufferID(); }); + // === Slice planes py::class_(m, "SlicePlane") .def(py::init()) From 9042ee1c3397a9515daf68865cb38f3ecceecbeb Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Wed, 11 Oct 2023 16:57:05 -0700 Subject: [PATCH 40/88] add bindings --- src/polyscope/core.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/polyscope/core.py b/src/polyscope/core.py index 1aff29f..7b6aaad 100644 --- a/src/polyscope/core.py +++ b/src/polyscope/core.py @@ -76,6 +76,12 @@ def set_enable_vsync(b): def set_use_prefs_file(v): psb.set_use_prefs_file(v) +def request_redraw(): + psb.request_redraw() + +def get_redraw_requested(): + return psb.get_redraw_requested() + def set_always_redraw(v): psb.set_always_redraw(v) @@ -185,7 +191,7 @@ def get_view_camera_parameters(): def set_view_camera_parameters(params): if not isinstance(params, CameraParameters): raise ValueError("must pass CameraParameters") - set_view_camera_parameters(params.instance) + psb.set_view_camera_parameters(params.instance) def get_view_buffer_resolution(): return CameraParameters(instance=psb.get_view_camera_parameters()) @@ -244,6 +250,11 @@ def set_transparency_render_passes(n): ## Rendering def set_SSAA_factor(n): psb.set_SSAA_factor(n) + +## Low-level internals access +# (warning, 'advanced' users only, may change) +def get_final_scene_color_texture_native_handle(): + return psb.get_final_scene_color_texture_native_handle() ## Slice planes From 3f867089206c80a5b0b9a9e16836768c69c3563c Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Wed, 11 Oct 2023 16:57:23 -0700 Subject: [PATCH 41/88] better error messages and checks for buffers --- src/polyscope/device_interop.py | 40 ++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/src/polyscope/device_interop.py b/src/polyscope/device_interop.py index a02b675..49d3aa7 100644 --- a/src/polyscope/device_interop.py +++ b/src/polyscope/device_interop.py @@ -1,6 +1,6 @@ import polyscope_bindings as psb -import os +import os, sys import numpy as np @@ -128,7 +128,7 @@ def map(self): def unmap(self): - if self.cuda_buffer_ptr is None: + if not hasattr(self, 'cuda_buffer_ptr') or self.cuda_buffer_ptr is None: return check_cudart_err(cudart.cudaGraphicsUnmapResources(1, self.resource_handle, None)) @@ -154,7 +154,8 @@ def set_data_from_array(self, arr, expected_shape=None, expected_dtype=None): # number of bytes as the destination. This is just lazily saving us # from repeating the math, and also directly validates the copy we # are about to do below. - raise ValueError(f"Mapped buffer write has wrong size, expected {cupy_arr.nbytes} bytes but got {self.cuda_buffer_size} bytes. Could it be the wrong size/shape or wrong dtype?") + # raise ValueError(f"Mapped buffer write has wrong size, expected {cupy_arr.nbytes} bytes but got {self.cuda_buffer_size} bytes. Could it be the wrong size/shape or wrong dtype?") + pass # perform the actualy copy @@ -234,7 +235,7 @@ def map(self): def unmap(self): - if self.cuda_buffer_array is None: + if not hasattr(self, 'cuda_buffer_array') or self.cuda_buffer_array is None: return check_cudart_err(cudart.cudaGraphicsUnmapResources(1, self.resource_handle, None)) @@ -260,26 +261,39 @@ def set_data_from_array(self, arr, expected_shape=None, expected_dtype=None): if self.buffer_type == psb.DeviceBufferType.texture2d: - - buff_width = extent.width - buff_height = extent.height - buff_bytes_per = (desc.x + desc.y + desc.z + desc.w) // 8 - + + dst_buff_width = extent.width + dst_buff_height = extent.height + dst_buff_width_pad = 0 + dst_buff_bytes_per = (desc.x + desc.y + desc.z + desc.w) // 8 + + dst_n_cmp = (1 if desc.x > 0 else 0) + \ + (1 if desc.y > 0 else 0) + \ + (1 if desc.z > 0 else 0) + \ + (1 if desc.w > 0 else 0) + + dst_n_elems = dst_buff_width*dst_buff_height*dst_n_cmp + # do some shape & type checks - expected_bytes = buff_width * buff_height * buff_bytes_per + if cupy_arr.size != dst_n_elems: + raise ValueError(f"Mapped buffer write has wrong size, destination buffer has {dst_n_elems} elements, but source buffer has {cupy_arr.size}.") + + expected_bytes = dst_buff_width * dst_buff_height * dst_buff_bytes_per if cupy_arr.nbytes != expected_bytes: # if cupy_arr has the right size/dtype, it should have exactly the same # number of bytes as the destination. This is just lazily saving us # from repeating the math, and also directly validates the copy we # are about to do below. raise ValueError(f"Mapped buffer write has wrong size, expected {cupy_arr.nbytes} bytes but got {expected_bytes} bytes. Could it be the wrong size/shape or wrong dtype?") - - + # perform the actual copy check_cudart_err( cuda.cudart.cudaMemcpy2DToArray( self.cuda_buffer_array, 0, 0, - cupy_arr.data.ptr, buff_width * buff_bytes_per, buff_width * buff_bytes_per, buff_height, + cupy_arr.data.ptr, + dst_buff_width * dst_buff_bytes_per, + dst_buff_width * dst_buff_bytes_per, + dst_buff_height, cudart.cudaMemcpyKind.cudaMemcpyDeviceToDevice ) ) From 398fbc3e01c593b256ec74a428c3c3e25572f66a Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Wed, 11 Oct 2023 16:57:35 -0700 Subject: [PATCH 42/88] add additional low-level getters for native buffer access --- src/polyscope/managed_buffer.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/polyscope/managed_buffer.py b/src/polyscope/managed_buffer.py index 42d8f5d..4123c24 100644 --- a/src/polyscope/managed_buffer.py +++ b/src/polyscope/managed_buffer.py @@ -110,3 +110,23 @@ def get_mapped_buffer_CUDAOpenGL(self): def release_mapped_buffer_CUDAOpenGL(self): if self.uniqueID in _mapped_buffer_cache_CUDAOpenGL: del _mapped_buffer_cache_CUDAOpenGL[self.uniqueID] + + def get_texture_native_id(self): + if self.device_buffer_type == psb.DeviceBufferType.attribute: + raise ValueError("buffer is not a texture (perhaps try the 'attribute' variant?)") + + return self.bound_buffer.get_native_render_texture_buffer_ID() + + def get_attribute_native_id(self): + if self.device_buffer_type != psb.DeviceBufferType.attribute: + raise ValueError("buffer is not an attribute (perhaps try the 'texture' variant?)") + + return self.bound_buffer.get_native_render_attribute_buffer_ID() + + + def mark_device_buffer_updated(self): + + if self.device_buffer_type == psb.DeviceBufferType.attribute: + self.bound_buffer.mark_render_attribute_buffer_updated() + else: # texture + self.bound_buffer.mark_render_texture_buffer_updated() From e8377e6785e748d79d7e65969a97d590baeff88b Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Wed, 11 Oct 2023 16:57:40 -0700 Subject: [PATCH 43/88] fix error messages --- src/polyscope/structure.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/polyscope/structure.py b/src/polyscope/structure.py index dbab314..f1f24ab 100644 --- a/src/polyscope/structure.py +++ b/src/polyscope/structure.py @@ -62,7 +62,7 @@ def get_buffer(self, buffer_name): present, buffer_type = self.bound_instance.has_buffer_type(buffer_name) - if not present: raise ValueError("structure has no buffer named " + name) + if not present: raise ValueError("structure has no buffer named " + buffer_name) return { psb.ManagedBufferType.Float : lambda n,t : ManagedBuffer(self.bound_instance.get_buffer_Float (n), t), @@ -89,7 +89,7 @@ def get_quantity_buffer(self, quantity_name, buffer_name): # give a more informative error if this was called on the global floating quantity, becuase it is particularly easy for users to get confused and call this function at the global scope rather than on a quantity raise ValueError("Quantity has no buffer named " + name + ". NOTE: calling polyscope.get_quantity_buffer() is for global floating quantities only, call structure.get_quantity_buffer() to get buffers for a quantity added to some structure.") else: - raise ValueError("quantity has no buffer named " + name) + raise ValueError(f"quantity {quantity_name} has no buffer named {buffer_name}") return { psb.ManagedBufferType.Float : lambda q,n,t : ManagedBuffer(self.bound_instance.get_quantity_buffer_Float (q,n), t), From d3ab204c9bfc20ec91df5c3a584fddad5a8d6c59 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Wed, 11 Oct 2023 16:57:50 -0700 Subject: [PATCH 44/88] add a few tests --- test/polyscope_test.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/polyscope_test.py b/test/polyscope_test.py index 83d8d4f..5ea8fd0 100644 --- a/test/polyscope_test.py +++ b/test/polyscope_test.py @@ -50,6 +50,10 @@ def test_options(self): ps.set_autocenter_structures(False) ps.set_autoscale_structures(False) + ps.request_redraw() + self.assertTrue(ps.get_redraw_requested()) + ps.set_always_redraw(False) + ps.set_build_gui(True) ps.set_render_scene(True) ps.set_open_imgui_window_for_user_callback(True) @@ -59,6 +63,8 @@ def test_options(self): ps.set_background_color((0.7, 0.8, 0.9)) ps.set_background_color((0.7, 0.8, 0.9, 0.9)) ps.get_background_color() + + ps.get_final_scene_color_texture_native_handle() ps.show(3) From e4868dbcebac757c3c622ae8534d2843bfa56bc4 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Fri, 13 Oct 2023 18:01:29 -0700 Subject: [PATCH 45/88] update dep --- deps/polyscope | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/polyscope b/deps/polyscope index 6ab4b07..880f297 160000 --- a/deps/polyscope +++ b/deps/polyscope @@ -1 +1 @@ -Subproject commit 6ab4b071df59df2f73b875c04f21ce58024873d6 +Subproject commit 880f2975b0703909d23ffaf9ba7cb7f958f205fa From 664b1f019e3a12fe43d3e5cef3bc42f2cc4d205e Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Fri, 13 Oct 2023 18:01:55 -0700 Subject: [PATCH 46/88] overhaul device interop to allow user-defined cuda bindings --- src/cpp/managed_buffer.cpp | 23 +- src/polyscope/__init__.py | 1 + src/polyscope/device_interop.py | 408 ++++++++++++++++++++------------ src/polyscope/managed_buffer.py | 9 +- 4 files changed, 287 insertions(+), 154 deletions(-) diff --git a/src/cpp/managed_buffer.cpp b/src/cpp/managed_buffer.cpp index 7555d03..21ade88 100644 --- a/src/cpp/managed_buffer.cpp +++ b/src/cpp/managed_buffer.cpp @@ -32,12 +32,33 @@ py::class_> bind_managed_buffer_T(py::module& m, ps .def("get_value", overload_cast_()(&ps::render::ManagedBuffer::getValue)) .def("get_value", overload_cast_()(&ps::render::ManagedBuffer::getValue)) .def("mark_host_buffer_updated", &ps::render::ManagedBuffer::markHostBufferUpdated) + .def("get_device_buffer_size_in_bytes", [](ps::render::ManagedBuffer& s) { + // NOTE: this could cause the underlying device buffer to be allocatred if it wasn't already + if (s.getDeviceBufferType() == polyscope::DeviceBufferType::Attribute) { + return s.getRenderAttributeBuffer()->getDataSizeInBytes(); + } else { + return s.getRenderTextureBuffer()->getSizeInBytes(); + } + }) + .def("get_device_buffer_element_size_in_bytes", [](ps::render::ManagedBuffer& s) { + // NOTE: this could cause the underlying device buffer to be allocatred if it wasn't already + if (s.getDeviceBufferType() == polyscope::DeviceBufferType::Attribute) { + std::shared_ptr buff = s.getRenderAttributeBuffer(); + return polyscope::sizeInBytes(buff->getType()) * buff->getArrayCount(); + } else { + std::shared_ptr buff = s.getRenderTextureBuffer(); + return polyscope::sizeInBytes(buff->getFormat()); + } + }) .def("get_native_render_attribute_buffer_ID", [](ps::render::ManagedBuffer& s) { return s.getRenderAttributeBuffer()->getNativeBufferID(); }) .def("mark_render_attribute_buffer_updated", &ps::render::ManagedBuffer::markRenderAttributeBufferUpdated) .def("get_native_render_texture_buffer_ID", [](ps::render::ManagedBuffer& s) { return s.getRenderTextureBuffer()->getNativeBufferID(); }) - .def("mark_render_texture_buffer_updated", &ps::render::ManagedBuffer::markRenderTextureBufferUpdated); + .def("mark_render_texture_buffer_updated", &ps::render::ManagedBuffer::markRenderTextureBufferUpdated) + + + ; } // clang-format off diff --git a/src/polyscope/__init__.py b/src/polyscope/__init__.py index 1b9403a..5035fed 100644 --- a/src/polyscope/__init__.py +++ b/src/polyscope/__init__.py @@ -4,6 +4,7 @@ from polyscope.floating_quantities import * from polyscope.managed_buffer import * from polyscope.implicit_helpers import * +from polyscope.device_interop import * from polyscope.surface_mesh import * from polyscope.point_cloud import * diff --git a/src/polyscope/device_interop.py b/src/polyscope/device_interop.py index 49d3aa7..57bd318 100644 --- a/src/polyscope/device_interop.py +++ b/src/polyscope/device_interop.py @@ -1,86 +1,218 @@ import polyscope_bindings as psb import os, sys +from functools import partial import numpy as np from polyscope.core import get_render_engine_backend_name +device_interop_funcs = None -# Dependencies which are needed for interop but otherwise optional for polyscope +############################################################################# +### Default CUDA implementation of interop functions +############################################################################# + +# Device interoperability requires directly copying data from user's arrays +# on the GPU into Polyscope's openGL buffers. This cannot be done directly +# through openGL, it requires a memcopy from device compute library (generally CUDA). +# +# Unfortunately, CUDA is is a nontrivial dependency, and many things could go wrong +# with installation and mismatched versions. To give more flexibility, all of the +# function calls related to the CUDA memcpy are abstracted as a dictionary of callbacks; +# as long as this dictionary is populated, Polyscope can call the functions to access +# user arrays and do the copies. +# +# There are two options for users to get the needed functions # -# Try both of these imports, but fail silently if they don't work (we will try -# again and print an informative error below only if a relevant function is called) -try: - import cuda - from cuda import cudart - import cupy -except ImportError: - _has_cuda_libs = False -else: - _has_cuda_libs = True +# - [Default Case] The python packages `cuda` and `cupy` can be installed as additional +# optional dependencies. They offer the necessary CUDA functions. This is the default +# path if device interop functions are used. The result is that any array type which +# implements __cuda_array_interface__ or __dlpack__ (aka almost all libraries) +# can be automatically read from. +# +# - [Custom Case] In some cases, the `cuda` and `cupy` packages may not install correctly, +# or the user's own codebase may already have its own preferred bindings to cuda functions. +# in this case the user can call set_device_interop_funcs() once, and pass a dictionary +# with a handful of callbacks to do the necessary mapping and copying. See below for +# the meaning of these callbacks. + +def ensure_device_interop_funcs_resolve(): + check_device_module_availibility() + if device_interop_funcs is not None: + return + resolve_default_device_interop_funcs() + +def set_device_interop_funcs(func_dict): + global device_interop_funcs + device_interop_funcs = func_dict + +def resolve_default_device_interop_funcs(): + + # Try both of these imports, but fail silently if they don't work (we will try + # again and print an informative error below only if a relevant function is called) + try: + import cuda + from cuda import cudart + import cupy + except ImportError: + raise ImportError('This Polyscope functionality requires cuda bindings to be installed. Please install the packages `cuda` and `cupy`. Try `python -m pip install cuda-python cupy`. See https://nvidia.github.io/cuda-python/ & https://cupy.dev/.') + + + # TODO is it possible to implement this without relying on exceptions? + ''' + def is_dlpack(obj): + return hasattr(obj, '__dlpack__') and hasattr(obj, '__dlpack_device__') + ''' + + def is_cuda_array_interface(obj): + return hasattr(obj, '__cuda_array_interface__') + + + def format_cudart_err(err): + return ( + f"{cudart.cudaGetErrorName(err)[1].decode('utf-8')}({int(err)}): " + f"{cudart.cudaGetErrorString(err)[1].decode('utf-8')}" + ) -# This seems absurd! These constants are needed for cuda.cudart calls, but there is apparently no canonical way to get them without introducing an additional dependency. -# See discussion https://discourse.panda3d.org/t/allowing-torch-tensorflow-to-directly-access-rendered-image-in-gpu/29119/3 -# As a workaround, we hardcord their constant values, fetched from https://cvs.khronos.org/svn/repos/ogl/trunk/doc/registry/public/api/GL/glcorearb.h -_CONSTANT_GL_TEXTURE_2D = int('0DE1',16) -_CONSTANT_GL_TEXTURE_3D = int('806F',16) -## Experimental things + # helper function: check errors + def check_cudart_err(args): + if isinstance(args, tuple): + assert len(args) >= 1 + err = args[0] + if len(args) == 1: + ret = None + elif len(args) == 2: + ret = args[1] + else: + ret = args[1:] + else: + err = args + ret = None -def check_device_module_availibility(): + assert isinstance(err, cudart.cudaError_t), type(err) + if err != cudart.cudaError_t.cudaSuccess: + raise RuntimeError(format_cudart_err(err)) - supported_backends = ["openGL3_glfw"] - if get_render_engine_backend_name() not in supported_backends: - raise ValueError(f"This Polyscope functionality is not supported by the current rendering backend ({get_render_engine_backend_name()}. Supported backends: {','.join(supported_backends)}.") + return ret + + # helper function: dispatch to one of the kinds of objects that we can read from + def get_array_from_unknown_data(arr): - if not _has_cuda_libs: - raise ImportError('This Polyscope functionality requires cuda bindings to be installed. Please install the packages `cuda` and `cupy`. https://nvidia.github.io/cuda-python/ & https://cupy.dev/') + # __cuda_array_interface__ + if is_cuda_array_interface(arr): + cupy_arr = cupy.ascontiguousarray(cupy.asarray(arr)) + else: + # __dlpack__ + # (I can't figure out any way to check this except try-catch) + try: + cupy_arr = cupy.ascontiguousarray(cupy.from_dlpack(arr)) + except ValueError: + pass + + raise ValueError("Cannot read from device data object. Must be a _dlpack_ array or implement the __cuda_array_interface__.") + + shape = cupy_arr.shape + dtype = cupy_arr.dtype + n_bytes = cupy_arr.nbytes + + return cupy_arr.data.ptr, shape, dtype, n_bytes + + def map_resource_and_get_array(handle): + check_cudart_err(cudart.cudaGraphicsMapResources(1, handle, None)), + return check_cudart_err(cudart.cudaGraphicsSubResourceGetMappedArray(handle, 0, 0)) + + func_dict = { + + # returns tuple `(desc, extent, flags)`, as in cudaArrayGetInfo() + # this function is optional, and only used for sanity checks it can be left undefined + 'get_array_info' : lambda array: + check_cudart_err( + cudart.cudaArrayGetInfo(array) + ), -# TODO is it possible to implement this without relying on exceptions? -''' -def is_dlpack(obj): - return hasattr(obj, '__dlpack__') and hasattr(obj, '__dlpack_device__') -''' + # as cudaGraphicsUnmapResources(1, handle, None) + 'unmap_resource' : lambda handle : + check_cudart_err(cudart.cudaGraphicsUnmapResources(1, handle, None)), -def is_cuda_array_interface(obj): - return hasattr(obj, '__cuda_array_interface__') + # returns a registered handle + # as cudaGraphicsGLRegisterBuffer() + 'register_gl_buffer' : lambda native_id : + check_cudart_err(cudart.cudaGraphicsGLRegisterBuffer( + native_id, + cudart.cudaGraphicsRegisterFlags.cudaGraphicsRegisterFlagsWriteDiscard + )), + -def format_cudart_err(err): - return ( - f"{cudart.cudaGetErrorName(err)[1].decode('utf-8')}({int(err)}): " - f"{cudart.cudaGetErrorString(err)[1].decode('utf-8')}" - ) + # returns a registered handle + # as in cudaGraphicsGLRegisterImage () + 'register_gl_image_2d' : lambda native_id : + check_cudart_err(cudart.cudaGraphicsGLRegisterImage( + native_id, + _CONSTANT_GL_TEXTURE_2D, + cudart.cudaGraphicsRegisterFlags.cudaGraphicsRegisterFlagsWriteDiscard + )), + + # as in cudaGraphicsUnregisterResource() + 'unregister_resource' : lambda handle : + check_cudart_err(cudart.cudaGraphicsUnregisterResource(handle)), -def check_cudart_err(args): - if isinstance(args, tuple): - assert len(args) >= 1 - err = args[0] - if len(args) == 1: - ret = None - elif len(args) == 2: - ret = args[1] - else: - ret = args[1:] - else: - err = args - ret = None - assert isinstance(err, cudart.cudaError_t), type(err) - if err != cudart.cudaError_t.cudaSuccess: - raise RuntimeError(format_cudart_err(err)) + # returns array + # as in cudaGraphicsMapResources() + cudaGraphicsSubResourceGetMappedArray() + 'map_resource_and_get_array' : map_resource_and_get_array + , + + + # returns a tuple (arr_ptr, shape, dtype, nbytes) + # The last three entries are all optional and can be None. If given they will be used for additional sanity checks. + 'get_array_ptr' : lambda input_array: + get_array_from_unknown_data(input_array), + + + # as in cudaMemcpy2DToArray + 'memcpy_2d' : lambda dst_ptr, src_ptr, width, height : + check_cudart_err(cuda.cudart.cudaMemcpy2DToArray( + dst_ptr, 0, 0, src_ptr, width, width, height, cudart.cudaMemcpyKind.cudaMemcpyDeviceToDevice + )), + + } + + set_device_interop_funcs(func_dict) + + +# This seems absurd! These constants are needed for cuda.cudart calls, but there is apparently no canonical way to get them without introducing an additional dependency. +# See discussion https://discourse.panda3d.org/t/allowing-torch-tensorflow-to-directly-access-rendered-image-in-gpu/29119/3 +# As a workaround, we hardcord their constant values, fetched from https://cvs.khronos.org/svn/repos/ogl/trunk/doc/registry/public/api/GL/glcorearb.h +_CONSTANT_GL_TEXTURE_2D = int('0DE1',16) +_CONSTANT_GL_TEXTURE_3D = int('806F',16) + + + +############################################################################# +### Mapped buffer interop interface +############################################################################# + +def check_device_module_availibility(): + + supported_backends = ["openGL3_glfw"] + if get_render_engine_backend_name() not in supported_backends: + raise ValueError(f"This Polyscope functionality is not supported by the current rendering backend ({get_render_engine_backend_name()}. Supported backends: {','.join(supported_backends)}.") - return ret class CUDAOpenGLMappedAttributeBuffer: # Roughly based on this, see for more goodies: https://gist.github.com/keckj/e37d312128eac8c5fca790ce1e7fc437 def __init__(self, gl_attribute_native_id, buffer_type): - check_device_module_availibility() + # FIXME TODO + raise NotImplementedError("these calls need to be updated to use the new dictionary setup") + + ensure_device_interop_funcs_resolve() self.gl_attribute_native_id = gl_attribute_native_id self.buffer_type = buffer_type @@ -97,9 +229,7 @@ def __init__(self, gl_attribute_native_id, buffer_type): self.resource_handle = check_cudart_err( cudart.cudaGraphicsGLRegisterBuffer( self.gl_attribute_native_id, - # NOTE: setting write-only flag cudart.cudaGraphicsRegisterFlags.cudaGraphicsRegisterFlagsWriteDiscard - # cudart.cudaGraphicsRegisterFlags.cudaGraphicsRegisterFlagsNone ) ) @@ -119,7 +249,8 @@ def map(self): if self.cuda_buffer_ptr is not None: return - check_cudart_err(cudart.cudaGraphicsMapResources(1, self.resource_handle, None)) + device_interop_funcs['cudaGraphicsMapResources'](self.resource_handle) + # check_cudart_err(cudart.cudaGraphicsMapResources(1, self.resource_handle, None)) ptr, size = check_cudart_err(cudart.cudaGraphicsResourceGetMappedPointer(self.resource_handle)) @@ -136,7 +267,7 @@ def unmap(self): self.cuda_buffer_ptr = None self.cuda_buffer_size = -1 - def set_data_from_array(self, arr, expected_shape=None, expected_dtype=None): + def set_data_from_array(self, arr, buffer_size_in_bytes=None, expected_shape=None, expected_dtype=None): self.map() @@ -164,29 +295,11 @@ def set_data_from_array(self, arr, expected_shape=None, expected_dtype=None): self.unmap() - def get_array_from_unknown_data(self, arr): - - # Dispatch to one of the kinds of objects that we can read from - - # __cuda_array_interface__ - if is_cuda_array_interface(arr): - return cupy.ascontiguousarray(cupy.asarray(arr)) - - # __dlpack__ - # (I can't figure out any way to check this except try-catch) - try: - return cupy.ascontiguousarray(cupy.from_dlpack(arr)) - except ValueError: - pass - - raise ValueError("Cannot read from device data object. Must be a _dlpack_ array or implement the __cuda_array_interface__.") - - class CUDAOpenGLMappedTextureBuffer: def __init__(self, gl_attribute_native_id, buffer_type): - check_device_module_availibility() + ensure_device_interop_funcs_resolve() self.gl_attribute_native_id = gl_attribute_native_id self.buffer_type = buffer_type @@ -204,16 +317,10 @@ def __init__(self, gl_attribute_native_id, buffer_type): # see cudaGraphicsGLRegisterImage in these docs https://docs.nvidia.com/cuda/cuda-runtime-api/group__CUDART__OPENGL.html elif self.buffer_type == psb.DeviceBufferType.texture2d: - - self.resource_handle = check_cudart_err( - cudart.cudaGraphicsGLRegisterImage( - self.gl_attribute_native_id, - _CONSTANT_GL_TEXTURE_2D, - cudart.cudaGraphicsRegisterFlags.cudaGraphicsRegisterFlagsWriteDiscard - ) - ) + self.resource_handle = device_interop_funcs['register_gl_image_2d'](self.gl_attribute_native_id) elif self.buffer_type == psb.DeviceBufferType.texture3d: + # TODO raise ValueError("3d texture writes are not implemented") @@ -222,81 +329,94 @@ def __del__(self): def unregister(self): self.unmap() - self.resource_handle = check_cudart_err( - cudart.cudaGraphicsUnregisterResource(self.resource_handle)) + device_interop_funcs['unregister_resource'](self.resource_handle) def map(self): if self.cuda_buffer_array is not None: return - - check_cudart_err(cudart.cudaGraphicsMapResources(1, self.resource_handle, None)) - - self.cuda_buffer_array = check_cudart_err(cudart.cudaGraphicsSubResourceGetMappedArray(self.resource_handle, 0, 0)) + + self.cuda_buffer_array = device_interop_funcs['map_resource_and_get_array'](self.resource_handle) def unmap(self): if not hasattr(self, 'cuda_buffer_array') or self.cuda_buffer_array is None: return - check_cudart_err(cudart.cudaGraphicsUnmapResources(1, self.resource_handle, None)) + device_interop_funcs['unmap_resource'](self.resource_handle) self.cuda_buffer_array = None - def set_data_from_array(self, arr, expected_shape=None, expected_dtype=None): + def set_data_from_array(self, arr, texture_dims, entry_size_in_bytes): self.map() - # get some info about the polyscope buffer we just mapped - desc, extent, flags = check_cudart_err(cudart.cudaArrayGetInfo(self.cuda_buffer_array)) + # get some info about the buffer we just mapped + # NOTE: this used to be necessary to get the size of the copy, but now it is optional and just use it for sanity checking - # find a way to access the input array - cupy_arr = self.get_array_from_unknown_data(arr) - - # do some shape & type checks - if expected_dtype is not None and cupy_arr.dtype != expected_dtype: - raise ValueError(f"dlpack array has wrong dtype, expected {expected_dtype} but got {cupy_arr.dtype}") + extent_tup = None + if 'get_array_info' in device_interop_funcs: + extent_tup = device_interop_funcs['get_array_info'](self.cuda_buffer_array) - if expected_shape is not None and cupy_arr.shape != expected_shape: - raise ValueError(f"dlpack array has wrong shape, expected {expected_shape} but got {cupy_arr.shape}") + # access the input array + arr_ptr, arr_shape, arr_dtype, arr_nbytes = device_interop_funcs['get_array_ptr'](arr) if self.buffer_type == psb.DeviceBufferType.texture2d: - - dst_buff_width = extent.width - dst_buff_height = extent.height - dst_buff_width_pad = 0 - dst_buff_bytes_per = (desc.x + desc.y + desc.z + desc.w) // 8 - - dst_n_cmp = (1 if desc.x > 0 else 0) + \ - (1 if desc.y > 0 else 0) + \ - (1 if desc.z > 0 else 0) + \ - (1 if desc.w > 0 else 0) + + # if we got extent info from the destination array, use it to do additional sanity checks + if extent_tup is not None: + + desc, extent, flags = extent_tup + dst_buff_width = extent.width + dst_buff_height = extent.height + dst_buff_width_pad = 0 + + dst_buff_bytes_per = (desc.x + desc.y + desc.z + desc.w) // 8 + dst_buff_width_in_bytes = dst_buff_width * dst_buff_bytes_per + + dst_n_cmp = (1 if desc.x > 0 else 0) + \ + (1 if desc.y > 0 else 0) + \ + (1 if desc.z > 0 else 0) + \ + (1 if desc.w > 0 else 0) - dst_n_elems = dst_buff_width*dst_buff_height*dst_n_cmp - - # do some shape & type checks - if cupy_arr.size != dst_n_elems: - raise ValueError(f"Mapped buffer write has wrong size, destination buffer has {dst_n_elems} elements, but source buffer has {cupy_arr.size}.") - - expected_bytes = dst_buff_width * dst_buff_height * dst_buff_bytes_per - if cupy_arr.nbytes != expected_bytes: - # if cupy_arr has the right size/dtype, it should have exactly the same - # number of bytes as the destination. This is just lazily saving us - # from repeating the math, and also directly validates the copy we - # are about to do below. - raise ValueError(f"Mapped buffer write has wrong size, expected {cupy_arr.nbytes} bytes but got {expected_bytes} bytes. Could it be the wrong size/shape or wrong dtype?") - - # perform the actual copy - check_cudart_err( - cuda.cudart.cudaMemcpy2DToArray( - self.cuda_buffer_array, 0, 0, - cupy_arr.data.ptr, - dst_buff_width * dst_buff_bytes_per, - dst_buff_width * dst_buff_bytes_per, - dst_buff_height, - cudart.cudaMemcpyKind.cudaMemcpyDeviceToDevice + dst_n_elems = dst_buff_width*dst_buff_height*dst_n_cmp + + if extent.width != texture_dims[0]: raise ValueError(f"Mapped buffer width mismatch, {extent.width} vs. {texture_dims[0]}.") + if extent.height != texture_dims[1]: raise ValueError(f"Mapped buffer height mismatch, {extent.height} vs. {texture_dims[1]}.") + if extent.depth != texture_dims[2]: raise ValueError(f"Mapped buffer depth mismatch, {extent.depth} vs. {texture_dims[2]}.") + if dst_buff_bytes_per != entry_size_in_bytes : raise ValueError(f"Mapped buffer entry byte size mismatch, {dst_buff_bytes_per} vs. {entry_size_in_bytes}.") + + + # if we got shape info from the source array, use it to do sanity checks + if arr_shape is not None: + + # size of the input array + arr_size = 1 + for s in arr_shape: arr_size *= s + + # size of the destination array + dst_size = 1 + for s in texture_dims: + if s > 0: dst_size *= s + + if arr_size != dst_size: + raise ValueError(f"Mapped buffer write has wrong size, destination buffer has {dst_size} elements, but source buffer has {arr_size}.") + + + # if we got bytesize info from the source AND destination array, use it to do more sanity checks + if arr_nbytes is not None and extent_tup is not None: + expected_bytes = dst_buff_width * dst_buff_height * dst_buff_bytes_per + if arr_nbytes != expected_bytes: + # if cupy_arr has the right size/dtype, it should have exactly the same + # number of bytes as the destination. This is just lazily saving us + # from repeating the math, and also directly validates the copy we + # are about to do below. + raise ValueError(f"Mapped buffer write has wrong size, expected {arr_nbytes} bytes but got {expected_bytes} bytes. Could it be the wrong size/shape or wrong dtype?") + + + device_interop_funcs['memcpy_2d']( + self.cuda_buffer_array, arr_ptr, texture_dims[0]*entry_size_in_bytes, texture_dims[1] ) - ) else: raise ValueError("not implemented") @@ -304,19 +424,3 @@ def set_data_from_array(self, arr, expected_shape=None, expected_dtype=None): self.unmap() - def get_array_from_unknown_data(self, arr): - - # Dispatch to one of the kinds of objects that we can read from - - # __cuda_array_interface__ - if is_cuda_array_interface(arr): - return cupy.ascontiguousarray(cupy.asarray(arr)) - - # __dlpack__ - # (I can't figure out any way to check this except try-catch) - try: - return cupy.ascontiguousarray(cupy.from_dlpack(arr)) - except ValueError: - pass - - raise ValueError("Cannot read from device data object. Must be a _dlpack_ array or implement the __cuda_array_interface__.") diff --git a/src/polyscope/managed_buffer.py b/src/polyscope/managed_buffer.py index 4123c24..ab089f0 100644 --- a/src/polyscope/managed_buffer.py +++ b/src/polyscope/managed_buffer.py @@ -74,11 +74,18 @@ def update_data_from_device(self, new_vals_device): self.check_ref_still_valid() mapped_buffer = self.get_mapped_buffer_CUDAOpenGL() - mapped_buffer.set_data_from_array(new_vals_device) if self.device_buffer_type == psb.DeviceBufferType.attribute: + + mapped_buffer.set_data_from_array(new_vals_device, self.bound_buffer.get_device_buffer_size_in_bytes()) + self.bound_buffer.mark_render_attribute_buffer_updated() + else: # texture + + mapped_buffer.set_data_from_array(new_vals_device, self.bound_buffer.get_texture_size(), + self.bound_buffer.get_device_buffer_element_size_in_bytes()) + self.bound_buffer.mark_render_texture_buffer_updated() def get_mapped_buffer_CUDAOpenGL(self): From de4bd73d754177129101e640bb6b195e485480c7 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Mon, 16 Oct 2023 00:39:37 -0700 Subject: [PATCH 47/88] update dep --- deps/polyscope | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/polyscope b/deps/polyscope index 880f297..81f1c78 160000 --- a/deps/polyscope +++ b/deps/polyscope @@ -1 +1 @@ -Subproject commit 880f2975b0703909d23ffaf9ba7cb7f958f205fa +Subproject commit 81f1c78d3077e4b3d96203c68ea928520bbcc6a3 From 08ac646cffbf566e750db168a5af942a51dcbbc9 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Mon, 16 Oct 2023 00:40:01 -0700 Subject: [PATCH 48/88] add bindings for texture mapping surfaces --- src/cpp/surface_mesh.cpp | 19 ++++++++++++++++-- src/polyscope/surface_mesh.py | 30 ++++++++++++++++++++-------- test/polyscope_test.py | 37 ++++++++++++++++++++++++----------- 3 files changed, 65 insertions(+), 21 deletions(-) diff --git a/src/cpp/surface_mesh.cpp b/src/cpp/surface_mesh.cpp index 93dcd3f..421e977 100644 --- a/src/cpp/surface_mesh.cpp +++ b/src/cpp/surface_mesh.cpp @@ -24,10 +24,12 @@ void bind_surface_mesh(py::module& m) { bindScalarQuantity(m, "SurfaceFaceScalarQuantity"); bindScalarQuantity(m, "SurfaceEdgeScalarQuantity"); bindScalarQuantity(m, "SurfaceHalfedgeScalarQuantity"); + bindScalarQuantity(m, "SurfaceTextureScalarQuantity"); // Color quantities bindColorQuantity(m, "SurfaceVertexColorQuantity"); bindColorQuantity(m, "SurfaceFaceColorQuantity"); + bindColorQuantity(m, "SurfaceTextureColorQuantity"); // Parameterization quantities py::class_(m, "SurfaceCornerParameterizationQuantity") @@ -106,12 +108,23 @@ void bind_surface_mesh(py::module& m) { .def("add_halfedge_scalar_quantity", &ps::SurfaceMesh::addHalfedgeScalarQuantity, "Add a scalar function at halfedges", py::arg("name"), py::arg("values"), py::arg("data_type") = ps::DataType::STANDARD, py::return_value_policy::reference) + .def("add_texture_scalar_quantity", + overload_cast_()(&ps::SurfaceMesh::addTextureScalarQuantity), + "Add a scalar function from a texture map", py::arg("name"), py::arg("param_name"), py::arg("dimX"), + py::arg("dimY"), py::arg("values"), py::arg("image_origin"), py::arg("data_type") = ps::DataType::STANDARD, + py::return_value_policy::reference) // Colors .def("add_vertex_color_quantity", &ps::SurfaceMesh::addVertexColorQuantity, "Add a color value at vertices", py::return_value_policy::reference) .def("add_face_color_quantity", &ps::SurfaceMesh::addFaceColorQuantity, "Add a color value at faces", py::return_value_policy::reference) + .def("add_texture_color_quantity", + overload_cast_()( + &ps::SurfaceMesh::addTextureColorQuantity), + "Add a color function from a texture map", py::arg("name"), py::arg("param_name"), py::arg("dimX"), + py::arg("dimY"), py::arg("colors"), py::arg("image_origin"), py::return_value_policy::reference) // Distance .def("add_vertex_distance_quantity", &ps::SurfaceMesh::addVertexDistanceQuantity, @@ -134,9 +147,11 @@ void bind_surface_mesh(py::module& m) { "Add a vertex 2D vector quantity", py::return_value_policy::reference) .def("add_face_vector_quantity2D", &ps::SurfaceMesh::addFaceVectorQuantity2D, "Add a face 2D vector quantity", py::return_value_policy::reference) - .def("add_vertex_tangent_vector_quantity", &ps::SurfaceMesh::addVertexTangentVectorQuantity, + .def("add_vertex_tangent_vector_quantity", + &ps::SurfaceMesh::addVertexTangentVectorQuantity, "Add a vertex tangent vector quantity", py::return_value_policy::reference) - .def("add_face_tangent_vector_quantity", &ps::SurfaceMesh::addFaceTangentVectorQuantity, + .def("add_face_tangent_vector_quantity", + &ps::SurfaceMesh::addFaceTangentVectorQuantity, "Add a face tangent vector quantity", py::return_value_policy::reference) .def("add_one_form_tangent_vector_quantity", &ps::SurfaceMesh::addOneFormTangentVectorQuantity>, diff --git a/src/polyscope/surface_mesh.py b/src/polyscope/surface_mesh.py index 191e783..0186a79 100644 --- a/src/polyscope/surface_mesh.py +++ b/src/polyscope/surface_mesh.py @@ -3,9 +3,9 @@ from polyscope.core import str_to_datatype, str_to_vectortype, str_to_param_coords_type, \ str_to_param_viz_style, str_to_back_face_policy, back_face_policy_to_str,\ - glm3 + str_to_image_origin, glm3 from polyscope.structure import Structure -from polyscope.common import process_quantity_args, process_scalar_args, process_color_args, process_vector_args, process_parameterization_args, check_all_args_processed +from polyscope.common import process_quantity_args, process_scalar_args, process_color_args, process_vector_args, process_parameterization_args, check_all_args_processed, check_is_scalar_image, check_is_image3 class SurfaceMesh(Structure): @@ -155,9 +155,9 @@ def set_all_permutations(self, ## Quantities # Scalar - def add_scalar_quantity(self, name, values, defined_on='vertices', datatype="standard", **scalar_args): + def add_scalar_quantity(self, name, values, defined_on='vertices', datatype="standard", param_name=None, image_origin="upper_left", **scalar_args): - if len(values.shape) != 1: raise ValueError("'values' should be a length-N array") + if defined_on != 'texture' and len(values.shape) != 1: raise ValueError("'values' should be a length-N array") if defined_on == 'vertices': if values.shape[0] != self.n_vertices(): raise ValueError("'values' should be a length n_vertices array") @@ -171,8 +171,15 @@ def add_scalar_quantity(self, name, values, defined_on='vertices', datatype="sta elif defined_on == 'halfedges': if values.shape[0] != self.n_halfedges(): raise ValueError("'values' should be a length n_halfedges array") q = self.bound_instance.add_halfedge_scalar_quantity(name, values, str_to_datatype(datatype)) + elif defined_on == 'texture': + check_is_scalar_image(values) + dimY = values.shape[0] + dimX = values.shape[1] + if not isinstance(param_name, str): + raise ValueError("when adding a quantity defined in a texture, you must pass 'param_name' as a string giving the name of a parameterization quantity on this structure, which provides the UV coords") + q = self.bound_instance.add_texture_scalar_quantity(name, param_name, dimX, dimY, values.flatten(), str_to_image_origin(image_origin), str_to_datatype(datatype)) else: - raise ValueError("bad `defined_on` value {}, should be one of ['vertices', 'faces', 'edges', 'halfedges']".format(defined_on)) + raise ValueError("bad `defined_on` value {}, should be one of ['vertices', 'faces', 'edges', 'halfedges', 'texture']".format(defined_on)) # process and act on additional arguments @@ -183,8 +190,8 @@ def add_scalar_quantity(self, name, values, defined_on='vertices', datatype="sta # Color - def add_color_quantity(self, name, values, defined_on='vertices', **color_args): - if len(values.shape) != 2 or values.shape[1] != 3: raise ValueError("'values' should be an Nx3 array") + def add_color_quantity(self, name, values, defined_on='vertices', param_name=None, image_origin="upper_left", **color_args): + if defined_on != 'texture' and (len(values.shape) != 2 or values.shape[1] != 3): raise ValueError("'values' should be an Nx3 array") if defined_on == 'vertices': if values.shape[0] != self.n_vertices(): raise ValueError("'values' should be a length n_vertices array") @@ -192,8 +199,15 @@ def add_color_quantity(self, name, values, defined_on='vertices', **color_args): elif defined_on == 'faces': if values.shape[0] != self.n_faces(): raise ValueError("'values' should be a length n_faces array") q = self.bound_instance.add_face_color_quantity(name, values) + elif defined_on == 'texture': + check_is_image3(values) + dimY = values.shape[0] + dimX = values.shape[1] + if not isinstance(param_name, str): + raise ValueError("when adding a quantity defined in a texture, you must pass 'param_name' as a string giving the name of a parameterization quantity on this structure, which provides the UV coords") + q = self.bound_instance.add_texture_color_quantity(name, param_name, dimX, dimY, values.reshape(-1,3), str_to_image_origin(image_origin)) else: - raise ValueError("bad `defined_on` value {}, should be one of ['vertices', 'faces']".format(defined_on)) + raise ValueError("bad `defined_on` value {}, should be one of ['vertices', 'faces', 'texture']".format(defined_on)) # process and act on additional arguments diff --git a/test/polyscope_test.py b/test/polyscope_test.py index 5ea8fd0..4e600ba 100644 --- a/test/polyscope_test.py +++ b/test/polyscope_test.py @@ -1102,11 +1102,13 @@ def test_permutation_with_size(self): def test_scalar(self): - for on in ['vertices', 'faces', 'edges', 'halfedges']: + for on in ['vertices', 'faces', 'edges', 'halfedges', 'texture']: ps.register_surface_mesh("test_mesh", self.generate_verts(), self.generate_faces()) p = ps.get_surface_mesh("test_mesh") - + + param_name = None # used for texture case only + if on == 'vertices': vals = np.random.rand(p.n_vertices()) elif on == 'faces': @@ -1116,12 +1118,18 @@ def test_scalar(self): p.set_edge_permutation(np.random.permutation(p.n_edges())) elif on == 'halfedges': vals = np.random.rand(p.n_halfedges()) + elif on == 'texture': + param_vals = np.random.rand(p.n_vertices(), 2) + param_name = "test_param" + p.add_parameterization_quantity(param_name, param_vals, defined_on='vertices', enabled=True) + vals = np.random.rand(20,30) - p.add_scalar_quantity("test_vals", vals, defined_on=on) - p.add_scalar_quantity("test_vals2", vals, defined_on=on, enabled=True) - p.add_scalar_quantity("test_vals_with_range", vals, defined_on=on, vminmax=(-5., 5.), enabled=True) - p.add_scalar_quantity("test_vals_with_datatype", vals, defined_on=on, enabled=True, datatype='symmetric') - p.add_scalar_quantity("test_vals_with_cmap", vals, defined_on=on, enabled=True, cmap='blues') + + p.add_scalar_quantity("test_vals", vals, defined_on=on, param_name=param_name) + p.add_scalar_quantity("test_vals2", vals, defined_on=on, param_name=param_name, enabled=True) + p.add_scalar_quantity("test_vals_with_range", vals, defined_on=on, param_name=param_name, vminmax=(-5., 5.), enabled=True) + p.add_scalar_quantity("test_vals_with_datatype", vals, defined_on=on, param_name=param_name, enabled=True, datatype='symmetric') + p.add_scalar_quantity("test_vals_with_cmap", vals, defined_on=on, param_name=param_name, enabled=True, cmap='blues') ps.show(3) @@ -1137,16 +1145,23 @@ def test_color(self): ps.register_surface_mesh("test_mesh", self.generate_verts(), self.generate_faces()) p = ps.get_surface_mesh("test_mesh") - for on in ['vertices', 'faces']: - + for on in ['vertices', 'faces', 'texture']: + + param_name = None # used for texture case only + if on == 'vertices': vals = np.random.rand(p.n_vertices(), 3) elif on == 'faces': vals = np.random.rand(p.n_faces(), 3) + elif on == 'texture': + param_vals = np.random.rand(p.n_vertices(), 2) + param_name = "test_param" + p.add_parameterization_quantity(param_name, param_vals, defined_on='vertices', enabled=True) + vals = np.random.rand(20,30,3) - p.add_color_quantity("test_vals", vals, defined_on=on) - p.add_color_quantity("test_vals", vals, defined_on=on, enabled=True) + p.add_color_quantity("test_vals", vals, defined_on=on, param_name=param_name) + p.add_color_quantity("test_vals", vals, defined_on=on, param_name=param_name, enabled=True) ps.show(3) p.remove_all_quantities() From fb50bd463c434d77cbcedf3bd0244647ed588265 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Mon, 16 Oct 2023 15:03:38 -0700 Subject: [PATCH 49/88] rewrite unreachable code path to silence compiler warning --- src/cpp/utils.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/cpp/utils.h b/src/cpp/utils.h index 9a5f348..8c9444e 100644 --- a/src/cpp/utils.h +++ b/src/cpp/utils.h @@ -77,7 +77,10 @@ void def_get_quantity_managed_buffer(C& c, std::string postfix) { return fqPtr->template getManagedBuffer(buffer_name); } ps::exception("structure " + s.name + " has no quantity " + quantity_name); - return *static_cast*>(nullptr); // invalid, never executed + + // this line should be unreachable, it is here to convince the compiler that we don't need a return value + // here, to silence warnings + throw std::logic_error("structure has no such quantity"); }, "get quantity managed buffer", py::return_value_policy::reference); } From 83114458a98716799989213c31cd658d873abfce Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Mon, 16 Oct 2023 15:04:13 -0700 Subject: [PATCH 50/88] change visibility setting due to symbol visibility errors on osx --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 633e6d9..7e8ee43 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,6 +23,7 @@ pybind11_add_module(polyscope_bindings src/cpp/managed_buffer.cpp src/cpp/imgui.cpp ) +set_target_properties(polyscope_bindings PROPERTIES CXX_VISIBILITY_PRESET "default") target_include_directories(polyscope_bindings PUBLIC "${EIGEN3_INCLUDE_DIR}") target_link_libraries(polyscope_bindings PRIVATE polyscope) From 1a88f3b0dc5c2f806b27f06304b857e2f1454088 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Mon, 16 Oct 2023 17:43:14 -0700 Subject: [PATCH 51/88] first person navigation and view matrix --- deps/polyscope | 2 +- src/cpp/core.cpp | 4 ++++ src/polyscope/core.py | 10 ++++++++++ src/polyscope/managed_buffer.py | 2 -- 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/deps/polyscope b/deps/polyscope index 81f1c78..4a4e7a4 160000 --- a/deps/polyscope +++ b/deps/polyscope @@ -1 +1 @@ -Subproject commit 81f1c78d3077e4b3d96203c68ea928520bbcc6a3 +Subproject commit 4a4e7a40a0abc93d73ccb87341c6a08d13b33b5d diff --git a/src/cpp/core.cpp b/src/cpp/core.cpp index c3dd009..e130ebb 100644 --- a/src/cpp/core.cpp +++ b/src/cpp/core.cpp @@ -144,6 +144,8 @@ PYBIND11_MODULE(polyscope_bindings, m) { m.def("set_view_projection_mode", [](ps::ProjectionMode x) { ps::view::projectionMode = x; }); m.def("get_view_camera_parameters", &ps::view::getCameraParametersForCurrentView); m.def("set_view_camera_parameters", &ps::view::setViewToCamera); + m.def("set_camera_view_matrix", [](Eigen::Matrix4f mat) { ps::view::setCameraViewMatrix(eigen2glm(mat)); }); + m.def("get_camera_view_matrix", []() { return glm2eigen(ps::view::getCameraViewMatrix()); }); m.def("set_window_size", &ps::view::setWindowSize); m.def("get_window_size", &ps::view::getWindowSize); m.def("get_buffer_size", &ps::view::getBufferSize); @@ -324,6 +326,8 @@ PYBIND11_MODULE(polyscope_bindings, m) { .value("free", ps::view::NavigateStyle::Free) .value("planar", ps::view::NavigateStyle::Planar) .value("arcball", ps::view::NavigateStyle::Arcball) + .value("none", ps::view::NavigateStyle::None) + .value("first_person", ps::view::NavigateStyle::FirstPerson) .export_values(); py::enum_(m, "ProjectionMode") diff --git a/src/polyscope/core.py b/src/polyscope/core.py index 7b6aaad..29f5e99 100644 --- a/src/polyscope/core.py +++ b/src/polyscope/core.py @@ -196,6 +196,14 @@ def set_view_camera_parameters(params): def get_view_buffer_resolution(): return CameraParameters(instance=psb.get_view_camera_parameters()) +def set_camera_view_matrix(mat): + mat = np.asarray(mat) + if mat.shape != (4,4): raise ValueError("mat should be a 4x4 numpy matrix") + psb.set_camera_view_matrix(mat) + +def get_camera_view_matrix(): + return psb.get_camera_view_matrix() + ### Messages def info(message): @@ -413,6 +421,8 @@ def load_color_map(cmap_name, filename): "turntable" : psb.NavigateStyle.turntable, "free" : psb.NavigateStyle.free, "planar" : psb.NavigateStyle.planar, + "none" : psb.NavigateStyle.none, + "first_person" : psb.NavigateStyle.first_person, } def str_to_navigate_style(s): diff --git a/src/polyscope/managed_buffer.py b/src/polyscope/managed_buffer.py index ab089f0..b407277 100644 --- a/src/polyscope/managed_buffer.py +++ b/src/polyscope/managed_buffer.py @@ -42,8 +42,6 @@ def has_data(self): def summary_string(self): self.check_ref_still_valid() - - return self.bound_buffer.summary_string() def get_value(self, ind, indY=None, indZ=None): From da4f4c8200975da14245c2f50fe576c523a19803 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Tue, 17 Oct 2023 01:32:56 -0700 Subject: [PATCH 52/88] update dep --- deps/polyscope | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/polyscope b/deps/polyscope index 4a4e7a4..1cf203a 160000 --- a/deps/polyscope +++ b/deps/polyscope @@ -1 +1 @@ -Subproject commit 4a4e7a40a0abc93d73ccb87341c6a08d13b33b5d +Subproject commit 1cf203a3a03052a03dd9456a4996759105d4aba9 From 146c39c10b99205e3eda8a28683ab6d10ca0485c Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Tue, 17 Oct 2023 01:33:42 -0700 Subject: [PATCH 53/88] more options for control flow etc --- src/cpp/core.cpp | 11 +++++++++-- src/polyscope/camera_view.py | 2 +- src/polyscope/core.py | 17 +++++++++++++++++ src/polyscope/curve_network.py | 2 +- src/polyscope/point_cloud.py | 2 +- src/polyscope/surface_mesh.py | 2 +- src/polyscope/volume_mesh.py | 2 +- test/polyscope_test.py | 4 ++++ 8 files changed, 35 insertions(+), 7 deletions(-) diff --git a/src/cpp/core.cpp b/src/cpp/core.cpp index e130ebb..c904b01 100644 --- a/src/cpp/core.cpp +++ b/src/cpp/core.cpp @@ -66,8 +66,8 @@ PYBIND11_MODULE(polyscope_bindings, m) { // === Basic flow m.def("init", &ps::init, py::arg("backend")="", "Initialize Polyscope"); - m.def("checkInitialized", &ps::checkInitialized); - m.def("isInitialized", &ps::isInitialized); + m.def("check_initialized", &ps::checkInitialized); + m.def("is_initialized", &ps::isInitialized); m.def("show", [](size_t forFrames) { if (ps::state::userCallback == nullptr) { bool oldVal = ps::options::openImGuiWindowForUserCallback; @@ -85,10 +85,15 @@ PYBIND11_MODULE(polyscope_bindings, m) { }, py::arg("forFrames")=std::numeric_limits::max() ); + m.def("unshow", &ps::unshow); m.def("frame_tick", &ps::frameTick); // === Render engine related things m.def("get_render_engine_backend_name", &ps::render::getRenderEngineBackendName); + m.def("get_key_code", [](char c) { + ps::checkInitialized(); + return ps::render::engine->getKeyCode(c); + }); // === Structure management m.def("remove_all_structures", &ps::removeAllStructures, "Remove all structures from polyscope"); @@ -113,6 +118,8 @@ PYBIND11_MODULE(polyscope_bindings, m) { m.def("set_autocenter_structures", [](bool x) { ps::options::autocenterStructures = x; }); m.def("set_autoscale_structures", [](bool x) { ps::options::autoscaleStructures = x; }); m.def("set_build_gui", [](bool x) { ps::options::buildGui = x; }); + m.def("set_user_gui_is_on_right_side", [](bool x) { ps::options::userGuiIsOnRightSide = x; }); + m.def("set_build_default_gui_panels", [](bool x) { ps::options::buildDefaultGuiPanels = x; }); m.def("set_render_scene", [](bool x) { ps::options::renderScene = x; }); m.def("set_open_imgui_window_for_user_callback", [](bool x) { ps::options::openImGuiWindowForUserCallback= x; }); m.def("set_invoke_user_callback_for_nested_show", [](bool x) { ps::options::invokeUserCallbackForNestedShow = x; }); diff --git a/src/polyscope/camera_view.py b/src/polyscope/camera_view.py index aeb26d4..d57e6ce 100644 --- a/src/polyscope/camera_view.py +++ b/src/polyscope/camera_view.py @@ -65,7 +65,7 @@ def register_camera_view(name, camera_parameters, widget_color=None, widget_thickness=None, widget_focal_length=None, ): """Register a new camera view""" - if not psb.isInitialized(): + if not psb.is_initialized(): raise RuntimeError("Polyscope has not been initialized") p = CameraView(name, camera_parameters) diff --git a/src/polyscope/core.py b/src/polyscope/core.py index 29f5e99..0e99313 100644 --- a/src/polyscope/core.py +++ b/src/polyscope/core.py @@ -25,6 +25,15 @@ def show(forFrames=None): else: psb.show(forFrames) +def unshow(): + psb.unshow() + +def check_initialized(): + psb.check_initialized() + +def is_initialized(): + return psb.is_initialized() + def frame_tick(): psb.frame_tick() @@ -33,6 +42,8 @@ def frame_tick(): def get_render_engine_backend_name(): return psb.get_render_engine_backend_name() +def get_key_code(c): + return psb.get_key_code(c) ### Structure management @@ -97,6 +108,12 @@ def set_autoscale_structures(b): def set_build_gui(b): psb.set_build_gui(b) +def set_user_gui_is_on_right_side(b): + psb.set_user_gui_is_on_right_side(b) + +def set_build_default_gui_panels(b): + psb.set_build_default_gui_panels(b) + def set_render_scene(b): psb.set_render_scene(b) diff --git a/src/polyscope/curve_network.py b/src/polyscope/curve_network.py index d0e66d8..3be899b 100644 --- a/src/polyscope/curve_network.py +++ b/src/polyscope/curve_network.py @@ -170,7 +170,7 @@ def add_vector_quantity(self, name, values, defined_on='nodes', vectortype="stan def register_curve_network(name, nodes, edges, enabled=None, radius=None, color=None, material=None, transparency=None): """Register a new curve network""" - if not psb.isInitialized(): + if not psb.is_initialized(): raise RuntimeError("Polyscope has not been initialized") p = CurveNetwork(name, nodes, edges) diff --git a/src/polyscope/point_cloud.py b/src/polyscope/point_cloud.py index 623b73b..8098271 100644 --- a/src/polyscope/point_cloud.py +++ b/src/polyscope/point_cloud.py @@ -130,7 +130,7 @@ def add_vector_quantity(self, name, values, vectortype="standard", **vector_args def register_point_cloud(name, points, enabled=None, radius=None, point_render_mode=None, color=None, material=None, transparency=None): """Register a new point cloud""" - if not psb.isInitialized(): + if not psb.is_initialized(): raise RuntimeError("Polyscope has not been initialized") p = PointCloud(name, points) diff --git a/src/polyscope/surface_mesh.py b/src/polyscope/surface_mesh.py index 0186a79..1f2203b 100644 --- a/src/polyscope/surface_mesh.py +++ b/src/polyscope/surface_mesh.py @@ -352,7 +352,7 @@ def register_surface_mesh(name, vertices, faces, enabled=None, color=None, edge_ edge_width=None, material=None, back_face_policy=None, back_face_color=None, transparency=None): """Register a new surface mesh""" - if not psb.isInitialized(): + if not psb.is_initialized(): raise RuntimeError("Polyscope has not been initialized") p = SurfaceMesh(name, vertices, faces) diff --git a/src/polyscope/volume_mesh.py b/src/polyscope/volume_mesh.py index 8bdc1a9..f9eb284 100644 --- a/src/polyscope/volume_mesh.py +++ b/src/polyscope/volume_mesh.py @@ -187,7 +187,7 @@ def register_volume_mesh(name, vertices, tets=None, hexes=None, mixed_cells=None """Register a new volume mesh""" - if not psb.isInitialized(): + if not psb.is_initialized(): raise RuntimeError("Polyscope has not been initialized") p = VolumeMesh(name, vertices=vertices, tets=tets, hexes=hexes, mixed_cells=mixed_cells) diff --git a/test/polyscope_test.py b/test/polyscope_test.py index 4e600ba..3427bd8 100644 --- a/test/polyscope_test.py +++ b/test/polyscope_test.py @@ -96,6 +96,8 @@ def test_view_options(self): ps.set_navigation_style("turntable") ps.set_navigation_style("free") ps.set_navigation_style("planar") + ps.set_navigation_style("none") + ps.set_navigation_style("first_person") ps.set_navigation_style(ps.get_navigation_style()) ps.set_up_dir("x_up") @@ -116,6 +118,8 @@ def test_view_options(self): ps.set_view_projection_mode("orthographic") ps.set_view_projection_mode("perspective") + + ps.set_camera_view_matrix(ps.get_camera_view_matrix()) ps.set_window_size(800, 600) self.assertEqual(ps.get_window_size(), (800,600)) From e8d3d38dedcc53a185c586ff16a86ebcea9db5f4 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Wed, 18 Oct 2023 10:00:36 -0700 Subject: [PATCH 54/88] update dep --- deps/polyscope | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/polyscope b/deps/polyscope index 1cf203a..d9472ec 160000 --- a/deps/polyscope +++ b/deps/polyscope @@ -1 +1 @@ -Subproject commit 1cf203a3a03052a03dd9456a4996759105d4aba9 +Subproject commit d9472ec287bf40b06d2535b468a8bdedb7c92142 From 7256ee76f3dc5212337436adaa961ad0a30caa6d Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Wed, 18 Oct 2023 10:01:06 -0700 Subject: [PATCH 55/88] minor --- src/polyscope/managed_buffer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/polyscope/managed_buffer.py b/src/polyscope/managed_buffer.py index b407277..1777fcd 100644 --- a/src/polyscope/managed_buffer.py +++ b/src/polyscope/managed_buffer.py @@ -57,7 +57,7 @@ def get_value(self, ind, indY=None, indZ=None): def update_data(self, new_vals): self.check_ref_still_valid() - self.update_data_from_host(new_vals); + self.update_data_from_host(new_vals) def update_data_from_host(self, new_vals): self.check_ref_still_valid() @@ -66,7 +66,7 @@ def update_data_from_host(self, new_vals): # what the underlying kind of buffer is. We should probably document it # better or provide some error checking at the Python level. # NOTE: this method calls mark_host_buffer_updated() internally, so there is no need to call it again - self.bound_buffer.update_data(new_vals); + self.bound_buffer.update_data(new_vals) def update_data_from_device(self, new_vals_device): self.check_ref_still_valid() From 898b4d9383f6e52ecb140091281b0245856a5820 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Wed, 18 Oct 2023 10:01:32 -0700 Subject: [PATCH 56/88] remove accidental glm return type --- src/cpp/core.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cpp/core.cpp b/src/cpp/core.cpp index c904b01..9cb299a 100644 --- a/src/cpp/core.cpp +++ b/src/cpp/core.cpp @@ -161,9 +161,9 @@ PYBIND11_MODULE(polyscope_bindings, m) { m.def("set_view_from_json", ps::view::setViewFromJson); m.def("get_view_as_json", ps::view::getViewAsJson); m.def("set_background_color", [](glm::vec4 c) { for(int i = 0; i < 4; i++) ps::view::bgColor[i] = c[i]; }); - m.def("get_background_color", []() { return glm::vec4{ + m.def("get_background_color", []() { return glm2eigen(glm::vec4{ ps::view::bgColor[0], ps::view::bgColor[1], ps::view::bgColor[2], ps::view::bgColor[3] - }; + }); }); // === "Advanced" UI management From 12cddcd06f916a024109502a3053ab68e21cee93 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Wed, 18 Oct 2023 16:45:40 -0700 Subject: [PATCH 57/88] fix bugs in managed buffers --- deps/polyscope | 2 +- src/cpp/managed_buffer.cpp | 53 +++++++++++++++++++++------------ src/polyscope/managed_buffer.py | 2 ++ 3 files changed, 37 insertions(+), 20 deletions(-) diff --git a/deps/polyscope b/deps/polyscope index d9472ec..ae87c92 160000 --- a/deps/polyscope +++ b/deps/polyscope @@ -1 +1 @@ -Subproject commit d9472ec287bf40b06d2535b468a8bdedb7c92142 +Subproject commit ae87c92cc9fd3e42d53268b4859c1072a98fad95 diff --git a/src/cpp/managed_buffer.cpp b/src/cpp/managed_buffer.cpp index 21ade88..f1aceeb 100644 --- a/src/cpp/managed_buffer.cpp +++ b/src/cpp/managed_buffer.cpp @@ -32,24 +32,26 @@ py::class_> bind_managed_buffer_T(py::module& m, ps .def("get_value", overload_cast_()(&ps::render::ManagedBuffer::getValue)) .def("get_value", overload_cast_()(&ps::render::ManagedBuffer::getValue)) .def("mark_host_buffer_updated", &ps::render::ManagedBuffer::markHostBufferUpdated) - .def("get_device_buffer_size_in_bytes", [](ps::render::ManagedBuffer& s) { - // NOTE: this could cause the underlying device buffer to be allocatred if it wasn't already - if (s.getDeviceBufferType() == polyscope::DeviceBufferType::Attribute) { - return s.getRenderAttributeBuffer()->getDataSizeInBytes(); - } else { - return s.getRenderTextureBuffer()->getSizeInBytes(); - } - }) - .def("get_device_buffer_element_size_in_bytes", [](ps::render::ManagedBuffer& s) { - // NOTE: this could cause the underlying device buffer to be allocatred if it wasn't already - if (s.getDeviceBufferType() == polyscope::DeviceBufferType::Attribute) { - std::shared_ptr buff = s.getRenderAttributeBuffer(); - return polyscope::sizeInBytes(buff->getType()) * buff->getArrayCount(); - } else { - std::shared_ptr buff = s.getRenderTextureBuffer(); - return polyscope::sizeInBytes(buff->getFormat()); - } - }) + .def("get_device_buffer_size_in_bytes", + [](ps::render::ManagedBuffer& s) { + // NOTE: this could cause the underlying device buffer to be allocatred if it wasn't already + if (s.getDeviceBufferType() == polyscope::DeviceBufferType::Attribute) { + return s.getRenderAttributeBuffer()->getDataSizeInBytes(); + } else { + return s.getRenderTextureBuffer()->getSizeInBytes(); + } + }) + .def("get_device_buffer_element_size_in_bytes", + [](ps::render::ManagedBuffer& s) { + // NOTE: this could cause the underlying device buffer to be allocatred if it wasn't already + if (s.getDeviceBufferType() == polyscope::DeviceBufferType::Attribute) { + std::shared_ptr buff = s.getRenderAttributeBuffer(); + return polyscope::sizeInBytes(buff->getType()) * buff->getArrayCount(); + } else { + std::shared_ptr buff = s.getRenderTextureBuffer(); + return polyscope::sizeInBytes(buff->getFormat()); + } + }) .def("get_native_render_attribute_buffer_ID", [](ps::render::ManagedBuffer& s) { return s.getRenderAttributeBuffer()->getNativeBufferID(); }) .def("mark_render_attribute_buffer_updated", &ps::render::ManagedBuffer::markRenderAttributeBufferUpdated) @@ -58,7 +60,7 @@ py::class_> bind_managed_buffer_T(py::module& m, ps .def("mark_render_texture_buffer_updated", &ps::render::ManagedBuffer::markRenderTextureBufferUpdated) - ; + ; } // clang-format off @@ -69,6 +71,7 @@ void bind_managed_buffer(py::module& m) { bind_managed_buffer_T(m, ps::ManagedBufferType::Float) .def("update_data", [](ps::render::ManagedBuffer& s, Eigen::VectorXf& d) { if(d.rows() != s.size()) ps::exception("bad update size, should be " + std::to_string(s.size())); + s.ensureHostBufferAllocated(); for(uint32_t i = 0; i < s.size(); i++) s.data[i] = d(i); s.markHostBufferUpdated(); }) @@ -77,6 +80,7 @@ void bind_managed_buffer(py::module& m) { bind_managed_buffer_T(m, ps::ManagedBufferType::Double) .def("update_data", [](ps::render::ManagedBuffer& s, Eigen::VectorXd& d) { if(d.rows() != s.size()) ps::exception("bad update size, should be " + std::to_string(s.size())); + s.ensureHostBufferAllocated(); for(uint32_t i = 0; i < s.size(); i++) s.data[i] = d(i); s.markHostBufferUpdated(); }) @@ -85,6 +89,7 @@ void bind_managed_buffer(py::module& m) { bind_managed_buffer_T(m, ps::ManagedBufferType::Vec2) .def("update_data", [](ps::render::ManagedBuffer& s, Eigen::Matrix& d) { if(d.rows() != s.size()) ps::exception("bad update size, should be " + std::to_string(s.size()) + " x 2"); + s.ensureHostBufferAllocated(); for(uint32_t i = 0; i < s.size(); i++) s.data[i] = {d(i,0), d(i,1)}; s.markHostBufferUpdated(); }) @@ -93,6 +98,7 @@ void bind_managed_buffer(py::module& m) { bind_managed_buffer_T(m, ps::ManagedBufferType::Vec3) .def("update_data", [](ps::render::ManagedBuffer& s, Eigen::Matrix& d) { if(d.rows() != s.size()) ps::exception("bad update size, should be " + std::to_string(s.size()) + " x 3"); + s.ensureHostBufferAllocated(); for(uint32_t i = 0; i < s.size(); i++) s.data[i] = {d(i,0), d(i,1), d(i,2)}; s.markHostBufferUpdated(); }) @@ -101,6 +107,7 @@ void bind_managed_buffer(py::module& m) { bind_managed_buffer_T(m, ps::ManagedBufferType::Vec4) .def("update_data", [](ps::render::ManagedBuffer& s, Eigen::Matrix& d) { if(d.rows() != s.size() || d.cols() != 4) ps::exception("bad update size, should be " + std::to_string(s.size()) + " x 4"); + s.ensureHostBufferAllocated(); for(uint32_t i = 0; i < s.size(); i++) s.data[i] = {d(i,0), d(i,1), d(i,2), d(i,3)}; s.markHostBufferUpdated(); }) @@ -111,6 +118,7 @@ void bind_managed_buffer(py::module& m) { for(uint32_t k = 0; k < 2; k++) { if(d[k].rows() != s.size()) ps::exception("bad update size, should be " + std::to_string(s.size()) + " x 3"); } + s.ensureHostBufferAllocated(); for(uint32_t i = 0; i < s.size(); i++) { for(uint32_t k = 0; k < 2; k++) { s.data[i][k] = {d[k](i,0), d[k](i,1), d[k](i,2)}; @@ -126,6 +134,7 @@ void bind_managed_buffer(py::module& m) { for(uint32_t k = 0; k < 3; k++) { if(d[k].rows() != s.size()) ps::exception("bad update size, should be " + std::to_string(s.size()) + " x 3"); } + s.ensureHostBufferAllocated(); for(uint32_t i = 0; i < s.size(); i++) { for(uint32_t k = 0; k < 3; k++) { s.data[i][k] = {d[k](i,0), d[k](i,1), d[k](i,2)}; @@ -140,6 +149,7 @@ void bind_managed_buffer(py::module& m) { for(uint32_t k = 0; k < 4; k++) { if(d[k].rows() != s.size()) ps::exception("bad update size, should be " + std::to_string(s.size()) + " x 3"); } + s.ensureHostBufferAllocated(); for(uint32_t i = 0; i < s.size(); i++) { for(uint32_t k = 0; k < 4; k++) { s.data[i][k] = {d[k](i,0), d[k](i,1), d[k](i,2)}; @@ -152,6 +162,7 @@ void bind_managed_buffer(py::module& m) { bind_managed_buffer_T(m, ps::ManagedBufferType::UInt32) .def("update_data", [](ps::render::ManagedBuffer& s, Eigen::Matrix& d) { if(d.rows() != s.size()) ps::exception("bad update size, should be " + std::to_string(s.size())); + s.ensureHostBufferAllocated(); for(uint32_t i = 0; i < s.size(); i++) s.data[i] = d(i); s.markHostBufferUpdated(); }) @@ -160,6 +171,7 @@ void bind_managed_buffer(py::module& m) { bind_managed_buffer_T(m, ps::ManagedBufferType::Int32) .def("update_data", [](ps::render::ManagedBuffer& s, Eigen::Matrix& d) { if(d.rows() != s.size()) ps::exception("bad update size, should be " + std::to_string(s.size())); + s.ensureHostBufferAllocated(); for(uint32_t i = 0; i < s.size(); i++) s.data[i] = d(i); s.markHostBufferUpdated(); }) @@ -168,6 +180,7 @@ void bind_managed_buffer(py::module& m) { bind_managed_buffer_T(m, ps::ManagedBufferType::UVec2) .def("update_data", [](ps::render::ManagedBuffer& s, Eigen::Matrix& d) { if(d.rows() != s.size()) ps::exception("bad update size, should be " + std::to_string(s.size()) + " x 2"); + s.ensureHostBufferAllocated(); for(uint32_t i = 0; i < s.size(); i++) s.data[i] = {d(i,0), d(i,1)}; s.markHostBufferUpdated(); }) @@ -177,6 +190,7 @@ void bind_managed_buffer(py::module& m) { bind_managed_buffer_T(m, ps::ManagedBufferType::UVec3) .def("update_data", [](ps::render::ManagedBuffer& s, Eigen::Matrix& d) { if(d.rows() != s.size()) ps::exception("bad update size, should be " + std::to_string(s.size()) + " x 3"); + s.ensureHostBufferAllocated(); for(uint32_t i = 0; i < s.size(); i++) s.data[i] = {d(i,0), d(i,1), d(i,2)}; s.markHostBufferUpdated(); }) @@ -185,6 +199,7 @@ void bind_managed_buffer(py::module& m) { bind_managed_buffer_T(m, ps::ManagedBufferType::UVec4) .def("update_data", [](ps::render::ManagedBuffer& s, Eigen::Matrix& d) { if(d.rows() != s.size()) ps::exception("bad update size, should be " + std::to_string(s.size()) + " x 4"); + s.ensureHostBufferAllocated(); for(uint32_t i = 0; i < s.size(); i++) s.data[i] = {d(i,0), d(i,1), d(i,2), d(i,3)}; s.markHostBufferUpdated(); }) diff --git a/src/polyscope/managed_buffer.py b/src/polyscope/managed_buffer.py index 1777fcd..f86d304 100644 --- a/src/polyscope/managed_buffer.py +++ b/src/polyscope/managed_buffer.py @@ -57,6 +57,8 @@ def get_value(self, ind, indY=None, indZ=None): def update_data(self, new_vals): self.check_ref_still_valid() + new_vals = new_vals.reshape((self.size(),-1)) + self.update_data_from_host(new_vals) def update_data_from_host(self, new_vals): From b2be28d8e68808e58b6c63680107e362273ddae7 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Wed, 18 Oct 2023 17:11:19 -0700 Subject: [PATCH 58/88] update dep --- deps/polyscope | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/polyscope b/deps/polyscope index ae87c92..ebdef1d 160000 --- a/deps/polyscope +++ b/deps/polyscope @@ -1 +1 @@ -Subproject commit ae87c92cc9fd3e42d53268b4859c1072a98fad95 +Subproject commit ebdef1d7fdb07a1507808a56d9a0bb637bd24294 From 9513ca5b44fa933fd456d92ff09c147a7a0f8b4d Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Thu, 19 Oct 2023 00:40:11 -0700 Subject: [PATCH 59/88] bind new options --- src/cpp/core.cpp | 1 + src/polyscope/core.py | 3 +++ 2 files changed, 4 insertions(+) diff --git a/src/cpp/core.cpp b/src/cpp/core.cpp index 9cb299a..260fa2c 100644 --- a/src/cpp/core.cpp +++ b/src/cpp/core.cpp @@ -124,6 +124,7 @@ PYBIND11_MODULE(polyscope_bindings, m) { m.def("set_open_imgui_window_for_user_callback", [](bool x) { ps::options::openImGuiWindowForUserCallback= x; }); m.def("set_invoke_user_callback_for_nested_show", [](bool x) { ps::options::invokeUserCallbackForNestedShow = x; }); m.def("set_give_focus_on_show", [](bool x) { ps::options::giveFocusOnShow = x; }); + m.def("set_hide_window_after_show", [](bool x) { ps::options::hideWindowAfterShow = x; }); // === Scene extents diff --git a/src/polyscope/core.py b/src/polyscope/core.py index 0e99313..44af23e 100644 --- a/src/polyscope/core.py +++ b/src/polyscope/core.py @@ -126,6 +126,9 @@ def set_invoke_user_callback_for_nested_show(b): def set_give_focus_on_show(b): psb.set_give_focus_on_show(b) +def set_hide_window_after_show(b): + psb.set_hide_window_after_show(b) + def set_navigation_style(s): psb.set_navigation_style(str_to_navigate_style(s)) def get_navigation_style(): From 6696907091ecc9f3078d757e6aaae05216a9ffa9 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Thu, 19 Oct 2023 00:40:31 -0700 Subject: [PATCH 60/88] optional normals in render image --- src/polyscope/floating_quantities.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/polyscope/floating_quantities.py b/src/polyscope/floating_quantities.py index f398471..ba0a3d5 100644 --- a/src/polyscope/floating_quantities.py +++ b/src/polyscope/floating_quantities.py @@ -3,6 +3,7 @@ from polyscope.common import check_is_scalar_image, check_is_image3, check_is_image4, check_image_dims_compatible, process_scalar_args, process_color_args, process_image_args, process_render_image_args, process_quantity_args, check_all_args_processed from polyscope.core import str_to_image_origin, str_to_datatype, glm3 +import numpy as np def _resolve_floating_struct_instance(struct_ref): if struct_ref is None: @@ -85,8 +86,11 @@ def add_depth_render_image_quantity(name, depth_values, normal_values, image_ori struct_instance_ref = _resolve_floating_struct_instance(struct_ref) check_is_scalar_image(depth_values) - check_is_image3(normal_values) - check_image_dims_compatible([depth_values, normal_values]) + if normal_values is None: + normal_values = np.zeros((0,0,3)) + else: + check_is_image3(normal_values) + check_image_dims_compatible([depth_values, normal_values]) dimY = depth_values.shape[0] dimX = depth_values.shape[1] @@ -112,9 +116,13 @@ def add_color_render_image_quantity(name, depth_values, normal_values, color_val struct_instance_ref = _resolve_floating_struct_instance(struct_ref) check_is_scalar_image(depth_values) - check_is_image3(normal_values) check_is_image3(color_values) - check_image_dims_compatible([depth_values, normal_values, color_values]) + if normal_values is None: + normal_values = np.zeros((0,0,3)) + check_image_dims_compatible([depth_values, color_values]) + else: + check_is_image3(normal_values) + check_image_dims_compatible([depth_values, normal_values, color_values]) dimY = depth_values.shape[0] dimX = depth_values.shape[1] @@ -140,9 +148,13 @@ def add_scalar_render_image_quantity(name, depth_values, normal_values, scalar_v struct_instance_ref = _resolve_floating_struct_instance(struct_ref) check_is_scalar_image(depth_values) - check_is_image3(normal_values) check_is_scalar_image(scalar_values) - check_image_dims_compatible([depth_values, normal_values, scalar_values]) + if normal_values is None: + normal_values = np.zeros((0,0,3)) + check_image_dims_compatible([depth_values, scalar_values]) + else: + check_is_image3(normal_values) + check_image_dims_compatible([depth_values, normal_values, scalar_values]) dimY = depth_values.shape[0] dimX = depth_values.shape[1] From 46a579aa9f8ed29bc76fcf03493ba4b3ae2fbe09 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Thu, 19 Oct 2023 00:40:36 -0700 Subject: [PATCH 61/88] update dep --- deps/polyscope | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/polyscope b/deps/polyscope index ebdef1d..6b4b60d 160000 --- a/deps/polyscope +++ b/deps/polyscope @@ -1 +1 @@ -Subproject commit ebdef1d7fdb07a1507808a56d9a0bb637bd24294 +Subproject commit 6b4b60df9f74d4b19de34926a25f4ffa6b4b7629 From eb2b5e77c30d0ae5e3f23c682a3277f763e0dcd8 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sun, 22 Oct 2023 19:18:13 -0700 Subject: [PATCH 62/88] WIP adding volume grids --- CMakeLists.txt | 1 + src/cpp/core.cpp | 9 ++ src/cpp/volume_grid.cpp | 125 +++++++++++++++++++++ src/polyscope/__init__.py | 1 + src/polyscope/core.py | 2 + src/polyscope/volume_grid.py | 205 +++++++++++++++++++++++++++++++++++ test/polyscope_test.py | 204 ++++++++++++++++++++++++++++++++++ 7 files changed, 547 insertions(+) create mode 100644 src/cpp/volume_grid.cpp create mode 100644 src/polyscope/volume_grid.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 7e8ee43..320877d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,6 +17,7 @@ pybind11_add_module(polyscope_bindings src/cpp/point_cloud.cpp src/cpp/curve_network.cpp src/cpp/volume_mesh.cpp + src/cpp/volume_grid.cpp src/cpp/camera_view.cpp src/cpp/floating_quantities.cpp src/cpp/implicit_helpers.cpp diff --git a/src/cpp/core.cpp b/src/cpp/core.cpp index 260fa2c..c16cda5 100644 --- a/src/cpp/core.cpp +++ b/src/cpp/core.cpp @@ -33,6 +33,7 @@ void bind_surface_mesh(py::module& m); void bind_point_cloud(py::module& m); void bind_curve_network(py::module& m); void bind_volume_mesh(py::module& m); +void bind_volume_grid(py::module& m); void bind_camera_view(py::module& m); void bind_floating_quantities(py::module& m); void bind_implicit_helpers(py::module& m); @@ -462,6 +463,13 @@ PYBIND11_MODULE(polyscope_bindings, m) { [](const glm::vec4& x) { return std::tuple(x[0], x[1], x[2], x[3]); }); + + py::class_(m, "glm_uvec3"). + def(py::init()) + .def("as_tuple", + [](const glm::uvec3& x) { + return std::tuple(x[0], x[1], x[2]); + }); // === Bind structures defined in other files @@ -471,6 +479,7 @@ PYBIND11_MODULE(polyscope_bindings, m) { bind_curve_network(m); bind_surface_mesh(m); bind_volume_mesh(m); + bind_volume_grid(m); bind_camera_view(m); bind_managed_buffer(m); bind_imgui(m); diff --git a/src/cpp/volume_grid.cpp b/src/cpp/volume_grid.cpp new file mode 100644 index 0000000..86a9268 --- /dev/null +++ b/src/cpp/volume_grid.cpp @@ -0,0 +1,125 @@ +#include +#include +#include +#include + +#include "Eigen/Dense" + +#include "polyscope/polyscope.h" +#include "polyscope/volume_grid.h" + +#include "utils.h" + +namespace py = pybind11; +namespace ps = polyscope; + + +void bind_volume_grid(py::module& m) { + + // == Helper quantity classes + + // Scalar quantities + bindScalarQuantity(m, "VolumeGridNodeScalarQuantity") + .def("set_gridcube_viz_enabled", &ps::VolumeGridNodeScalarQuantity::setGridcubeVizEnabled) + .def("set_isosurface_viz_enabled", &ps::VolumeGridNodeScalarQuantity::setIsosurfaceVizEnabled) + .def("set_isosurface_level", &ps::VolumeGridNodeScalarQuantity::setIsosurfaceLevel) + .def("set_isosurface_color", &ps::VolumeGridNodeScalarQuantity::setIsosurfaceColor) + .def("set_slice_planes_affect_isosurface", &ps::VolumeGridNodeScalarQuantity::setSlicePlanesAffectIsosurface) + .def("register_isosurface_as_mesh_with_name", + [](ps::VolumeGridNodeScalarQuantity& x, std::string name) { return x.registerIsosurfaceAsMesh(name); }) + ; + + bindScalarQuantity(m, "VolumeGridCellScalarQuantity") + .def("set_gridcube_viz_enabled", &ps::VolumeGridCellScalarQuantity::setGridcubeVizEnabled) + ; + + // == Main class + bindStructure(m, "VolumeGrid") + + // basics + .def("n_nodes", &ps::VolumeGrid::nNodes) + .def("n_cells", &ps::VolumeGrid::nCells) + .def("grid_spacing", &ps::VolumeGrid::gridSpacing) + .def("get_grid_node_dim", &ps::VolumeGrid::getGridNodeDim) + .def("get_grid_cell_dim", &ps::VolumeGrid::getGridCellDim) + .def("get_bound_min", [](ps::VolumeGrid& x) { return glm2eigen(x.getBoundMin()); }) + .def("get_bound_max", [](ps::VolumeGrid& x) { return glm2eigen(x.getBoundMax()); }) + + .def("mark_nodes_as_used", &ps::VolumeGrid::markNodesAsUsed) + .def("mark_cells_as_used", &ps::VolumeGrid::markCellsAsUsed) + + // options + .def("set_color", &ps::VolumeGrid::setColor) + .def("get_color", &ps::VolumeGrid::getColor) + .def("set_edge_color", &ps::VolumeGrid::setEdgeColor) + .def("get_edge_color", &ps::VolumeGrid::getEdgeColor) + .def("set_material", &ps::VolumeGrid::setMaterial, "Set material") + .def("get_material", &ps::VolumeGrid::getMaterial, "Get material") + .def("set_edge_width", &ps::VolumeGrid::setEdgeWidth, "Set edge width") + .def("get_edge_width", &ps::VolumeGrid::getEdgeWidth, "Get edge width") + + + // = quantities + + // Scalars + .def("add_node_scalar_quantity", &ps::VolumeGrid::addNodeScalarQuantity, + py::arg("name"), py::arg("values"), py::arg("data_type") = ps::DataType::STANDARD, + py::return_value_policy::reference) + + .def("add_cell_scalar_quantity", &ps::VolumeGrid::addCellScalarQuantity, + py::arg("name"), py::arg("values"), py::arg("data_type") = ps::DataType::STANDARD, + py::return_value_policy::reference) + + // add from a callable lambda + .def("add_node_scalar_quantity_from_callable", []( + ps::VolumeGrid& grid, + std::string name, + const std::function)>& func, + ps::DataType data_type) { + + // Polyscope's API uses raw buffer pointers, but we use Eigen mats for pybind11. + // Create a wrapper function that goes to/from the Eigen mats + auto wrapped_func = [&](const float* pos_ptr, float* result_ptr, uint64_t size) { + Eigen::Map> mapped_pos(pos_ptr, size, 3); + Eigen::Map mapped_result(result_ptr, size); + mapped_result = func(mapped_pos); + }; + + return grid.addNodeScalarQuantityFromBatchCallable(name, wrapped_func, data_type); + }, + py::arg("name"), py::arg("values"), py::arg("data_type") = ps::DataType::STANDARD, + py::return_value_policy::reference) + + .def("add_cell_scalar_quantity_from_callable", []( + ps::VolumeGrid& grid, + std::string name, + const std::function)>& func, + ps::DataType data_type) { + + // Polyscope's API uses raw buffer pointers, but we use Eigen mats for pybind11. + // Create a wrapper function that goes to/from the Eigen mats + auto wrapped_func = [&](const float* pos_ptr, float* result_ptr, uint64_t size) { + Eigen::Map> mapped_pos(pos_ptr, size, 3); + Eigen::Map mapped_result(result_ptr, size); + mapped_result = func(mapped_pos); + }; + + return grid.addNodeScalarQuantityFromBatchCallable(name, wrapped_func, data_type); + }, + py::arg("name"), py::arg("values"), py::arg("data_type") = ps::DataType::STANDARD, + py::return_value_policy::reference) + + // Colors + + // Vectors + + ; + + // Static adders and getters + m.def("register_volume_grid", overload_cast_()(&ps::registerVolumeGrid), py::arg("name"), + py::arg("gridNodeDim"), py::arg("boundMin"), py::arg("boundMax"), py::return_value_policy::reference); + + m.def("remove_volume_grid", &polyscope::removeVolumeGrid); + m.def("get_volume_grid", &polyscope::getVolumeGrid, py::return_value_policy::reference); + m.def("has_volume_grid", &polyscope::hasVolumeGrid); +} diff --git a/src/polyscope/__init__.py b/src/polyscope/__init__.py index 5035fed..9b606cb 100644 --- a/src/polyscope/__init__.py +++ b/src/polyscope/__init__.py @@ -10,5 +10,6 @@ from polyscope.point_cloud import * from polyscope.curve_network import * from polyscope.volume_mesh import * +from polyscope.volume_grid import * from polyscope.camera_view import * from polyscope.global_floating_quantity_structure import * diff --git a/src/polyscope/core.py b/src/polyscope/core.py index 44af23e..d898d53 100644 --- a/src/polyscope/core.py +++ b/src/polyscope/core.py @@ -399,6 +399,8 @@ def generate_camera_ray_corners(self): return self.instance.generate_camera_ray_ ## Small utilities +def glm3u(vals): + return psb.glm_uvec3(vals[0], vals[1], vals[2]) def glm3(vals): return psb.glm_vec3(vals[0], vals[1], vals[2]) def glm4(vals): diff --git a/src/polyscope/volume_grid.py b/src/polyscope/volume_grid.py new file mode 100644 index 0000000..b1a489d --- /dev/null +++ b/src/polyscope/volume_grid.py @@ -0,0 +1,205 @@ +import polyscope_bindings as psb +import numpy as np + +from polyscope.core import str_to_datatype, str_to_vectortype, str_to_param_coords_type, str_to_param_viz_style, glm3, glm3u +from polyscope.structure import Structure +from polyscope.common import process_quantity_args, process_scalar_args, process_color_args, process_vector_args, check_all_args_processed + +def process_volume_grid_scalar_args(structure, quantity, scalar_args, defined_on): + + val = check_and_pop_arg(scalar_args, 'enable_gridcube_viz') + if val is not None: + quantity.set_gridcube_viz_enabled(val) + + if defined_on == 'nodes': + + val = check_and_pop_arg(scalar_args, 'enable_isosurface_viz') + if val is not None: + quantity.set_isosurface_viz_enabled(val) + + val = check_and_pop_arg(scalar_args, 'isosurface_level') + if val is not None: + quantity.set_isosurface_level(val) + + val = check_and_pop_arg(scalar_args, 'isosurface_color') + if val is not None: + quantity.set_isosurface_color(val) + + val = check_and_pop_arg(scalar_args, 'slice_planes_affect_isosurface') + if val is not None: + quantity.set_slice_planes_affect_isosurface(val) + + val = check_and_pop_arg(scalar_args, 'register_isosurface_as_mesh_with_name') + if val is not None: + quantity.register_isosurface_as_mesh_with_name(val) + + +class VolumeGrid(Structure): + + # This class wraps a _reference_ to the underlying object, whose lifetime is managed by Polyscope + + # End users should not call this constrctor, use register_volume_grid instead + def __init__(self, name=None, node_dims=None, bound_low=None, bound_high=None, instance=None): + + super().__init__() + + if instance is not None: + # Wrap an existing instance + self.bound_instance = instance + + else: + # Create a new instance + + node_dims = glm3(node_dims) + bound_low = glm3(bound_low) + bound_high = glm3(bound_high) + + self.bound_instance = psb.register_volume_grid(name, node_dims, bound_low, bound_high) + + + def n_nodes(self): + return self.bound_instance.n_nodes() + def n_cells(self): + return self.bound_instance.n_cell() + def grid_spacing(self): + return self.bound_instance.grid_spacing() + def get_grid_node_dim(self): + return self.bound_instance.get_grid_node_dim().as_tuple() + def get_grid_cell_dim(self): + return self.bound_instance.get_grid_cell_dim().as_tuple() + def get_bound_min(self): + return self.bound_instance.get_bound_min() + def get_bound_max(self): + return self.bound_instance.get_bound_max() + + ## Structure management + + ## Options + + # Color + def set_color(self, val): + self.bound_instance.set_color(glm3(val)) + def get_color(self): + return self.bound_instance.get_color().as_tuple() + + # Edge Color + def set_edge_color(self, val): + self.bound_instance.set_edge_color(glm3(val)) + def get_edge_color(self): + return self.bound_instance.get_edge_color().as_tuple() + + # Edge width + def set_edge_width(self, val): + self.bound_instance.set_edge_width(val) + def get_edge_width(self): + return self.bound_instance.get_edge_width() + + # Material + def set_material(self, mat): + self.bound_instance.set_material(mat) + def get_material(self): + return self.bound_instance.get_material() + + ## Other stateful options + + def mark_nodes_as_used(self): + return self.bound_instance.mark_nodes_as_used() + def mark_cells_as_used(self): + return self.bound_instance.mark_cells_as_used() + + + ## Quantities + + # Scalar + + def add_scalar_quantity(self, name, values, defined_on='nodes', datatype="standard", **scalar_args): + + if defined_on == 'nodes': + + if len(values.shape) != self.get_grid_node_dim(): raise ValueError(f"'values' should be a {self.get_grid_node_dim()} array") + + q = self.bound_instance.add_node_scalar_quantity(name, values.flatten(), str_to_datatype(datatype)) + + elif defined_on == 'cells': + + if len(values.shape) != self.get_grid_cell_dim(): raise ValueError(f"'values' should be a {self.get_grid_cell_dim()} array") + + q = self.bound_instance.add_cell_scalar_quantity(name, values.flatten(), str_to_datatype(datatype)) + + else: + raise ValueError("bad `defined_on` value {}, should be one of ['nodes', 'cells']".format(defined_on)) + + + # process and act on additional arguments + # note: each step modifies the args dict and removes processed args + process_volume_grid_scalar_args(self, q, scalar_args, defined_on) + process_quantity_args(self, q, scalar_args) + process_scalar_args(self, q, scalar_args) + check_all_args_processed(self, q, scalar_args) + + + def add_scalar_quantity_from_callable(self, name, func, defined_on='nodes', datatype="standard", **scalar_args): + + if defined_on == 'nodes': + + q = self.bound_instance.add_node_scalar_quantity_from_callable(name, func, str_to_datatype(datatype)) + + elif defined_on == 'cells': + + q = self.bound_instance.add_cell_scalar_quantity_from_callable(name, func, str_to_datatype(datatype)) + + else: + raise ValueError("bad `defined_on` value {}, should be one of ['nodes', 'cells']".format(defined_on)) + + + # process and act on additional arguments + # note: each step modifies the args dict and removes processed args + process_volume_grid_scalar_args(self, q, scalar_args, defined_on) + process_quantity_args(self, q, scalar_args) + process_scalar_args(self, q, scalar_args) + check_all_args_processed(self, q, scalar_args) + + +def register_volume_grid(name, bound_low, bound_high, node_dims, enabled=None, color=None, edge_color=None, edge_width=None, material=None, transparency=None): + + """Register a new volume grid""" + + if not psb.is_initialized(): + raise RuntimeError("Polyscope has not been initialized") + + p = VolumeGrid(name, node_dims=node_dims, bound_low=bound_low, bound_high=bound_high) + + # == Apply options + if enabled is not None: + p.set_enabled(enabled) + if color is not None: + p.set_color(color) + if edge_color is not None: + p.set_edge_color(edge_color) + if edge_width is not None: + p.set_edge_width(edge_width) + if material is not None: + p.set_material(material) + if transparency is not None: + p.set_transparency(transparency) + + return p + +def remove_volume_grid(name, error_if_absent=True): + """Remove a volume grid by name""" + psb.remove_volume_grid(name, error_if_absent) + +def get_volume_grid(name): + """Get volume grid by name""" + if not has_volume_grid(name): + raise ValueError("no volume grid with name " + str(name)) + + raw = psb.get_volume_grid(name) + + # Wrap the instance + return VolumeGrid(instance=raw) + +def has_volume_grid(name): + """Check if a volume grid exists by name""" + return psb.has_volume_grid(name) + diff --git a/test/polyscope_test.py b/test/polyscope_test.py index 3427bd8..485bbe0 100644 --- a/test/polyscope_test.py +++ b/test/polyscope_test.py @@ -1563,6 +1563,210 @@ def test_vector(self): ps.remove_all_structures() +class TestVolumeGrid(unittest.TestCase): + + def test_add_remove(self): + + # add + n = ps.register_volume_grid("test_grid", (0.,0.,0,), (1., 1., 1.), (10,12,14)) + self.assertTrue(ps.has_volume_grid("test_grid")) + self.assertFalse(ps.has_volume_grid("nope")) + self.assertEqual(n.n_nodes(), 10*12*14) + self.assertEqual(n.n_cells(), (10-1)*(12-1)*(14-1)) + + # remove by name + ps.register_volume_grid("test_grid2", (0.,0.,0,), (1., 1., 1.), (10,12,14)) + ps.remove_volume_grid("test_grid2") + self.assertTrue(ps.has_volume_grid("test_grid")) + self.assertFalse(ps.has_volume_grid("test_grid2")) + + # remove by ref + c = ps.register_volume_grid("test_grid2", (0.,0.,0,), (1., 1., 1.), (10,12,14)) + c.remove() + self.assertTrue(ps.has_volume_grid("test_grid")) + self.assertFalse(ps.has_volume_grid("test_grid2")) + + # get by name + ps.register_volume_grid("test_grid3", (0.,0.,0,), (1., 1., 1.), (10,12,14)) + p = ps.get_volume_grid("test_grid3") # should be wrapped instance, not underlying PSB instance + self.assertTrue(isinstance(p, ps.VolumeGrid)) + + ps.remove_all_structures() + + def test_render(self): + + ps.register_volume_grid("test_grid", (0.,0.,0,), (1., 1., 1.), (10,12,14)) + ps.show(3) + ps.remove_all_structures() + + def test_options(self): + + p = ps.register_volume_grid("test_grid", (0.,0.,0,), (1., 1., 1.), (10,12,14)) + + # misc getters + p.n_nodes() + p.n_cells() + p.grid_spacing() + self.assertEqual(p.get_grid_node_dim(), 10*12*14) + self.assertEqual(p.get_grid_cell_dim(), (10-1)*(12-1)*(14-1)) + self.assertEqual(p.get_bound_min(), np.array((0., 0., 0.))) + self.assertEqual(p.get_bound_max(), np.array((1., 1., 1.))) + + # Set enabled + p.set_enabled() + p.set_enabled(False) + p.set_enabled(True) + self.assertTrue(p.is_enabled()) + + # Color + color = (0.3, 0.3, 0.5) + p.set_color(color) + ret_color = p.get_color() + for i in range(3): + self.assertAlmostEqual(ret_color[i], color[i]) + + # Edge color + color = (0.1, 0.5, 0.5) + p.set_edge_color(color) + ret_color = p.get_edge_color() + for i in range(3): + self.assertAlmostEqual(ret_color[i], color[i]) + + ps.show(3) + + # Edge width + p.set_edge_width(1.5) + ps.show(3) + self.assertAlmostEqual(p.get_edge_width(), 1.5) + + # Material + p.set_material("candy") + self.assertEqual("candy", p.get_material()) + p.set_material("clay") + + # Transparency + p.set_transparency(0.8) + self.assertAlmostEqual(0.8, p.get_transparency()) + + # Set with optional arguments + p2 = ps.register_volume_mesh("test_grid", (0.,0.,0,), (1., 1., 1.), (10,12,14), + enabled=True, material='wax', color=(1., 0., 0.), edge_color=(0.5, 0.5, 0.5), edge_width=0.5, transparency=0.9) + + ps.show(3) + + ps.remove_all_structures() + ps.set_transparency_mode('none') + + def test_transform(self): + + p = ps.register_volume_grid("test_grid", (0.,0.,0,), (1., 1., 1.), (10,12,14)) + test_transforms(self,p) + ps.remove_all_structures() + + def test_slice_plane(self): + + p = ps.register_volume_grid("test_grid", (0.,0.,0,), (1., 1., 1.), (10,12,14)) + + plane = ps.add_scene_slice_plane() + p.set_cull_whole_elements(True) + ps.show(3) + p.set_cull_whole_elements(False) + ps.show(3) + + p.set_ignore_slice_plane(plane, True) + self.assertEqual(True, p.get_ignore_slice_plane(plane)) + p.set_ignore_slice_plane(plane.get_name(), False) + self.assertEqual(False, p.get_ignore_slice_plane(plane.get_name())) + + ps.show(3) + + ps.remove_all_structures() + ps.remove_last_scene_slice_plane() + + + def test_scalar(self): + node_dim = (10,12,14) + cell_dim = (10-1,12-1,14-1) + p = ps.register_volume_grid("test_grid", (0.,0.,0,), (1., 1., 1.), node_dim) + + for on in ['nodes', 'cells']: + + if on == 'nodes': + vals = np.random.rand(node_dim) + elif on == 'cells': + vals = np.random.rand(cell_dim) + + p.add_scalar_quantity("test_vals", vals, defined_on=on) + p.add_scalar_quantity("test_vals2", vals, defined_on=on, enabled=True) + p.add_scalar_quantity("test_vals_with_range", vals, defined_on=on, vminmax=(-5., 5.), enabled=True) + p.add_scalar_quantity("test_vals_with_datatype", vals, defined_on=on, enabled=True, datatype='symmetric') + p.add_scalar_quantity("test_vals_with_cmap", vals, defined_on=on, enabled=True, cmap='blues', enable_gridcube_viz=False) + + ps.show(3) + + # test some additions/removal while we're at it + p.remove_quantity("test_vals") + p.remove_quantity("not_here") # should not error + p.remove_all_quantities() + p.remove_all_quantities() + + ps.remove_all_structures() + + def test_scalar_from_callable(self): + node_dim = (10,12,14) + cell_dim = (10-1,12-1,14-1) + p = ps.register_volume_grid("test_grid", (0.,0.,0,), (1., 1., 1.), node_dim) + + def sphere_sdf(pts): return np.linalg.norm(pts, axis=-1) - 1. + + for on in ['nodes', 'cells']: + + p.add_scalar_quantity_from_callable("test_vals", sphere_sdf, defined_on=on) + p.add_scalar_quantity_from_callable("test_vals2", sphere_sdf, defined_on=on, enabled=True) + p.add_scalar_quantity_from_callable("test_vals_with_range", sphere_sdf, defined_on=on, vminmax=(-5., 5.), enabled=True) + p.add_scalar_quantity_from_callable("test_vals_with_datatype", sphere_sdf, defined_on=on, enabled=True, datatype='symmetric') + p.add_scalar_quantity_from_callable("test_vals_with_cmap", sphere_sdf, defined_on=on, enabled=True, cmap='blues', enable_gridcube_viz=False) + + ps.show(3) + + # test some additions/removal while we're at it + p.remove_quantity("test_vals") + p.remove_quantity("not_here") # should not error + p.remove_all_quantities() + p.remove_all_quantities() + + ps.remove_all_structures() + + def test_scalar_isosurface_array(self): + node_dim = (10,12,14) + + p = ps.register_volume_grid("test_grid", (0.,0.,0,), (1., 1., 1.), node_dim) + vals = np.random.rand(node_dim) + + p.add_scalar_quantity("test_vals", vals, defined_on='nodes', enabled=True, + enable_gridcube_viz=False, enable_isosurface_viz=True, + isosurface_level=-0.2, isosurface_color=(0.5,0.6,0.7), + slice_planes_affect_isosurface=False, + register_isosurface_as_mesh_with_name="isomesh") + + ps.remove_all_structures() + + def test_scalar_isosurface_callable(self): + node_dim = (10,12,14) + + p = ps.register_volume_grid("test_grid", (0.,0.,0,), (1., 1., 1.), node_dim) + def sphere_sdf(pts): return np.linalg.norm(pts, axis=-1) - 1. + + p.add_scalar_quantity_from_callable("test_vals", sphere_sdf, defined_on='nodes', enabled=True, + enable_gridcube_viz=False, enable_isosurface_viz=True, + isosurface_level=-0.2, isosurface_color=(0.5,0.6,0.7), + slice_planes_affect_isosurface=False, + register_isosurface_as_mesh_with_name="isomesh") + + ps.remove_all_structures() + + + class TestCameraView(unittest.TestCase): def generate_parameters(self): From 8e7a4fd235117018b384869d7c6f3dd117b6e29a Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sun, 22 Oct 2023 22:48:29 -0700 Subject: [PATCH 63/88] finish volume grid initial implementation --- src/cpp/volume_grid.cpp | 4 +++- src/polyscope/volume_grid.py | 12 ++++++------ test/polyscope_test.py | 16 ++++++++-------- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/cpp/volume_grid.cpp b/src/cpp/volume_grid.cpp index 86a9268..b8f7684 100644 --- a/src/cpp/volume_grid.cpp +++ b/src/cpp/volume_grid.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include "Eigen/Dense" @@ -26,7 +27,8 @@ void bind_volume_grid(py::module& m) { .def("set_isosurface_color", &ps::VolumeGridNodeScalarQuantity::setIsosurfaceColor) .def("set_slice_planes_affect_isosurface", &ps::VolumeGridNodeScalarQuantity::setSlicePlanesAffectIsosurface) .def("register_isosurface_as_mesh_with_name", - [](ps::VolumeGridNodeScalarQuantity& x, std::string name) { return x.registerIsosurfaceAsMesh(name); }) + [](ps::VolumeGridNodeScalarQuantity& x, std::string name) { return x.registerIsosurfaceAsMesh(name); }, + py::return_value_policy::reference) ; bindScalarQuantity(m, "VolumeGridCellScalarQuantity") diff --git a/src/polyscope/volume_grid.py b/src/polyscope/volume_grid.py index b1a489d..bfb483f 100644 --- a/src/polyscope/volume_grid.py +++ b/src/polyscope/volume_grid.py @@ -3,7 +3,7 @@ from polyscope.core import str_to_datatype, str_to_vectortype, str_to_param_coords_type, str_to_param_viz_style, glm3, glm3u from polyscope.structure import Structure -from polyscope.common import process_quantity_args, process_scalar_args, process_color_args, process_vector_args, check_all_args_processed +from polyscope.common import process_quantity_args, process_scalar_args, process_color_args, process_vector_args, check_all_args_processed, check_and_pop_arg def process_volume_grid_scalar_args(structure, quantity, scalar_args, defined_on): @@ -23,7 +23,7 @@ def process_volume_grid_scalar_args(structure, quantity, scalar_args, defined_on val = check_and_pop_arg(scalar_args, 'isosurface_color') if val is not None: - quantity.set_isosurface_color(val) + quantity.set_isosurface_color(glm3(val)) val = check_and_pop_arg(scalar_args, 'slice_planes_affect_isosurface') if val is not None: @@ -50,7 +50,7 @@ def __init__(self, name=None, node_dims=None, bound_low=None, bound_high=None, i else: # Create a new instance - node_dims = glm3(node_dims) + node_dims = glm3u(node_dims) bound_low = glm3(bound_low) bound_high = glm3(bound_high) @@ -60,7 +60,7 @@ def __init__(self, name=None, node_dims=None, bound_low=None, bound_high=None, i def n_nodes(self): return self.bound_instance.n_nodes() def n_cells(self): - return self.bound_instance.n_cell() + return self.bound_instance.n_cells() def grid_spacing(self): return self.bound_instance.grid_spacing() def get_grid_node_dim(self): @@ -116,13 +116,13 @@ def add_scalar_quantity(self, name, values, defined_on='nodes', datatype="standa if defined_on == 'nodes': - if len(values.shape) != self.get_grid_node_dim(): raise ValueError(f"'values' should be a {self.get_grid_node_dim()} array") + if values.shape != self.get_grid_node_dim(): raise ValueError(f"'values' should be a {self.get_grid_node_dim()} array") q = self.bound_instance.add_node_scalar_quantity(name, values.flatten(), str_to_datatype(datatype)) elif defined_on == 'cells': - if len(values.shape) != self.get_grid_cell_dim(): raise ValueError(f"'values' should be a {self.get_grid_cell_dim()} array") + if values.shape != self.get_grid_cell_dim(): raise ValueError(f"'values' should be a {self.get_grid_cell_dim()} array") q = self.bound_instance.add_cell_scalar_quantity(name, values.flatten(), str_to_datatype(datatype)) diff --git a/test/polyscope_test.py b/test/polyscope_test.py index 485bbe0..dedab45 100644 --- a/test/polyscope_test.py +++ b/test/polyscope_test.py @@ -1607,10 +1607,10 @@ def test_options(self): p.n_nodes() p.n_cells() p.grid_spacing() - self.assertEqual(p.get_grid_node_dim(), 10*12*14) - self.assertEqual(p.get_grid_cell_dim(), (10-1)*(12-1)*(14-1)) - self.assertEqual(p.get_bound_min(), np.array((0., 0., 0.))) - self.assertEqual(p.get_bound_max(), np.array((1., 1., 1.))) + self.assertTrue((p.get_grid_node_dim() == (10,12,14))) + self.assertTrue((p.get_grid_cell_dim() == ((10-1),(12-1),(14-1)))) + self.assertTrue((p.get_bound_min() == np.array((0., 0., 0.))).all()) + self.assertTrue((p.get_bound_max() == np.array((1., 1., 1.))).all()) # Set enabled p.set_enabled() @@ -1649,7 +1649,7 @@ def test_options(self): self.assertAlmostEqual(0.8, p.get_transparency()) # Set with optional arguments - p2 = ps.register_volume_mesh("test_grid", (0.,0.,0,), (1., 1., 1.), (10,12,14), + p2 = ps.register_volume_grid("test_grid", (0.,0.,0,), (1., 1., 1.), (10,12,14), enabled=True, material='wax', color=(1., 0., 0.), edge_color=(0.5, 0.5, 0.5), edge_width=0.5, transparency=0.9) ps.show(3) @@ -1692,9 +1692,9 @@ def test_scalar(self): for on in ['nodes', 'cells']: if on == 'nodes': - vals = np.random.rand(node_dim) + vals = np.random.rand(*node_dim) elif on == 'cells': - vals = np.random.rand(cell_dim) + vals = np.random.rand(*cell_dim) p.add_scalar_quantity("test_vals", vals, defined_on=on) p.add_scalar_quantity("test_vals2", vals, defined_on=on, enabled=True) @@ -1741,7 +1741,7 @@ def test_scalar_isosurface_array(self): node_dim = (10,12,14) p = ps.register_volume_grid("test_grid", (0.,0.,0,), (1., 1., 1.), node_dim) - vals = np.random.rand(node_dim) + vals = np.random.rand(*node_dim) p.add_scalar_quantity("test_vals", vals, defined_on='nodes', enabled=True, enable_gridcube_viz=False, enable_isosurface_viz=True, From 06ba02795d4a1395c567e1b3984ab782bd15b511 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Thu, 26 Oct 2023 17:38:23 -0700 Subject: [PATCH 64/88] add a few font-related bindings --- src/cpp/imgui.cpp | 66 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 46 insertions(+), 20 deletions(-) diff --git a/src/cpp/imgui.cpp b/src/cpp/imgui.cpp index 6589b42..0b9afaf 100644 --- a/src/cpp/imgui.cpp +++ b/src/cpp/imgui.cpp @@ -1,9 +1,9 @@ #include "imgui.h" #include +#include #include #include -#include namespace py = pybind11; @@ -71,15 +71,14 @@ void bind_imgui_structs(py::module& m) { // ImGuiIO py::class_(m, "ImGuiIO") - // .def(py::init()) - .def_readwrite("DisplaySize" ,&ImGuiIO::DisplaySize ) + .def_readwrite("DisplaySize" ,&ImGuiIO::DisplaySize ) .def_readwrite("DeltaTime" ,&ImGuiIO::DeltaTime ) .def_readwrite("IniSavingRate" ,&ImGuiIO::IniSavingRate ) .def_readwrite("IniFilename" ,&ImGuiIO::IniFilename ) .def_readwrite("MouseDoubleClickTime" ,&ImGuiIO::MouseDoubleClickTime ) .def_readwrite("MouseDoubleClickMaxDist" ,&ImGuiIO::MouseDoubleClickMaxDist ) .def_readwrite("MouseDragThreshold" ,&ImGuiIO::MouseDragThreshold ) - .def_property_readonly("KeyMap" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{ImGuiKey_COUNT, o.KeyMap, ob};}) + .def_property_readonly("KeyMap" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{ImGuiKey_COUNT, o.KeyMap, ob};}) .def_readwrite("KeyRepeatDelay" ,&ImGuiIO::KeyRepeatDelay ) .def_readwrite("KeyRepeatRate" ,&ImGuiIO::KeyRepeatRate ) .def_readwrite("Fonts" ,&ImGuiIO::Fonts ) @@ -95,15 +94,15 @@ void bind_imgui_structs(py::module& m) { .def_readwrite("ConfigWindowsMoveFromTitleBarOnly" ,&ImGuiIO::ConfigWindowsMoveFromTitleBarOnly ) .def_readwrite("ConfigMemoryCompactTimer" ,&ImGuiIO::ConfigMemoryCompactTimer ) .def_readwrite("MousePos" ,&ImGuiIO::MousePos ) - .def_property_readonly("MouseDown" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{5, o.MouseDown , ob};}) + .def_property_readonly("MouseDown" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{5, o.MouseDown , ob};}) .def_readwrite("MouseWheel" ,&ImGuiIO::MouseWheel ) .def_readwrite("MouseWheelH" ,&ImGuiIO::MouseWheelH ) .def_readwrite("KeyCtrl" ,&ImGuiIO::KeyCtrl ) .def_readwrite("KeyShift" ,&ImGuiIO::KeyShift ) .def_readwrite("KeyAlt" ,&ImGuiIO::KeyAlt ) .def_readwrite("KeySuper" ,&ImGuiIO::KeySuper ) - .def_property_readonly("KeysDown" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{512, o.KeysDown , ob};}) - .def_property_readonly("NavInputs" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{ImGuiNavInput_COUNT, o.NavInputs , ob};}) + .def_property_readonly("KeysDown" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{512, o.KeysDown , ob};}) + .def_property_readonly("NavInputs" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{ImGuiNavInput_COUNT, o.NavInputs , ob};}) .def_readwrite("WantCaptureMouse" ,&ImGuiIO::WantCaptureMouse ) .def_readwrite("WantCaptureKeyboard" ,&ImGuiIO::WantCaptureKeyboard ) .def_readwrite("WantTextInput" ,&ImGuiIO::WantTextInput ) @@ -122,22 +121,22 @@ void bind_imgui_structs(py::module& m) { .def_readwrite("KeyMods" ,&ImGuiIO::KeyMods ) .def_readwrite("KeyModsPrev" ,&ImGuiIO::KeyModsPrev ) .def_readwrite("MousePosPrev" ,&ImGuiIO::MousePosPrev ) - .def_property_readonly("MouseClickedPos" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{5, o.MouseClickedPos, ob};}) - .def_property_readonly("MouseClickedTime" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{5, o.MouseClickedTime, ob};}) - .def_property_readonly("MouseClicked" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{5, o.MouseClicked, ob};}) - .def_property_readonly("MouseDoubleClicked" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{5, o.MouseDoubleClicked, ob};}) - .def_property_readonly("MouseClickedCount" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{5, o.MouseClickedCount, ob};}) - .def_property_readonly("MouseClickedLastCount" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{5, o.MouseClickedLastCount, ob};}) - .def_property_readonly("MouseReleased" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{5, o.MouseReleased, ob};}) - .def_property_readonly("MouseDownOwned" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{5, o.MouseDownOwned, ob};}) + .def_property_readonly("MouseClickedPos" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{5, o.MouseClickedPos, ob};}) + .def_property_readonly("MouseClickedTime" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{5, o.MouseClickedTime, ob};}) + .def_property_readonly("MouseClicked" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{5, o.MouseClicked, ob};}) + .def_property_readonly("MouseDoubleClicked" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{5, o.MouseDoubleClicked, ob};}) + .def_property_readonly("MouseClickedCount" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{5, o.MouseClickedCount, ob};}) + .def_property_readonly("MouseClickedLastCount" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{5, o.MouseClickedLastCount, ob};}) + .def_property_readonly("MouseReleased" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{5, o.MouseReleased, ob};}) + .def_property_readonly("MouseDownOwned" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{5, o.MouseDownOwned, ob};}) .def_property_readonly("MouseDownOwnedUnlessPopupClose" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{5, o.MouseDownOwnedUnlessPopupClose, ob};}) - .def_property_readonly("MouseDownDuration" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{5, o.MouseDownDuration, ob};}) - .def_property_readonly("MouseDownDurationPrev" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{5, o.MouseDownDurationPrev, ob};}) + .def_property_readonly("MouseDownDuration" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{5, o.MouseDownDuration, ob};}) + .def_property_readonly("MouseDownDurationPrev" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{5, o.MouseDownDurationPrev, ob};}) .def_property_readonly("MouseDragMaxDistanceAbs" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{5, o.MouseDragMaxDistanceAbs, ob};}) .def_property_readonly("MouseDragMaxDistanceSqr" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{5, o.MouseDragMaxDistanceSqr, ob};}) - .def_property_readonly("KeysDownDuration" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{512, o.KeysDownDuration, ob};}) - .def_property_readonly("KeysDownDurationPrev" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{512, o.KeysDownDurationPrev, ob};}) - .def_property_readonly("NavInputsDownDuration" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{ImGuiNavInput_COUNT, o.NavInputsDownDuration, ob};}) + .def_property_readonly("KeysDownDuration" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{512, o.KeysDownDuration, ob};}) + .def_property_readonly("KeysDownDurationPrev" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{512, o.KeysDownDurationPrev, ob};}) + .def_property_readonly("NavInputsDownDuration" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{ImGuiNavInput_COUNT, o.NavInputsDownDuration, ob};}) .def_property_readonly("NavInputsDownDurationPrev" , [](py::object& ob) { ImGuiIO& o = ob.cast(); return py::array{ImGuiNavInput_COUNT, o.NavInputsDownDurationPrev, ob};}) .def_readwrite("PenPressure" ,&ImGuiIO::PenPressure ) .def_readwrite("AppFocusLost" ,&ImGuiIO::AppFocusLost ) @@ -145,6 +144,23 @@ void bind_imgui_structs(py::module& m) { .def_readwrite("InputQueueCharacters" ,&ImGuiIO::InputQueueCharacters ) ; + + + py::class_(m, "ImFontAtlas") + .def("AddFontFromFileTTF", + [](py::object& ob, std::string filename, float size_pixels) { ImFontAtlas& o = ob.cast(); return o.AddFontFromFileTTF(filename.c_str(), size_pixels);}, + py::return_value_policy::reference) + + // TODO add bindings to the rest of the font functions + + ; + + py::class_(m, "ImFont") + + // TODO add bindings to the rest of the font functions + + ; + } void bind_imgui_methods(py::module& m) { @@ -323,6 +339,16 @@ void bind_imgui_methods(py::module& m) { py::arg("center_y_ratio") = 0.5f); // Parameters stacks (shared) + IMGUI_API void PushFont(ImFont* font); // use NULL as a shortcut to push default font + IMGUI_API void PopFont(); + m.def( + "PushFont", + [](ImFont* font) { ImGui::PushFont(font); }, + py::arg("font")); + m.def( + "PopFont", + []() { ImGui::PopFont(); } + ); m.def( "PushStyleColor", [](ImGuiCol idx, ImU32 col) { ImGui::PushStyleColor(idx, col); }, From 5644548ef3225a92c7657cd811c80335f81d2313 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sat, 28 Oct 2023 17:37:44 -0700 Subject: [PATCH 65/88] update dep --- deps/polyscope | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/polyscope b/deps/polyscope index 6b4b60d..39fdbda 160000 --- a/deps/polyscope +++ b/deps/polyscope @@ -1 +1 @@ -Subproject commit 6b4b60df9f74d4b19de34926a25f4ffa6b4b7629 +Subproject commit 39fdbda277d147fd70c4bc344250d8b578f1ce12 From f434d8738ad40a0e1160d47d654219e24f61966f Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Tue, 31 Oct 2023 23:25:30 -0700 Subject: [PATCH 66/88] update to premultiplied --- deps/polyscope | 2 +- src/cpp/floating_quantities.cpp | 2 ++ src/polyscope/common.py | 6 ++++-- test/polyscope_test.py | 4 ++-- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/deps/polyscope b/deps/polyscope index 39fdbda..2be9e09 160000 --- a/deps/polyscope +++ b/deps/polyscope @@ -1 +1 @@ -Subproject commit 39fdbda277d147fd70c4bc344250d8b578f1ce12 +Subproject commit 2be9e096caea9e4a1142d3b7703c7e0ba00cad55 diff --git a/src/cpp/floating_quantities.cpp b/src/cpp/floating_quantities.cpp index b9d015e..c3773f9 100644 --- a/src/cpp/floating_quantities.cpp +++ b/src/cpp/floating_quantities.cpp @@ -33,6 +33,7 @@ void bind_floating_quantities(py::module& m) { auto qColorImage = bindColorQuantity(m, "ColorImageQuantity"); addImageQuantityBindings(qColorImage); + qColorImage.def("set_is_premultiplied", &ps::ColorImageQuantity::setIsPremultiplied); // global / free-floating adders m.def("add_scalar_image_quantity", &ps::addScalarImageQuantity, @@ -62,6 +63,7 @@ void bind_floating_quantities(py::module& m) { auto qRawColorAlphaRenderImage = bindColorQuantity(m, "RawColorAlphaRenderImageQuantity"); qRawColorAlphaRenderImage.def("set_transparency", &ps::RawColorAlphaRenderImageQuantity::setTransparency, "Set transparency"); + qRawColorAlphaRenderImage.def("set_is_premultiplied", &ps::RawColorAlphaRenderImageQuantity::setIsPremultiplied); // global / free-floating adders m.def("add_depth_render_image_quantity", &ps::addDepthRenderImageQuantity, diff --git a/src/polyscope/common.py b/src/polyscope/common.py index f0fe265..dd3d655 100644 --- a/src/polyscope/common.py +++ b/src/polyscope/common.py @@ -46,8 +46,10 @@ def process_quantity_args(structure, quantity, quantity_args): # Process args, removing them from the dict if they are present def process_color_args(structure, quantity, color_args): - - pass # none yet + + val = check_and_pop_arg(color_args, 'is_premultiplied') + if val is not None: + quantity.set_is_premultiplied(val) # Process args, removing them from the dict if they are present diff --git a/test/polyscope_test.py b/test/polyscope_test.py index dedab45..9fe1cb2 100644 --- a/test/polyscope_test.py +++ b/test/polyscope_test.py @@ -1926,7 +1926,7 @@ def test_floating_color_alpha_images(self): cam.add_color_alpha_image_quantity("color_alpha_img", np.zeros((dimX, dimY, 4))) cam.add_color_alpha_image_quantity("color_alpha_img2", np.zeros((dimX, dimY, 4)), enabled=True, image_origin='lower_left', show_in_camera_billboard=True) - cam.add_color_alpha_image_quantity("color_alpha_img3", np.zeros((dimX, dimY, 4)), enabled=True, show_in_imgui_window=True, show_in_camera_billboard=False) + cam.add_color_alpha_image_quantity("color_alpha_img3", np.zeros((dimX, dimY, 4)), enabled=True, show_in_imgui_window=True, show_in_camera_billboard=False, is_premultiplied=True) cam.add_color_alpha_image_quantity("color_alpha_img4", np.zeros((dimX, dimY, 4)), enabled=True, show_fullscreen=True, show_in_camera_billboard=False) # true floating adder @@ -2034,7 +2034,7 @@ def test_floating_raw_color_alpha_render_images(self): colors = np.ones((dimX, dimY, 4)) cam.add_raw_color_alpha_render_image_quantity("render_img", depths, colors) - cam.add_raw_color_alpha_render_image_quantity("render_img2", depths, colors, enabled=True, image_origin='lower_left', transparency=0.7) + cam.add_raw_color_alpha_render_image_quantity("render_img2", depths, colors, enabled=True, image_origin='lower_left', transparency=0.7, is_premultiplied=True) # true floating adder ps.add_raw_color_alpha_render_image_quantity("render_img3", depths, colors, enabled=True, image_origin='lower_left', transparency=0.7) From 0997647b137409f4990992bf13ceb0955b956019 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Thu, 2 Nov 2023 09:26:09 -0700 Subject: [PATCH 67/88] add imgui disabled function --- src/cpp/imgui.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/cpp/imgui.cpp b/src/cpp/imgui.cpp index 0b9afaf..babe86e 100644 --- a/src/cpp/imgui.cpp +++ b/src/cpp/imgui.cpp @@ -1491,6 +1491,13 @@ void bind_imgui_methods(py::module& m) { m.def("LogFinish", []() { ImGui::LogFinish(); }); m.def("LogButtons", []() { ImGui::LogButtons(); }); + // Disabling + m.def("BeginDisabled", + [](bool disable) { + return ImGui::BeginDisabled(disable); + }, py::arg("disable")); + m.def("EndDisabled", []() { ImGui::EndDisabled(); }); + // Clipping m.def( "PushClipRect", From 416ceb2bcae8b870b42b0c872671e2a06e4cb42a Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Fri, 3 Nov 2023 02:04:06 -0700 Subject: [PATCH 68/88] cleanup and bugfixes to buffers --- src/cpp/utils.h | 11 +--- src/polyscope/device_interop.py | 107 +++++++++++++++++--------------- src/polyscope/structure.py | 9 ++- 3 files changed, 64 insertions(+), 63 deletions(-) diff --git a/src/cpp/utils.h b/src/cpp/utils.h index 8c9444e..44b4da0 100644 --- a/src/cpp/utils.h +++ b/src/cpp/utils.h @@ -101,6 +101,7 @@ py::class_ bindStructure(py::module& m, std::string name) { // structure basics s.def("remove", &StructureT::remove, "Remove the structure") + .def("get_name", [](StructureT& s) { return s.name; }, "Ge the name") .def("get_unique_prefix", &StructureT::uniquePrefix, "Get unique prefix") .def("set_enabled", &StructureT::setEnabled, "Enable the structure") .def("enable_isolate", &StructureT::enableIsolate, "Enable the structure, disable all of same type") @@ -130,13 +131,6 @@ py::class_ bindStructure(py::module& m, std::string name) { .def("get_transform", [](StructureT& s) { return glm2eigen(s.getTransform()); }, "get the current 4x4 transform matrix") .def("get_position", [](StructureT& s) { return glm2eigen(s.getPosition()); }, "get the position of the shape origin after transform") - // managed buffers - .def("get_buffer", [](StructureT& s, std::string name) { - - - - }, "Get a buffer by name") - // floating quantites .def("add_scalar_image_quantity", &StructureT::template addScalarImageQuantity, py::arg("name"), py::arg("dimX"), py::arg("dimY"), py::arg("values"), py::arg("imageOrigin")=ps::ImageOrigin::UpperLeft, py::arg("type")=ps::DataType::STANDARD, py::return_value_policy::reference) .def("add_color_image_quantity", &StructureT::template addColorImageQuantity, py::arg("name"), py::arg("dimX"), py::arg("dimY"), py::arg("values_rgb"), py::arg("imageOrigin")=ps::ImageOrigin::UpperLeft, py::return_value_policy::reference) @@ -178,8 +172,7 @@ py::class_ bindStructure(py::module& m, std::string name) { if (fqPtr) { return fqPtr->hasManagedBufferType(buffer_name); } - ps::exception("structure " + s.name + " has no quantity " + quantity_name); - return std::make_tuple(false, ps::ManagedBufferType::Float); // invalid, never executed + return std::make_tuple(false, ps::ManagedBufferType::Float); }, "has quantity managed buffer type"); diff --git a/src/polyscope/device_interop.py b/src/polyscope/device_interop.py index 57bd318..ae93622 100644 --- a/src/polyscope/device_interop.py +++ b/src/polyscope/device_interop.py @@ -123,6 +123,11 @@ def get_array_from_unknown_data(arr): def map_resource_and_get_array(handle): check_cudart_err(cudart.cudaGraphicsMapResources(1, handle, None)), return check_cudart_err(cudart.cudaGraphicsSubResourceGetMappedArray(handle, 0, 0)) + + def map_resource_and_get_pointer(handle): + check_cudart_err(cudart.cudaGraphicsMapResources(1, handle, None)), + raw_ptr, size = check_cudart_err(cudart.cudaGraphicsResourceGetMappedPointer(handle)) + return raw_ptr, size func_dict = { @@ -164,8 +169,11 @@ def map_resource_and_get_array(handle): # returns array # as in cudaGraphicsMapResources() + cudaGraphicsSubResourceGetMappedArray() - 'map_resource_and_get_array' : map_resource_and_get_array - , + 'map_resource_and_get_array' : map_resource_and_get_array, + + # returns ptr + # as in cudaGraphicsMapResources() + cudaGraphicsResourceGetMappedPointer() + 'map_resource_and_get_pointer' : map_resource_and_get_pointer, # returns a tuple (arr_ptr, shape, dtype, nbytes) @@ -173,6 +181,12 @@ def map_resource_and_get_array(handle): 'get_array_ptr' : lambda input_array: get_array_from_unknown_data(input_array), + # as in cudaMemcpy + 'memcpy' : lambda dst_ptr, src_ptr, size : + check_cudart_err(cuda.cudart.cudaMemcpy( + dst_ptr, src_ptr, size, cudart.cudaMemcpyKind.cudaMemcpyDeviceToDevice + )), + # as in cudaMemcpy2DToArray 'memcpy_2d' : lambda dst_ptr, src_ptr, width, height : @@ -209,37 +223,33 @@ class CUDAOpenGLMappedAttributeBuffer: # Roughly based on this, see for more goodies: https://gist.github.com/keckj/e37d312128eac8c5fca790ce1e7fc437 def __init__(self, gl_attribute_native_id, buffer_type): - # FIXME TODO - raise NotImplementedError("these calls need to be updated to use the new dictionary setup") - - ensure_device_interop_funcs_resolve() self.gl_attribute_native_id = gl_attribute_native_id self.buffer_type = buffer_type self.resource_handle = None self.cuda_buffer_ptr = None self.cuda_buffer_size = -1 + self.finished_init = False + ensure_device_interop_funcs_resolve() # Sanity checks if self.buffer_type != psb.DeviceBufferType.attribute: raise ValueError("device buffer type should be attribute") + # Register the buffer - self.resource_handle = check_cudart_err( - cudart.cudaGraphicsGLRegisterBuffer( - self.gl_attribute_native_id, - cudart.cudaGraphicsRegisterFlags.cudaGraphicsRegisterFlagsWriteDiscard - ) - ) + self.resource_handle = device_interop_funcs['register_gl_buffer'](self.gl_attribute_native_id) + + self.finished_init = True def __del__(self): - self.unregister() + if self.finished_init: + self.unregister() def unregister(self): self.unmap() - self.resource_handle = check_cudart_err( - cudart.cudaGraphicsUnregisterResource(self.resource_handle)) + device_interop_funcs['unregister_resource'](self.resource_handle) def map(self): """ @@ -249,20 +259,13 @@ def map(self): if self.cuda_buffer_ptr is not None: return - device_interop_funcs['cudaGraphicsMapResources'](self.resource_handle) - # check_cudart_err(cudart.cudaGraphicsMapResources(1, self.resource_handle, None)) - - ptr, size = check_cudart_err(cudart.cudaGraphicsResourceGetMappedPointer(self.resource_handle)) - - self.cuda_buffer_ptr = cupy.cuda.MemoryPointer(cupy.cuda.UnownedMemory(ptr, size, self), 0) - self.cuda_buffer_size = size - + self.cuda_buffer_ptr, self.cuda_buffer_size = device_interop_funcs['map_resource_and_get_pointer'](self.resource_handle) def unmap(self): if not hasattr(self, 'cuda_buffer_ptr') or self.cuda_buffer_ptr is None: return - check_cudart_err(cudart.cudaGraphicsUnmapResources(1, self.resource_handle, None)) + device_interop_funcs['unmap_resource'](self.resource_handle) self.cuda_buffer_ptr = None self.cuda_buffer_size = -1 @@ -271,26 +274,21 @@ def set_data_from_array(self, arr, buffer_size_in_bytes=None, expected_shape=Non self.map() - cupy_arr = self.get_array_from_unknown_data(arr) - - # do some shape & type checks - if expected_dtype is not None and cupy_arr.dtype != expected_dtype: - raise ValueError(f"dlpack array has wrong dtype, expected {expected_dtype} but got {cupy_arr.dtype}") - - if expected_shape is not None and cupy_arr.shape != expected_shape: - raise ValueError(f"dlpack array has wrong shape, expected {expected_shape} but got {cupy_arr.shape}") + # cupy_arr = self.get_array_from_unknown_data(arr) - if cupy_arr.nbytes != self.cuda_buffer_size: + # access the input array + arr_ptr, arr_shape, arr_dtype, arr_nbytes = device_interop_funcs['get_array_ptr'](arr) + + if arr_nbytes is not None and arr_nbytes != self.cuda_buffer_size: # if cupy_arr has the right size/dtype, it should have exactly the same # number of bytes as the destination. This is just lazily saving us # from repeating the math, and also directly validates the copy we # are about to do below. - # raise ValueError(f"Mapped buffer write has wrong size, expected {cupy_arr.nbytes} bytes but got {self.cuda_buffer_size} bytes. Could it be the wrong size/shape or wrong dtype?") - pass + raise ValueError(f"Mapped buffer write has wrong size, expected {arr_nbytes} bytes but got {self.cuda_buffer_size} bytes. Could it be the wrong size/shape or wrong dtype?") - # perform the actualy copy - self.cuda_buffer_ptr.copy_from_device(cupy_arr.data, self.cuda_buffer_size) + # perform the actual copy + device_interop_funcs['memcpy'](self.cuda_buffer_ptr, arr_ptr, self.cuda_buffer_size) self.unmap() @@ -299,12 +297,14 @@ class CUDAOpenGLMappedTextureBuffer: def __init__(self, gl_attribute_native_id, buffer_type): - ensure_device_interop_funcs_resolve() self.gl_attribute_native_id = gl_attribute_native_id self.buffer_type = buffer_type self.resource_handle = None self.cuda_buffer_array = None # NOTE: 'array' has a special cuda meaning here relating to texture memory + self.finished_init = False + + ensure_device_interop_funcs_resolve() # Register the buffer @@ -322,10 +322,13 @@ def __init__(self, gl_attribute_native_id, buffer_type): elif self.buffer_type == psb.DeviceBufferType.texture3d: # TODO raise ValueError("3d texture writes are not implemented") + + self.finished_init = True def __del__(self): - self.unregister() + if self.finished_init: + self.unregister() def unregister(self): self.unmap() @@ -389,18 +392,20 @@ def set_data_from_array(self, arr, texture_dims, entry_size_in_bytes): # if we got shape info from the source array, use it to do sanity checks if arr_shape is not None: - - # size of the input array - arr_size = 1 - for s in arr_shape: arr_size *= s - - # size of the destination array - dst_size = 1 - for s in texture_dims: - if s > 0: dst_size *= s - - if arr_size != dst_size: - raise ValueError(f"Mapped buffer write has wrong size, destination buffer has {dst_size} elements, but source buffer has {arr_size}.") + # TODO this test is broken as-written, doesn't account for presence/asbsence of channel count + pass + + # # size of the input array + # arr_size = 1 + # for s in arr_shape: arr_size *= s + # + # # size of the destination array + # dst_size = 1 + # for s in texture_dims: + # if s > 0: dst_size *= s + # + # if arr_size != dst_size: + # raise ValueError(f"Mapped buffer write has wrong size, destination buffer has {dst_size} elements, but source buffer has {arr_size}.") # if we got bytesize info from the source AND destination array, use it to do more sanity checks diff --git a/src/polyscope/structure.py b/src/polyscope/structure.py index f1f24ab..25049af 100644 --- a/src/polyscope/structure.py +++ b/src/polyscope/structure.py @@ -12,6 +12,9 @@ def __init__(self): ## Structure management + def get_name(self): + '''Get the name''' + return self.bound_instance.get_name() def remove(self): '''Remove the structure itself''' self.bound_instance.remove() @@ -85,11 +88,11 @@ def get_quantity_buffer(self, quantity_name, buffer_name): present, buffer_type = self.bound_instance.has_quantity_buffer_type(quantity_name, buffer_name) if not present: - if self == psb.get_global_floating_quantity_structure(): + if self.get_name() == psb.get_global_floating_quantity_structure().get_name(): # give a more informative error if this was called on the global floating quantity, becuase it is particularly easy for users to get confused and call this function at the global scope rather than on a quantity - raise ValueError("Quantity has no buffer named " + name + ". NOTE: calling polyscope.get_quantity_buffer() is for global floating quantities only, call structure.get_quantity_buffer() to get buffers for a quantity added to some structure.") + raise ValueError(f"There is no quantity {quantity_name} with buffer named {buffer_name}. NOTE: calling polyscope.get_quantity_buffer() is for global floating quantities only, call structure.get_quantity_buffer() to get buffers for a quantity added to some structure.") else: - raise ValueError(f"quantity {quantity_name} has no buffer named {buffer_name}") + raise ValueError(f"Structure {self.get_name()} does not have a quantity {quantity_name} with buffer named {buffer_name}") return { psb.ManagedBufferType.Float : lambda q,n,t : ManagedBuffer(self.bound_instance.get_quantity_buffer_Float (q,n), t), From 2d7472f8ddc52e1792e218325255c69797190ea8 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Fri, 3 Nov 2023 02:04:12 -0700 Subject: [PATCH 69/88] update dep --- deps/polyscope | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/polyscope b/deps/polyscope index 2be9e09..2cd38b7 160000 --- a/deps/polyscope +++ b/deps/polyscope @@ -1 +1 @@ -Subproject commit 2be9e096caea9e4a1142d3b7703c7e0ba00cad55 +Subproject commit 2cd38b7496a1e8ff79f615920de2980295bedef7 From 360135a7ee7248eb3c5c916852a774cf43af51c2 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Wed, 8 Nov 2023 08:55:01 -0800 Subject: [PATCH 70/88] add imdrawlist bindings --- src/cpp/imgui.cpp | 213 ++++++++++++++++++++++++++++++- src/polyscope_bindings/imgui.pyi | 25 ++++ 2 files changed, 237 insertions(+), 1 deletion(-) diff --git a/src/cpp/imgui.cpp b/src/cpp/imgui.cpp index babe86e..0bc318d 100644 --- a/src/cpp/imgui.cpp +++ b/src/cpp/imgui.cpp @@ -386,7 +386,7 @@ void bind_imgui_methods(py::module& m) { py::arg("idx"), py::arg("alpha_mul") = 1.0f); m.def( - "GetColorU32", [](const ImVec4& col) { return ImGui::GetColorU32(col); }, py::arg("col")); + "GetColorU32", [](const Vec4T& col) { return ImGui::GetColorU32(to_vec4(col)); }, py::arg("col")); m.def( "GetColorU32", [](ImU32 col) { return ImGui::GetColorU32(col); }, py::arg("col")); @@ -1664,6 +1664,217 @@ void bind_imgui_methods(py::module& m) { m.def("LoadIniSettingsFromMemory", [](const char *ini_data) { ImGui::LoadIniSettingsFromMemory(ini_data); }, py::arg("ini_data")); m.def("SaveIniSettingsToDisk", [](const char *ini_filename) { ImGui::SaveIniSettingsToDisk(ini_filename); }, py::arg("ini_filename")); m.def("SaveIniSettingsToMemory", []() { return ImGui::SaveIniSettingsToMemory(); }); + + // Draw Commands + m.def( + "AddLine", + [](const Vec2T& p1, const Vec2T& p2, ImU32 col, float thickness) { + ImGui::GetWindowDrawList()->AddRect(to_vec2(p1), to_vec2(p2), col, thickness); + }, + py::arg("p_min"), + py::arg("p_max"), + py::arg("col"), + py::arg("thickness") = 1.0f + ); + m.def( + "AddRect", + [](const Vec2T& p_min, const Vec2T& p_max, ImU32 col, float rounding, ImDrawFlags flags, float thickness) { + ImGui::GetWindowDrawList()->AddRect(to_vec2(p_min), to_vec2(p_max), col, rounding, flags, thickness); + }, + py::arg("p_min"), + py::arg("p_max"), + py::arg("col"), + py::arg("rounding") = 0.0f, + py::arg("flags") = 0, + py::arg("thickness") = 1.0f + ); + m.def( + "AddRectFilled", + [](const Vec2T& p_min, const Vec2T& p_max, ImU32 col, float rounding, ImDrawFlags flags) { + ImGui::GetWindowDrawList()->AddRectFilled(to_vec2(p_min), to_vec2(p_max), col, rounding, flags); + }, + py::arg("p_min"), + py::arg("p_max"), + py::arg("col"), + py::arg("rounding") = 0.0f, + py::arg("flags") = 0 + ); + m.def( + "AddRectFilledMultiColor", + [](const Vec2T& p_min, const Vec2T& p_max, ImU32 col_upr_left, ImU32 col_upr_right, ImU32 col_bot_right, ImU32 col_bot_left) { + ImGui::GetWindowDrawList()->AddRectFilledMultiColor(to_vec2(p_min), to_vec2(p_max), col_upr_left, col_upr_right, col_bot_right, col_bot_left); + }, + py::arg("p_min"), + py::arg("p_max"), + py::arg("col_upr_left"), + py::arg("col_upr_right"), + py::arg("col_bot_right"), + py::arg("col_bot_left") + ); + m.def( + "AddQuad", + [](const Vec2T& p1, const Vec2T& p2, const Vec2T& p3, const Vec2T& p4, ImU32 col, float thickness) { + ImGui::GetWindowDrawList()->AddQuad(to_vec2(p1), to_vec2(p2), to_vec2(p3), to_vec2(p4), col, thickness); + }, + py::arg("p1"), + py::arg("p2"), + py::arg("p3"), + py::arg("p4"), + py::arg("col"), + py::arg("thickness") = 1.0f + ); + m.def( + "AddQuadFilled", + [](const Vec2T& p1, const Vec2T& p2, const Vec2T& p3, const Vec2T& p4, ImU32 col) { + ImGui::GetWindowDrawList()->AddQuadFilled(to_vec2(p1), to_vec2(p2), to_vec2(p3), to_vec2(p4), col); + }, + py::arg("p1"), + py::arg("p2"), + py::arg("p3"), + py::arg("p4"), + py::arg("col") + ); + m.def( + "AddTriangle", + [](const Vec2T& p1, const Vec2T& p2, const Vec2T& p3, ImU32 col, float thickness) { + ImGui::GetWindowDrawList()->AddTriangle(to_vec2(p1), to_vec2(p2), to_vec2(p3), col, thickness); + }, + py::arg("p1"), + py::arg("p2"), + py::arg("p3"), + py::arg("col"), + py::arg("thickness") = 1.0f + ); + m.def( + "AddTriangleFilled", + [](const Vec2T& p1, const Vec2T& p2, const Vec2T& p3, ImU32 col) { + ImGui::GetWindowDrawList()->AddTriangleFilled(to_vec2(p1), to_vec2(p2), to_vec2(p3), col); + }, + py::arg("p1"), + py::arg("p2"), + py::arg("p3"), + py::arg("col") + ); + m.def( + "AddCircle", + [](const Vec2T& center, const float radius, ImU32 col, int num_segments, float thickness) { + ImGui::GetWindowDrawList()->AddCircle(to_vec2(center), radius, col, num_segments, thickness); + }, + py::arg("center"), + py::arg("radius"), + py::arg("col"), + py::arg("num_segments") = 0, + py::arg("thickness") = 1.0f + ); + m.def( + "AddCircleFilled", + [](const Vec2T& center, const float radius, ImU32 col, int num_segments) { + ImGui::GetWindowDrawList()->AddCircleFilled(to_vec2(center), radius, col, num_segments); + }, + py::arg("center"), + py::arg("radius"), + py::arg("col"), + py::arg("num_segments") = 0 + ); + m.def( + "AddNgon", + [](const Vec2T& center, const float radius, ImU32 col, int num_segments, float thickness) { + ImGui::GetWindowDrawList()->AddNgon(to_vec2(center), radius, col, num_segments, thickness); + }, + py::arg("center"), + py::arg("radius"), + py::arg("col"), + py::arg("num_segments") = 0, + py::arg("thickness") = 1.0f + ); + m.def( + "AddNgonFilled", + [](const Vec2T& center, const float radius, ImU32 col, int num_segments) { + ImGui::GetWindowDrawList()->AddNgonFilled(to_vec2(center), radius, col, num_segments); + }, + py::arg("center"), + py::arg("radius"), + py::arg("col"), + py::arg("num_segments") = 0 + ); + m.def( + "AddText", + [](const Vec2T& pos, ImU32 col, const char* text_begin, const char* text_end) { + ImGui::GetWindowDrawList()->AddText(to_vec2(pos), col, text_begin, text_end); + }, + py::arg("pos"), + py::arg("col"), + py::arg("text_begin"), + py::arg("text_end") = nullptr + ); + m.def( + "AddText", + [](const ImFont* font, float font_size, const Vec2T& pos, ImU32 col, const char* text_begin, const char* text_end, float wrap_width) { + ImGui::GetWindowDrawList()->AddText(font, font_size, to_vec2(pos), col, text_begin, text_end, wrap_width); + }, + py::arg("font"), + py::arg("font_size"), + py::arg("pos"), + py::arg("col"), + py::arg("text_begin"), + py::arg("text_end") = nullptr, + py::arg("wrap_width") = 0.0f + ); + m.def( + "AddPolyline", + [](const std::vector& points, int num_points, ImU32 col, ImDrawFlags flags, float thickness) { + std::vector points_vec2(points.size()); + for (int i = 0; i < points.size(); i++) { + points_vec2[i] = to_vec2(points[i]); + } + ImGui::GetWindowDrawList()->AddPolyline(points_vec2.data(), num_points, col, flags, thickness); + }, + py::arg("points"), + py::arg("num_points"), + py::arg("col"), + py::arg("flags"), + py::arg("thickness") + ); + m.def( + "AddConvexPolyFilled", + [](const std::vector& points, int num_points, ImU32 col) { + std::vector points_vec2(points.size()); + for (int i = 0; i < points.size(); i++) { + points_vec2[i] = to_vec2(points[i]); + } + ImGui::GetWindowDrawList()->AddConvexPolyFilled(points_vec2.data(), num_points, col); + }, + py::arg("points"), + py::arg("num_points"), + py::arg("col") + ); + m.def( + "AddBezierCubic", + [](const Vec2T& p1, const Vec2T& p2, const Vec2T& p3, const Vec2T& p4, ImU32 col, float thickness, int num_segments = 0) { + ImGui::GetWindowDrawList()->AddBezierCubic(to_vec2(p1), to_vec2(p2), to_vec2(p3), to_vec2(p4), col, thickness, num_segments); + }, + py::arg("p1"), + py::arg("p2"), + py::arg("p3"), + py::arg("p4"), + py::arg("col"), + py::arg("thickness"), + py::arg("num_segments") = 0 + ); + m.def( + "AddBezierQuadratic", + [](const Vec2T& p1, const Vec2T& p2, const Vec2T& p3, ImU32 col, float thickness, int num_segments = 0) { + ImGui::GetWindowDrawList()->AddBezierQuadratic(to_vec2(p1), to_vec2(p2), to_vec2(p3), col, thickness, num_segments); + }, + py::arg("p1"), + py::arg("p2"), + py::arg("p3"), + py::arg("col"), + py::arg("thickness"), + py::arg("num_segments") = 0 + ); + + } // clang-format on diff --git a/src/polyscope_bindings/imgui.pyi b/src/polyscope_bindings/imgui.pyi index d4bf983..e582eed 100644 --- a/src/polyscope_bindings/imgui.pyi +++ b/src/polyscope_bindings/imgui.pyi @@ -27,6 +27,10 @@ ImGuiTabItemFlags = NewType("ImGuiTabItemFlags", int) ImGuiTreeNodeFlags = NewType("ImGuiTreeNodeFlags", int) ImGuiWindowFlags = NewType("ImGuiWindowFlags", int) +# Draw Types +ImDrawFlags = NewType("ImDrawFlags", int) +ImDrawListFlags = NewType("ImDrawListFlags", int) + # Windows @@ -887,6 +891,27 @@ def SaveIniSettingsToDisk(ini_filename: str) -> None: ... def SaveIniSettingsToMemory() -> str: ... +# Draw Commands + +def AddLine(p1: ImVec2, p2: ImVec2, col: ImU32, thickness: float = 1.) -> None: ... +def AddRect(p_min: ImVec2, p_max: ImVec2, col: ImU32, rounding: float = 0., flags: ImDrawFlags = 0, thickness: float = 1.) -> None: ... +def AddRectFilled(p_min: ImVec2, p_max: ImVec2, col: ImU32, rounding: float = 0., flags: ImDrawFlags = 0) -> None: ... +def AddAddRectFilledMultiColor(p_min: ImVec2, p_max: ImVec2, col_upr_left: ImU32, col_upr_right: ImU32, col_bot_right: ImU32, col_bot_left: ImU32) -> None: ... +def AddQuad(p1: ImVec2, p2: ImVec2, p3: ImVec2, p4: ImVec2, col: ImU32, thickness: float = 1.) -> None: ... +def AddQuadFilled(p1: ImVec2, p2: ImVec2, p3: ImVec2, p4: ImVec2, col: ImU32) -> None: ... +def AddTriangle(p1: ImVec2, p2: ImVec2, p3: ImVec2, col: ImU32, thickness: float = 1.) -> None: ... +def AddTriangleFilled(p1: ImVec2, p2: ImVec2, p3: ImVec2, col: ImU32) -> None: ... +def AddCircle(center: ImVec2, radius: float, col: ImU32, num_segments: int = 0, thickness: float = 1.) -> None: ... +def AddCircleFilled(center: ImVec2, radius: float, col: ImU32, num_segments: int = 0) -> None: ... +def AddNgon(center: ImVec2, radius: float, col: ImU32, num_segments: int, thickness: float = 1.) -> None: ... +def AddNgonFilled(center: ImVec2, radius: float, col: ImU32, num_segments: int) -> None: ... +def AddText(pos: ImVec2, col: ImU32, text: str, text_end: Optional[str] = None) -> None: ... +def AddPolyline(points: List[ImVec2], num_points: int, col: ImU32, flags: ImDrawFlags, thickness) -> None: ... +def AddConvexPolyFilled(points: List[ImVec2], num_points: int, col: ImU32) -> None: ... +def AddBezierCubic(p1: ImVec2, p2: ImVec2, p3: ImVec2, p4: ImVec2, col: ImU32, thickness: float, num_segments: int = 0) -> None: ... +def AddBezierQuadratic(p1: ImVec2, p2: ImVec2, p3: ImVec2, col: ImU32, thickness: float, num_segments: int = 0) -> None: ... + + ImGuiWindowFlags_None: int ImGuiWindowFlags_NoTitleBar: int ImGuiWindowFlags_NoResize: int From 8e0a3c6f936a226149085bc126faf4cca0a4b092 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sun, 19 Nov 2023 22:05:29 -0800 Subject: [PATCH 71/88] update dep --- deps/polyscope | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/polyscope b/deps/polyscope index 2cd38b7..783a333 160000 --- a/deps/polyscope +++ b/deps/polyscope @@ -1 +1 @@ -Subproject commit 2cd38b7496a1e8ff79f615920de2980295bedef7 +Subproject commit 783a333b579d1b9648c3baf3d584b43d1dd31bbb From 26034b2283cd99e02d8fedafef4909cd0965a61e Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sat, 25 Nov 2023 23:37:25 -0800 Subject: [PATCH 72/88] update dep --- deps/polyscope | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/polyscope b/deps/polyscope index 783a333..c60e7d1 160000 --- a/deps/polyscope +++ b/deps/polyscope @@ -1 +1 @@ -Subproject commit 783a333b579d1b9648c3baf3d584b43d1dd31bbb +Subproject commit c60e7d11bbacee5940e3ffc5aae7fbe3b4d323bd From 6fe722a75e11a5bfb0f1b82f6b3420b0642fd54d Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sat, 25 Nov 2023 23:38:18 -0800 Subject: [PATCH 73/88] rework Groups API --- src/cpp/core.cpp | 29 +++++++++++++++ src/cpp/utils.h | 5 ++- src/polyscope/core.py | 59 ++++++++++++++++++++++++++++++- src/polyscope/structure.py | 7 ++++ test/polyscope_demo.py | 68 ++++++++++++++++++++++++++++------- test/polyscope_test.py | 72 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 225 insertions(+), 15 deletions(-) diff --git a/src/cpp/core.cpp b/src/cpp/core.cpp index c16cda5..26b5410 100644 --- a/src/cpp/core.cpp +++ b/src/cpp/core.cpp @@ -247,6 +247,35 @@ PYBIND11_MODULE(polyscope_bindings, m) { // === Rendering m.def("set_SSAA_factor", [](int n) { ps::options::ssaaFactor = n; }); + + + // === Structure + + // (this is the generic structure class, subtypes get bound in their respective files) + py::class_(m, "Structure") + .def_readonly("name", &ps::Structure::name) + ; + + // === Groups + + py::class_(m, "Group") + .def(py::init()) + .def_readonly("name", &ps::Group::name) + .def("add_child_group", &ps::Group::addChildGroup) + .def("add_child_structure", &ps::Group::addChildStructure) + .def("remove_child_group", &ps::Group::removeChildGroup) + .def("remove_child_structure", &ps::Group::removeChildStructure) + .def("set_enabled", &ps::Group::setEnabled, py::return_value_policy::reference) + .def("set_show_child_details", &ps::Group::setShowChildDetails) + .def("set_hide_descendents_from_structure_lists", &ps::Group::setHideDescendentsFromStructureLists) + ; + + // create/get/delete + m.def("create_group", &ps::createGroup, py::return_value_policy::reference); + m.def("get_group", &ps::getGroup, py::return_value_policy::reference); + m.def("remove_group", overload_cast_()(&ps::removeGroup)); + m.def("remove_all_groups", &ps::removeAllGroups); + // === Low-level internals access // (warning, 'advanced' users only, may change) diff --git a/src/cpp/utils.h b/src/cpp/utils.h index 44b4da0..7a25f40 100644 --- a/src/cpp/utils.h +++ b/src/cpp/utils.h @@ -97,7 +97,7 @@ void def_all_managed_buffer_funcs(C& c, ps::ManagedBufferType t) { template py::class_ bindStructure(py::module& m, std::string name) { - py::class_ s(m, name.c_str()); + py::class_ s(m, name.c_str()); // structure basics s.def("remove", &StructureT::remove, "Remove the structure") @@ -108,6 +108,9 @@ py::class_ bindStructure(py::module& m, std::string name) { .def("is_enabled", &StructureT::isEnabled, "Check if the structure is enabled") .def("set_transparency", &StructureT::setTransparency, "Set transparency alpha") .def("get_transparency", &StructureT::getTransparency, "Get transparency alpha") + + // group things + .def("add_to_group", overload_cast_()(&StructureT::addToGroup) , "Add to group") // slice plane things .def("set_ignore_slice_plane", &StructureT::setIgnoreSlicePlane, "Set ignore slice plane") diff --git a/src/polyscope/core.py b/src/polyscope/core.py index d898d53..f49ba55 100644 --- a/src/polyscope/core.py +++ b/src/polyscope/core.py @@ -278,7 +278,64 @@ def set_transparency_render_passes(n): ## Rendering def set_SSAA_factor(n): psb.set_SSAA_factor(n) - + +## Groups + +class Group: + # This class wraps a _reference_ to the underlying object, whose lifetime is managed by Polyscope + + # End users should not call this constrctor, use add_group() instead + def __init__(self, instance): + # Wrap an existing instance + self.bound_group = instance + + def get_name(self): + return self.bound_group.name + + def add_child_group(self, child_group): + if isinstance(child_group, str): + self.bound_group.add_child_group(get_group(child_group).bound_group) + else: + self.bound_group.add_child_group(child_group.bound_group) + + def add_child_structure(self, child_structure): + self.bound_group.add_child_structure(child_structure.bound_instance) + + def remove_child_group(self, child_group): + if isinstance(child_group, str): + self.bound_group.remove_child_group(get_group(child_group).bound_group) + else: + self.bound_group.remove_child_group(child_group.bound_group) + + def remove_child_structure(self, child_structure): + self.bound_group.remove_child_structure(child_structure.bound_instance) + + def set_enabled(self, new_val): + self.bound_group.set_enabled(new_val) + + def set_show_child_details(self, new_val): + self.bound_group.set_show_child_details(new_val) + + def set_hide_descendents_from_structure_lists(self, new_val): + self.bound_group.set_hide_descendents_from_structure_lists(new_val) + +def create_group(name): + return Group(psb.create_group(name)) + +def get_group(name): + return Group(psb.get_group(name)) + +def remove_group(group, error_if_absent=True): + # accept either a string or a group ref as input + if isinstance(group, str): + psb.remove_group(group, error_if_absent) + else: + psb.remove_group(group.get_name(), error_if_absent) + +def remove_all_groups(): + psb.remove_all_groups() + + ## Low-level internals access # (warning, 'advanced' users only, may change) def get_final_scene_color_texture_native_handle(): diff --git a/src/polyscope/structure.py b/src/polyscope/structure.py index 25049af..d80742f 100644 --- a/src/polyscope/structure.py +++ b/src/polyscope/structure.py @@ -110,6 +110,13 @@ def get_quantity_buffer(self, quantity_name, buffer_name): psb.ManagedBufferType.UVec4 : lambda q,n,t : ManagedBuffer(self.bound_instance.get_quantity_buffer_UVec4 (q,n), t), }[buffer_type](quantity_name, buffer_name, buffer_type) + ## Groups + def add_to_group(self, group): + # take either a string or a group object as input + if isinstance(group, str): + self.bound_instance.add_to_group(group) + else: + self.bound_instance.add_to_group(group.get_name()) ## Slice planes diff --git a/test/polyscope_demo.py b/test/polyscope_demo.py index fb6abf8..d4503f2 100644 --- a/test/polyscope_demo.py +++ b/test/polyscope_demo.py @@ -15,28 +15,51 @@ import polyscope.imgui as psim from polyscope import imgui as psim import potpourri3d as pp3d +import igl +import imageio import sys import argparse import numpy as np +# Helper funcs for defining implicits +def sphere_sdf(pts): + res = np.linalg.norm(pts, axis=-1) - 1. + return res +def color_func(pts): + # return np.cos(3*pts)**2 + A = np.ones_like(pts) * 0.3 + A[:,0] = np.cos(3*pts[:,0])**2 + return A + + def main(): parser = argparse.ArgumentParser() # Build arguments - parser.add_argument('mesh', type=str, help='path to a mesh') + # parser.add_argument('mesh', type=str, help='path to a mesh') + # parser.add_argument('image', type=str, help='path to a image') parser.add_argument('--surfacemesh', default=True, action='store_true') parser.add_argument('--no-surfacemesh', dest='surfacemesh', action='store_false') parser.add_argument('--pointcloud', default=False, action='store_true') parser.add_argument('--volumemesh', default=False, action='store_true') + parser.add_argument('--volumegrid', default=False, action='store_true') # Parse arguments args = parser.parse_args() # Load a mesh argument - verts, faces = pp3d.read_mesh(args.mesh) - + # verts, faces = pp3d.read_mesh(args.mesh) + + # verts, UVs, _, faces, UVinds, _ = igl.read_obj(args.mesh) + # corner_UVs = UVs[UVinds,:].reshape(-1,2) + # print(corner_UVs.shape) + # + # color_tex = imageio.imread(args.image) / 255. + # print(color_tex.shape) + + # print(UVs[:10,:]) # Set up a simple callback and UI button def my_function(): @@ -59,6 +82,7 @@ def callback(): polyscope.init() polyscope.set_ground_plane_mode("shadow_only") + polyscope.set_verbosity(101) ## Examples with a mesh if args.surfacemesh: @@ -68,6 +92,13 @@ def callback(): ps_mesh.add_scalar_quantity("X", verts[:,0]) ps_mesh.add_scalar_quantity("Y", verts[:,1]) + ps_mesh.add_parameterization_quantity("param", corner_UVs, defined_on='corners') + + ps_mesh.add_color_quantity("color_tex", color_tex, defined_on='texture', param_name="param", image_origin='upper_left') + ps_mesh.add_scalar_quantity("scalar_tex", color_tex[:,:,1], defined_on='texture', param_name="param") + + polyscope.add_color_image_quantity("my im", color_tex) + # Look at them polyscope.show() @@ -194,21 +225,32 @@ def callback(): # Remove the whole mesh structure polyscope.remove_all_structures() + + if args.volumegrid: - # Back to empty - polyscope.show() - - # polyscope.clear_user_callback() - +# Any implicit function mapping [N,3] numpy array +# of locations --> [N] numpy array of values def sphere_sdf(pts): res = np.linalg.norm(pts, axis=-1) - 1. return res -def color_func(pts): - # return np.cos(3*pts)**2 - A = np.ones_like(pts) * 0.3 - A[:,0] = np.cos(3*pts[:,0])**2 - return A +# Create the grid structure +bound_min = np.array([-1., -1., -1.]) +bound_max = np.array([+1., +1., +1.]) +node_dims = np.array([128,128,128]) +ps_grid = polyscope.register_volume_grid("test volume grid", bound_min, bound_max, node_dims) + +# This makes polyscope register the scalar quantity by calling +# your function for each point on the grid (so you don't have +# to worry about getting indexing right) +ps_grid.add_scalar_quantity_from_callable("sdf", sphere_sdf) + +polyscope.show() + + # Back to empty + polyscope.show() + + # polyscope.clear_user_callback() def implicit_ui(): diff --git a/test/polyscope_test.py b/test/polyscope_test.py index 9fe1cb2..6df621c 100644 --- a/test/polyscope_test.py +++ b/test/polyscope_test.py @@ -250,6 +250,78 @@ def test_scene_extents(self): ps.set_automatically_compute_scene_extents(True) + def test_groups(self): + + pts = np.zeros((10,3)) + pt_cloud_0 = ps.register_point_cloud("cloud0", pts) + pt_cloud_1 = ps.register_point_cloud("cloud1", pts) + pt_cloud_2 = ps.register_point_cloud("cloud2", pts) + + groupA = ps.create_group("group_A") + groupB = ps.create_group("group_B") + groupC = ps.create_group("group_C") + + groupA.add_child_group(groupB) + groupA.add_child_group("group_C") + + pt_cloud_0.add_to_group(groupA) + pt_cloud_1.add_to_group("group_A") + groupA.add_child_structure(pt_cloud_2) + + groupA.set_enabled(False) + groupB.set_show_child_details(True) + groupC.set_hide_descendents_from_structure_lists(True) + + ps.show(3) + + groupA.remove_child_group(groupB) + groupA.remove_child_group("group_C") + groupA.remove_child_structure(pt_cloud_0) + + ps.remove_group(groupB, True) + ps.remove_group("group_C", False) + + ps.show(3) + + ps.remove_all_groups() + + ps.remove_all_structures() + + + def test_groups_demo_example(self): + + # make a point cloud + pts = np.zeros((300,3)) + psCloud = ps.register_point_cloud("my cloud", pts) + + # make a curve network + nodes = np.zeros((4,3)) + edges = np.array([[1, 3], [3, 0], [1, 0], [0, 2]]) + psCurve = ps.register_curve_network("my network", nodes, edges) + + # create a group for these two objects + group = ps.create_group("my group") + psCurve.add_to_group(group) # you also say psCurve.add_to_group("my group") + psCloud.add_to_group(group) + + # toggle the enabled state for everything in the group + group.set_enabled(False) + + # hide items in group from displaying in the UI + # (useful if you are registering huge numbers of structures you don't always need to see) + group.set_hide_descendents_from_structure_lists(True) + group.set_show_child_details(False) + + # nest groups inside of other groups + super_group = ps.create_group("py parent group") + super_group.add_child_group(group) + + ps.show(3) + + ps.remove_all_groups() + ps.remove_all_structures() + + class TestImGuiBindings(unittest.TestCase): def test_ui_calls(self): From 9063e38d6d3e8d660c22ba5dae80b03a855d5447 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sun, 26 Nov 2023 00:51:50 -0800 Subject: [PATCH 74/88] spelling is hard --- deps/polyscope | 2 +- src/cpp/core.cpp | 2 +- src/polyscope/core.py | 4 ++-- test/polyscope_test.py | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/deps/polyscope b/deps/polyscope index c60e7d1..f3623ec 160000 --- a/deps/polyscope +++ b/deps/polyscope @@ -1 +1 @@ -Subproject commit c60e7d11bbacee5940e3ffc5aae7fbe3b4d323bd +Subproject commit f3623ec59c162e90e9703360341ecb0849536202 diff --git a/src/cpp/core.cpp b/src/cpp/core.cpp index 26b5410..0901311 100644 --- a/src/cpp/core.cpp +++ b/src/cpp/core.cpp @@ -267,7 +267,7 @@ PYBIND11_MODULE(polyscope_bindings, m) { .def("remove_child_structure", &ps::Group::removeChildStructure) .def("set_enabled", &ps::Group::setEnabled, py::return_value_policy::reference) .def("set_show_child_details", &ps::Group::setShowChildDetails) - .def("set_hide_descendents_from_structure_lists", &ps::Group::setHideDescendentsFromStructureLists) + .def("set_hide_descendants_from_structure_lists", &ps::Group::setHideDescendantsFromStructureLists) ; // create/get/delete diff --git a/src/polyscope/core.py b/src/polyscope/core.py index f49ba55..5d95130 100644 --- a/src/polyscope/core.py +++ b/src/polyscope/core.py @@ -316,8 +316,8 @@ def set_enabled(self, new_val): def set_show_child_details(self, new_val): self.bound_group.set_show_child_details(new_val) - def set_hide_descendents_from_structure_lists(self, new_val): - self.bound_group.set_hide_descendents_from_structure_lists(new_val) + def set_hide_descendants_from_structure_lists(self, new_val): + self.bound_group.set_hide_descendants_from_structure_lists(new_val) def create_group(name): return Group(psb.create_group(name)) diff --git a/test/polyscope_test.py b/test/polyscope_test.py index 6df621c..88270b3 100644 --- a/test/polyscope_test.py +++ b/test/polyscope_test.py @@ -270,7 +270,7 @@ def test_groups(self): groupA.set_enabled(False) groupB.set_show_child_details(True) - groupC.set_hide_descendents_from_structure_lists(True) + groupC.set_hide_descendants_from_structure_lists(True) ps.show(3) @@ -309,7 +309,7 @@ def test_groups_demo_example(self): # hide items in group from displaying in the UI # (useful if you are registering huge numbers of structures you don't always need to see) - group.set_hide_descendents_from_structure_lists(True) + group.set_hide_descendants_from_structure_lists(True) group.set_show_child_details(False) # nest groups inside of other groups From 90be869f0b1d7652440d91d94c401dd1f984adce Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sun, 26 Nov 2023 02:20:36 -0800 Subject: [PATCH 75/88] use float32 types for all interfaces --- src/cpp/curve_network.cpp | 32 +++++++++---------- src/cpp/floating_quantities.cpp | 16 +++++----- src/cpp/point_cloud.cpp | 16 +++++----- src/cpp/surface_mesh.cpp | 54 ++++++++++++++++----------------- src/cpp/utils.h | 16 +++++----- src/cpp/volume_grid.cpp | 4 +-- src/cpp/volume_mesh.cpp | 22 +++++++------- 7 files changed, 80 insertions(+), 80 deletions(-) diff --git a/src/cpp/curve_network.cpp b/src/cpp/curve_network.cpp index 40331b2..4392a91 100644 --- a/src/cpp/curve_network.cpp +++ b/src/cpp/curve_network.cpp @@ -35,8 +35,8 @@ void bind_curve_network(py::module& m) { bindStructure(m, "CurveNetwork") // basics - .def("update_node_positions", &ps::CurveNetwork::updateNodePositions, "Update node positions") - .def("update_node_positions2D", &ps::CurveNetwork::updateNodePositions2D, "Update node positions") + .def("update_node_positions", &ps::CurveNetwork::updateNodePositions, "Update node positions") + .def("update_node_positions2D", &ps::CurveNetwork::updateNodePositions2D, "Update node positions") .def("n_nodes", &ps::CurveNetwork::nNodes, "# nodes") .def("n_edges", &ps::CurveNetwork::nEdges, "# edges") @@ -49,42 +49,42 @@ void bind_curve_network(py::module& m) { .def("get_material", &ps::CurveNetwork::getMaterial, "Get material") // quantities - .def("add_node_color_quantity", &ps::CurveNetwork::addNodeColorQuantity, "Add a color function at nodes", + .def("add_node_color_quantity", &ps::CurveNetwork::addNodeColorQuantity, "Add a color function at nodes", py::arg("name"), py::arg("values"), py::return_value_policy::reference) - .def("add_edge_color_quantity", &ps::CurveNetwork::addEdgeColorQuantity, "Add a color function at edges", + .def("add_edge_color_quantity", &ps::CurveNetwork::addEdgeColorQuantity, "Add a color function at edges", py::arg("name"), py::arg("values"), py::return_value_policy::reference) - .def("add_node_scalar_quantity", &ps::CurveNetwork::addNodeScalarQuantity, "Add a scalar function at nodes", + .def("add_node_scalar_quantity", &ps::CurveNetwork::addNodeScalarQuantity, "Add a scalar function at nodes", py::arg("name"), py::arg("values"), py::arg("data_type")=ps::DataType::STANDARD, py::return_value_policy::reference) - .def("add_edge_scalar_quantity", &ps::CurveNetwork::addEdgeScalarQuantity, "Add a scalar function at edge", + .def("add_edge_scalar_quantity", &ps::CurveNetwork::addEdgeScalarQuantity, "Add a scalar function at edge", py::arg("name"), py::arg("values"), py::arg("data_type")=ps::DataType::STANDARD, py::return_value_policy::reference) - .def("add_node_vector_quantity", &ps::CurveNetwork::addNodeVectorQuantity, "Add a vector function at nodes", + .def("add_node_vector_quantity", &ps::CurveNetwork::addNodeVectorQuantity, "Add a vector function at nodes", py::arg("name"), py::arg("values"), py::arg("vector_type")=ps::VectorType::STANDARD, py::return_value_policy::reference) - .def("add_node_vector_quantity2D", &ps::CurveNetwork::addNodeVectorQuantity2D, "Add a vector function at nodes", + .def("add_node_vector_quantity2D", &ps::CurveNetwork::addNodeVectorQuantity2D, "Add a vector function at nodes", py::arg("name"), py::arg("values"), py::arg("vector_type")=ps::VectorType::STANDARD, py::return_value_policy::reference) - .def("add_edge_vector_quantity", &ps::CurveNetwork::addEdgeVectorQuantity, "Add a vector function at edges", + .def("add_edge_vector_quantity", &ps::CurveNetwork::addEdgeVectorQuantity, "Add a vector function at edges", py::arg("name"), py::arg("values"), py::arg("vector_type")=ps::VectorType::STANDARD, py::return_value_policy::reference) - .def("add_edge_vector_quantity2D", &ps::CurveNetwork::addEdgeVectorQuantity2D, "Add a vector function at edges", + .def("add_edge_vector_quantity2D", &ps::CurveNetwork::addEdgeVectorQuantity2D, "Add a vector function at edges", py::arg("name"), py::arg("values"), py::arg("vector_type")=ps::VectorType::STANDARD, py::return_value_policy::reference); // Static adders and getters - m.def("register_curve_network", &ps::registerCurveNetwork, + m.def("register_curve_network", &ps::registerCurveNetwork, py::arg("name"), py::arg("nodes"), py::arg("edges"), "Register a curve network", py::return_value_policy::reference); - m.def("register_curve_network2D", &ps::registerCurveNetwork2D, + m.def("register_curve_network2D", &ps::registerCurveNetwork2D, py::arg("name"), py::arg("nodes"), py::arg("edges"), "Register a curve network", py::return_value_policy::reference); - m.def("register_curve_network_line", &ps::registerCurveNetworkLine, + m.def("register_curve_network_line", &ps::registerCurveNetworkLine, py::arg("name"), py::arg("nodes"), "Register a curve network", py::return_value_policy::reference); - m.def("register_curve_network_line2D", &ps::registerCurveNetworkLine2D, + m.def("register_curve_network_line2D", &ps::registerCurveNetworkLine2D, py::arg("name"), py::arg("nodes"), "Register a curve network", py::return_value_policy::reference); - m.def("register_curve_network_loop", &ps::registerCurveNetworkLoop, + m.def("register_curve_network_loop", &ps::registerCurveNetworkLoop, py::arg("name"), py::arg("nodes"), "Register a curve network", py::return_value_policy::reference); - m.def("register_curve_network_loop2D", &ps::registerCurveNetworkLoop2D, + m.def("register_curve_network_loop2D", &ps::registerCurveNetworkLoop2D, py::arg("name"), py::arg("nodes"), "Register a curve network", py::return_value_policy::reference); diff --git a/src/cpp/floating_quantities.cpp b/src/cpp/floating_quantities.cpp index c3773f9..fe54580 100644 --- a/src/cpp/floating_quantities.cpp +++ b/src/cpp/floating_quantities.cpp @@ -36,11 +36,11 @@ void bind_floating_quantities(py::module& m) { qColorImage.def("set_is_premultiplied", &ps::ColorImageQuantity::setIsPremultiplied); // global / free-floating adders - m.def("add_scalar_image_quantity", &ps::addScalarImageQuantity, + m.def("add_scalar_image_quantity", &ps::addScalarImageQuantity, py::return_value_policy::reference); - m.def("add_color_image_quantity", &ps::addColorImageQuantity, + m.def("add_color_image_quantity", &ps::addColorImageQuantity, py::return_value_policy::reference); - m.def("add_color_alpha_image_quantity", &ps::addColorAlphaImageQuantity, + m.def("add_color_alpha_image_quantity", &ps::addColorAlphaImageQuantity, py::return_value_policy::reference); // == Render image floating quantities @@ -66,15 +66,15 @@ void bind_floating_quantities(py::module& m) { qRawColorAlphaRenderImage.def("set_is_premultiplied", &ps::RawColorAlphaRenderImageQuantity::setIsPremultiplied); // global / free-floating adders - m.def("add_depth_render_image_quantity", &ps::addDepthRenderImageQuantity, + m.def("add_depth_render_image_quantity", &ps::addDepthRenderImageQuantity, py::return_value_policy::reference); - m.def("add_color_render_image_quantity", &ps::addColorRenderImageQuantity, + m.def("add_color_render_image_quantity", &ps::addColorRenderImageQuantity, py::return_value_policy::reference); - m.def("add_scalar_render_image_quantity", &ps::addScalarRenderImageQuantity, + m.def("add_scalar_render_image_quantity", &ps::addScalarRenderImageQuantity, py::return_value_policy::reference); - m.def("add_raw_color_render_image_quantity", &ps::addRawColorRenderImageQuantity, + m.def("add_raw_color_render_image_quantity", &ps::addRawColorRenderImageQuantity, py::return_value_policy::reference); - m.def("add_raw_color_alpha_render_image_quantity", &ps::addRawColorAlphaRenderImageQuantity, + m.def("add_raw_color_alpha_render_image_quantity", &ps::addRawColorAlphaRenderImageQuantity, py::return_value_policy::reference); } diff --git a/src/cpp/point_cloud.cpp b/src/cpp/point_cloud.cpp index f82e172..c17218c 100644 --- a/src/cpp/point_cloud.cpp +++ b/src/cpp/point_cloud.cpp @@ -36,8 +36,8 @@ void bind_point_cloud(py::module& m) { bindStructure(m, "PointCloud") // basics - .def("update_point_positions", &ps::PointCloud::updatePointPositions, "Update point positions") - .def("update_point_positions2D", &ps::PointCloud::updatePointPositions2D, "Update point positions") + .def("update_point_positions", &ps::PointCloud::updatePointPositions, "Update point positions") + .def("update_point_positions2D", &ps::PointCloud::updatePointPositions2D, "Update point positions") .def("n_points", &ps::PointCloud::nPoints, "# points") // options @@ -60,19 +60,19 @@ void bind_point_cloud(py::module& m) { .def("clear_point_radius_quantity", &ps::PointCloud::clearPointRadiusQuantity, "Clear any quantity setting the radius") // quantities - .def("add_color_quantity", &ps::PointCloud::addColorQuantity, "Add a color function at points", + .def("add_color_quantity", &ps::PointCloud::addColorQuantity, "Add a color function at points", py::arg("name"), py::arg("values"), py::return_value_policy::reference) - .def("add_scalar_quantity", &ps::PointCloud::addScalarQuantity, "Add a scalar function at points", + .def("add_scalar_quantity", &ps::PointCloud::addScalarQuantity, "Add a scalar function at points", py::arg("name"), py::arg("values"), py::arg("data_type")=ps::DataType::STANDARD, py::return_value_policy::reference) - .def("add_vector_quantity", &ps::PointCloud::addVectorQuantity, "Add a vector function at points", + .def("add_vector_quantity", &ps::PointCloud::addVectorQuantity, "Add a vector function at points", py::arg("name"), py::arg("values"), py::arg("vector_type")=ps::VectorType::STANDARD, py::return_value_policy::reference) - .def("add_vector_quantity2D", &ps::PointCloud::addVectorQuantity2D, "Add a vector function at points", + .def("add_vector_quantity2D", &ps::PointCloud::addVectorQuantity2D, "Add a vector function at points", py::arg("name"), py::arg("values"), py::arg("vector_type")=ps::VectorType::STANDARD, py::return_value_policy::reference); // Static adders and getters - m.def("register_point_cloud", &ps::registerPointCloud, + m.def("register_point_cloud", &ps::registerPointCloud, py::arg("name"), py::arg("values"), "Register a point cloud", py::return_value_policy::reference); - m.def("register_point_cloud2D", &ps::registerPointCloud2D, + m.def("register_point_cloud2D", &ps::registerPointCloud2D, py::arg("name"), py::arg("values"), "Register a point cloud", py::return_value_policy::reference); m.def("remove_point_cloud", &polyscope::removePointCloud, "Remove a point cloud by name"); m.def("get_point_cloud", &polyscope::getPointCloud, "Get a point cloud by name", py::return_value_policy::reference); diff --git a/src/cpp/surface_mesh.cpp b/src/cpp/surface_mesh.cpp index 421e977..6ca8e49 100644 --- a/src/cpp/surface_mesh.cpp +++ b/src/cpp/surface_mesh.cpp @@ -59,9 +59,9 @@ void bind_surface_mesh(py::module& m) { bindStructure(m, "SurfaceMesh") // basics - .def("update_vertex_positions", &ps::SurfaceMesh::updateVertexPositions, + .def("update_vertex_positions", &ps::SurfaceMesh::updateVertexPositions, "Update vertex positions") - .def("update_vertex_positions2D", &ps::SurfaceMesh::updateVertexPositions2D, + .def("update_vertex_positions2D", &ps::SurfaceMesh::updateVertexPositions2D, "Update vertex positions") .def("n_vertices", &ps::SurfaceMesh::nVertices, "# vertices") .def("n_faces", &ps::SurfaceMesh::nFaces, "# faces") @@ -96,77 +96,77 @@ void bind_surface_mesh(py::module& m) { // = quantities // Scalars - .def("add_vertex_scalar_quantity", &ps::SurfaceMesh::addVertexScalarQuantity, + .def("add_vertex_scalar_quantity", &ps::SurfaceMesh::addVertexScalarQuantity, "Add a scalar function at vertices", py::arg("name"), py::arg("values"), py::arg("data_type") = ps::DataType::STANDARD, py::return_value_policy::reference) - .def("add_face_scalar_quantity", &ps::SurfaceMesh::addFaceScalarQuantity, + .def("add_face_scalar_quantity", &ps::SurfaceMesh::addFaceScalarQuantity, "Add a scalar function at faces", py::arg("name"), py::arg("values"), py::arg("data_type") = ps::DataType::STANDARD, py::return_value_policy::reference) - .def("add_edge_scalar_quantity", &ps::SurfaceMesh::addEdgeScalarQuantity, + .def("add_edge_scalar_quantity", &ps::SurfaceMesh::addEdgeScalarQuantity, "Add a scalar function at edges", py::arg("name"), py::arg("values"), py::arg("data_type") = ps::DataType::STANDARD, py::return_value_policy::reference) - .def("add_halfedge_scalar_quantity", &ps::SurfaceMesh::addHalfedgeScalarQuantity, + .def("add_halfedge_scalar_quantity", &ps::SurfaceMesh::addHalfedgeScalarQuantity, "Add a scalar function at halfedges", py::arg("name"), py::arg("values"), py::arg("data_type") = ps::DataType::STANDARD, py::return_value_policy::reference) .def("add_texture_scalar_quantity", - overload_cast_()(&ps::SurfaceMesh::addTextureScalarQuantity), + overload_cast_()(&ps::SurfaceMesh::addTextureScalarQuantity), "Add a scalar function from a texture map", py::arg("name"), py::arg("param_name"), py::arg("dimX"), py::arg("dimY"), py::arg("values"), py::arg("image_origin"), py::arg("data_type") = ps::DataType::STANDARD, py::return_value_policy::reference) // Colors - .def("add_vertex_color_quantity", &ps::SurfaceMesh::addVertexColorQuantity, + .def("add_vertex_color_quantity", &ps::SurfaceMesh::addVertexColorQuantity, "Add a color value at vertices", py::return_value_policy::reference) - .def("add_face_color_quantity", &ps::SurfaceMesh::addFaceColorQuantity, + .def("add_face_color_quantity", &ps::SurfaceMesh::addFaceColorQuantity, "Add a color value at faces", py::return_value_policy::reference) .def("add_texture_color_quantity", - overload_cast_()( - &ps::SurfaceMesh::addTextureColorQuantity), + overload_cast_()( + &ps::SurfaceMesh::addTextureColorQuantity), "Add a color function from a texture map", py::arg("name"), py::arg("param_name"), py::arg("dimX"), py::arg("dimY"), py::arg("colors"), py::arg("image_origin"), py::return_value_policy::reference) // Distance - .def("add_vertex_distance_quantity", &ps::SurfaceMesh::addVertexDistanceQuantity, + .def("add_vertex_distance_quantity", &ps::SurfaceMesh::addVertexDistanceQuantity, "Add a distance function at vertices", py::return_value_policy::reference) - .def("add_vertex_signed_distance_quantity", &ps::SurfaceMesh::addVertexSignedDistanceQuantity, + .def("add_vertex_signed_distance_quantity", &ps::SurfaceMesh::addVertexSignedDistanceQuantity, "Add a signed distance function at vertices", py::return_value_policy::reference) // Parameterization - .def("add_corner_parameterization_quantity", &ps::SurfaceMesh::addParameterizationQuantity, + .def("add_corner_parameterization_quantity", &ps::SurfaceMesh::addParameterizationQuantity, "Add a parameterization at corners", py::return_value_policy::reference) - .def("add_vertex_parameterization_quantity", &ps::SurfaceMesh::addVertexParameterizationQuantity, + .def("add_vertex_parameterization_quantity", &ps::SurfaceMesh::addVertexParameterizationQuantity, "Add a parameterization at vertices", py::return_value_policy::reference) // Vector - .def("add_vertex_vector_quantity", &ps::SurfaceMesh::addVertexVectorQuantity, + .def("add_vertex_vector_quantity", &ps::SurfaceMesh::addVertexVectorQuantity, "Add a vertex vector quantity", py::return_value_policy::reference) - .def("add_face_vector_quantity", &ps::SurfaceMesh::addFaceVectorQuantity, + .def("add_face_vector_quantity", &ps::SurfaceMesh::addFaceVectorQuantity, "Add a face vector quantity", py::return_value_policy::reference) - .def("add_vertex_vector_quantity2D", &ps::SurfaceMesh::addVertexVectorQuantity2D, + .def("add_vertex_vector_quantity2D", &ps::SurfaceMesh::addVertexVectorQuantity2D, "Add a vertex 2D vector quantity", py::return_value_policy::reference) - .def("add_face_vector_quantity2D", &ps::SurfaceMesh::addFaceVectorQuantity2D, + .def("add_face_vector_quantity2D", &ps::SurfaceMesh::addFaceVectorQuantity2D, "Add a face 2D vector quantity", py::return_value_policy::reference) .def("add_vertex_tangent_vector_quantity", - &ps::SurfaceMesh::addVertexTangentVectorQuantity, + &ps::SurfaceMesh::addVertexTangentVectorQuantity, "Add a vertex tangent vector quantity", py::return_value_policy::reference) .def("add_face_tangent_vector_quantity", - &ps::SurfaceMesh::addFaceTangentVectorQuantity, + &ps::SurfaceMesh::addFaceTangentVectorQuantity, "Add a face tangent vector quantity", py::return_value_policy::reference) .def("add_one_form_tangent_vector_quantity", - &ps::SurfaceMesh::addOneFormTangentVectorQuantity>, + &ps::SurfaceMesh::addOneFormTangentVectorQuantity>, "Add a one form tangent vector quantity", py::return_value_policy::reference); // Static adders and getters - m.def("register_surface_mesh", &ps::registerSurfaceMesh, py::arg("name"), + m.def("register_surface_mesh", &ps::registerSurfaceMesh, py::arg("name"), py::arg("vertices"), py::arg("faces"), "Register a surface mesh", py::return_value_policy::reference); - m.def("register_surface_mesh2D", &ps::registerSurfaceMesh2D, py::arg("name"), + m.def("register_surface_mesh2D", &ps::registerSurfaceMesh2D, py::arg("name"), py::arg("vertices"), py::arg("faces"), "Register a surface mesh", py::return_value_policy::reference); - m.def("register_surface_mesh_list", &ps::registerSurfaceMesh>>, + m.def("register_surface_mesh_list", &ps::registerSurfaceMesh>>, py::arg("name"), py::arg("vertices"), py::arg("faces"), "Register a surface mesh from a nested list", py::return_value_policy::reference); - m.def("register_surface_mesh_list2D", &ps::registerSurfaceMesh2D>>, + m.def("register_surface_mesh_list2D", &ps::registerSurfaceMesh2D>>, py::arg("name"), py::arg("vertices"), py::arg("faces"), "Register a surface mesh from a nested list", py::return_value_policy::reference); diff --git a/src/cpp/utils.h b/src/cpp/utils.h index 7a25f40..7e8da76 100644 --- a/src/cpp/utils.h +++ b/src/cpp/utils.h @@ -135,14 +135,14 @@ py::class_ bindStructure(py::module& m, std::string name) { .def("get_position", [](StructureT& s) { return glm2eigen(s.getPosition()); }, "get the position of the shape origin after transform") // floating quantites - .def("add_scalar_image_quantity", &StructureT::template addScalarImageQuantity, py::arg("name"), py::arg("dimX"), py::arg("dimY"), py::arg("values"), py::arg("imageOrigin")=ps::ImageOrigin::UpperLeft, py::arg("type")=ps::DataType::STANDARD, py::return_value_policy::reference) - .def("add_color_image_quantity", &StructureT::template addColorImageQuantity, py::arg("name"), py::arg("dimX"), py::arg("dimY"), py::arg("values_rgb"), py::arg("imageOrigin")=ps::ImageOrigin::UpperLeft, py::return_value_policy::reference) - .def("add_color_alpha_image_quantity", &StructureT::template addColorAlphaImageQuantity, py::arg("name"), py::arg("dimX"), py::arg("dimY"), py::arg("values_rgba"), py::arg("imageOrigin")=ps::ImageOrigin::UpperLeft, py::return_value_policy::reference) - .def("add_depth_render_image_quantity", &StructureT::template addDepthRenderImageQuantity, py::arg("name"), py::arg("dimX"), py::arg("dimY"), py::arg("depthData"), py::arg("normalData"), py::arg("imageOrigin")=ps::ImageOrigin::UpperLeft, py::return_value_policy::reference) - .def("add_color_render_image_quantity", &StructureT::template addColorRenderImageQuantity, py::arg("name"), py::arg("dimX"), py::arg("dimY"), py::arg("depthData"), py::arg("normalData"), py::arg("colorData"), py::arg("imageOrigin")=ps::ImageOrigin::UpperLeft, py::return_value_policy::reference) - .def("add_scalar_render_image_quantity", &StructureT::template addScalarRenderImageQuantity, py::arg("name"), py::arg("dimX"), py::arg("dimY"), py::arg("depthData"), py::arg("normalData"), py::arg("scalarData"), py::arg("imageOrigin")=ps::ImageOrigin::UpperLeft, py::arg("type")=ps::DataType::STANDARD, py::return_value_policy::reference) - .def("add_raw_color_render_image_quantity", &StructureT::template addRawColorRenderImageQuantity, py::arg("name"), py::arg("dimX"), py::arg("dimY"), py::arg("depthData"), py::arg("colorData"), py::arg("imageOrigin")=ps::ImageOrigin::UpperLeft, py::return_value_policy::reference) - .def("add_raw_color_alpha_render_image_quantity", &StructureT::template addRawColorAlphaRenderImageQuantity, py::arg("name"), py::arg("dimX"), py::arg("dimY"), py::arg("depthData"), py::arg("colorData"), py::arg("imageOrigin")=ps::ImageOrigin::UpperLeft, py::return_value_policy::reference) + .def("add_scalar_image_quantity", &StructureT::template addScalarImageQuantity, py::arg("name"), py::arg("dimX"), py::arg("dimY"), py::arg("values"), py::arg("imageOrigin")=ps::ImageOrigin::UpperLeft, py::arg("type")=ps::DataType::STANDARD, py::return_value_policy::reference) + .def("add_color_image_quantity", &StructureT::template addColorImageQuantity, py::arg("name"), py::arg("dimX"), py::arg("dimY"), py::arg("values_rgb"), py::arg("imageOrigin")=ps::ImageOrigin::UpperLeft, py::return_value_policy::reference) + .def("add_color_alpha_image_quantity", &StructureT::template addColorAlphaImageQuantity, py::arg("name"), py::arg("dimX"), py::arg("dimY"), py::arg("values_rgba"), py::arg("imageOrigin")=ps::ImageOrigin::UpperLeft, py::return_value_policy::reference) + .def("add_depth_render_image_quantity", &StructureT::template addDepthRenderImageQuantity, py::arg("name"), py::arg("dimX"), py::arg("dimY"), py::arg("depthData"), py::arg("normalData"), py::arg("imageOrigin")=ps::ImageOrigin::UpperLeft, py::return_value_policy::reference) + .def("add_color_render_image_quantity", &StructureT::template addColorRenderImageQuantity, py::arg("name"), py::arg("dimX"), py::arg("dimY"), py::arg("depthData"), py::arg("normalData"), py::arg("colorData"), py::arg("imageOrigin")=ps::ImageOrigin::UpperLeft, py::return_value_policy::reference) + .def("add_scalar_render_image_quantity", &StructureT::template addScalarRenderImageQuantity, py::arg("name"), py::arg("dimX"), py::arg("dimY"), py::arg("depthData"), py::arg("normalData"), py::arg("scalarData"), py::arg("imageOrigin")=ps::ImageOrigin::UpperLeft, py::arg("type")=ps::DataType::STANDARD, py::return_value_policy::reference) + .def("add_raw_color_render_image_quantity", &StructureT::template addRawColorRenderImageQuantity, py::arg("name"), py::arg("dimX"), py::arg("dimY"), py::arg("depthData"), py::arg("colorData"), py::arg("imageOrigin")=ps::ImageOrigin::UpperLeft, py::return_value_policy::reference) + .def("add_raw_color_alpha_render_image_quantity", &StructureT::template addRawColorAlphaRenderImageQuantity, py::arg("name"), py::arg("dimX"), py::arg("dimY"), py::arg("depthData"), py::arg("colorData"), py::arg("imageOrigin")=ps::ImageOrigin::UpperLeft, py::return_value_policy::reference) ; diff --git a/src/cpp/volume_grid.cpp b/src/cpp/volume_grid.cpp index b8f7684..b1b1d3f 100644 --- a/src/cpp/volume_grid.cpp +++ b/src/cpp/volume_grid.cpp @@ -64,11 +64,11 @@ void bind_volume_grid(py::module& m) { // = quantities // Scalars - .def("add_node_scalar_quantity", &ps::VolumeGrid::addNodeScalarQuantity, + .def("add_node_scalar_quantity", &ps::VolumeGrid::addNodeScalarQuantity, py::arg("name"), py::arg("values"), py::arg("data_type") = ps::DataType::STANDARD, py::return_value_policy::reference) - .def("add_cell_scalar_quantity", &ps::VolumeGrid::addCellScalarQuantity, + .def("add_cell_scalar_quantity", &ps::VolumeGrid::addCellScalarQuantity, py::arg("name"), py::arg("values"), py::arg("data_type") = ps::DataType::STANDARD, py::return_value_policy::reference) diff --git a/src/cpp/volume_mesh.cpp b/src/cpp/volume_mesh.cpp index 2c00e15..ada5d71 100644 --- a/src/cpp/volume_mesh.cpp +++ b/src/cpp/volume_mesh.cpp @@ -34,7 +34,7 @@ void bind_volume_mesh(py::module& m) { bindStructure(m, "VolumeMesh") // basics - .def("update_vertex_positions", &ps::VolumeMesh::updateVertexPositions, + .def("update_vertex_positions", &ps::VolumeMesh::updateVertexPositions, "Update vertex positions") .def("n_vertices", &ps::VolumeMesh::nVertices, "# vertices") .def("n_faces", &ps::VolumeMesh::nFaces, "# faces") @@ -56,38 +56,38 @@ void bind_volume_mesh(py::module& m) { // = quantities // Scalars - .def("add_vertex_scalar_quantity", &ps::VolumeMesh::addVertexScalarQuantity, + .def("add_vertex_scalar_quantity", &ps::VolumeMesh::addVertexScalarQuantity, "Add a scalar function at vertices", py::arg("name"), py::arg("values"), py::arg("data_type") = ps::DataType::STANDARD, py::return_value_policy::reference) - .def("add_cell_scalar_quantity", &ps::VolumeMesh::addCellScalarQuantity, + .def("add_cell_scalar_quantity", &ps::VolumeMesh::addCellScalarQuantity, "Add a scalar function at cells", py::arg("name"), py::arg("values"), py::arg("data_type") = ps::DataType::STANDARD, py::return_value_policy::reference) // Colors - .def("add_vertex_color_quantity", &ps::VolumeMesh::addVertexColorQuantity, + .def("add_vertex_color_quantity", &ps::VolumeMesh::addVertexColorQuantity, "Add a color value at vertices", py::return_value_policy::reference) - .def("add_cell_color_quantity", &ps::VolumeMesh::addCellColorQuantity, + .def("add_cell_color_quantity", &ps::VolumeMesh::addCellColorQuantity, "Add a color value at cells", py::return_value_policy::reference) // Vector - .def("add_vertex_vector_quantity", &ps::VolumeMesh::addVertexVectorQuantity, + .def("add_vertex_vector_quantity", &ps::VolumeMesh::addVertexVectorQuantity, "Add a vertex vector quantity", py::return_value_policy::reference) - .def("add_cell_vector_quantity", &ps::VolumeMesh::addCellVectorQuantity, + .def("add_cell_vector_quantity", &ps::VolumeMesh::addCellVectorQuantity, "Add a cell vector quantity", py::return_value_policy::reference); // Static adders and getters - m.def("register_tet_mesh", &ps::registerTetMesh, py::arg("name"), + m.def("register_tet_mesh", &ps::registerTetMesh, py::arg("name"), py::arg("vertices"), py::arg("tets"), "Register a volume mesh of tet cells", py::return_value_policy::reference); - m.def("register_hex_mesh", &ps::registerHexMesh, py::arg("name"), + m.def("register_hex_mesh", &ps::registerHexMesh, py::arg("name"), py::arg("vertices"), py::arg("hexes"), "Register a volume mesh of hex cells", py::return_value_policy::reference); - m.def("register_volume_mesh", &ps::registerVolumeMesh, py::arg("name"), + m.def("register_volume_mesh", &ps::registerVolumeMesh, py::arg("name"), py::arg("vertices"), py::arg("cells"), "Register a volume mesh with a mix of element types", py::return_value_policy::reference); - m.def("register_tet_hex_mesh", &ps::registerTetHexMesh, + m.def("register_tet_hex_mesh", &ps::registerTetHexMesh, py::arg("name"), py::arg("vertices"), py::arg("tets"), py::arg("hexes"), "Register a volume mesh with lists of tet and hex elements", py::return_value_policy::reference); From b2ab62745b39465192cfb9db04d78ef3cde5e5f2 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sun, 26 Nov 2023 02:20:45 -0800 Subject: [PATCH 76/88] update dep --- deps/polyscope | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/polyscope b/deps/polyscope index f3623ec..92b1dee 160000 --- a/deps/polyscope +++ b/deps/polyscope @@ -1 +1 @@ -Subproject commit f3623ec59c162e90e9703360341ecb0849536202 +Subproject commit 92b1deebc7bc3e0019098d82a187eb6d50e27d83 From 95698143f1dc1a82758c32ce6a8590c1a11fe6f6 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sun, 26 Nov 2023 18:07:23 -0800 Subject: [PATCH 77/88] bind missing fullscreen compositing setting --- src/cpp/floating_quantities.cpp | 5 +++++ src/polyscope/common.py | 4 ++++ test/polyscope_test.py | 10 +++++----- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/cpp/floating_quantities.cpp b/src/cpp/floating_quantities.cpp index fe54580..33d1c41 100644 --- a/src/cpp/floating_quantities.cpp +++ b/src/cpp/floating_quantities.cpp @@ -49,21 +49,26 @@ void bind_floating_quantities(py::module& m) { qDepthRenderImage.def("set_material", &ps::DepthRenderImageQuantity::setMaterial, "Set material"); qDepthRenderImage.def("set_color", &ps::DepthRenderImageQuantity::setColor, "Set color"); qDepthRenderImage.def("set_transparency", &ps::DepthRenderImageQuantity::setTransparency, "Set transparency"); + qDepthRenderImage.def("set_allow_fullscreen_compositing", &ps::DepthRenderImageQuantity::setAllowFullscreenCompositing); auto qColorRenderImage = bindColorQuantity(m, "ColorRenderImageQuantity"); qColorRenderImage.def("set_material", &ps::ColorRenderImageQuantity::setMaterial, "Set material"); qColorRenderImage.def("set_transparency", &ps::ColorRenderImageQuantity::setTransparency, "Set transparency"); + qColorRenderImage.def("set_allow_fullscreen_compositing", &ps::ColorRenderImageQuantity::setAllowFullscreenCompositing); auto qScalarRenderImage = bindScalarQuantity(m, "ScalarRenderImageQuantity"); qScalarRenderImage.def("set_material", &ps::ScalarRenderImageQuantity::setMaterial, "Set material"); qScalarRenderImage.def("set_transparency", &ps::ScalarRenderImageQuantity::setTransparency, "Set transparency"); + qScalarRenderImage.def("set_allow_fullscreen_compositing", &ps::ScalarRenderImageQuantity::setAllowFullscreenCompositing); auto qRawColorRenderImage = bindColorQuantity(m, "RawColorRenderImageQuantity"); qRawColorRenderImage.def("set_transparency", &ps::RawColorRenderImageQuantity::setTransparency, "Set transparency"); + qRawColorRenderImage.def("set_allow_fullscreen_compositing", &ps::RawColorRenderImageQuantity::setAllowFullscreenCompositing); auto qRawColorAlphaRenderImage = bindColorQuantity(m, "RawColorAlphaRenderImageQuantity"); qRawColorAlphaRenderImage.def("set_transparency", &ps::RawColorAlphaRenderImageQuantity::setTransparency, "Set transparency"); qRawColorAlphaRenderImage.def("set_is_premultiplied", &ps::RawColorAlphaRenderImageQuantity::setIsPremultiplied); + qRawColorAlphaRenderImage.def("set_allow_fullscreen_compositing", &ps::RawColorAlphaRenderImageQuantity::setAllowFullscreenCompositing); // global / free-floating adders m.def("add_depth_render_image_quantity", &ps::addDepthRenderImageQuantity, diff --git a/src/polyscope/common.py b/src/polyscope/common.py index dd3d655..7cf19d7 100644 --- a/src/polyscope/common.py +++ b/src/polyscope/common.py @@ -135,6 +135,10 @@ def process_render_image_args(structure, quantity, image_args): if val is not None: quantity.set_material(val) + val = check_and_pop_arg(image_args, 'allow_fullscreen_compositing') + if val is not None: + quantity.set_allow_fullscreen_compositing(val) + # Process args, removing them from the dict if they are present def process_implicit_render_args(opts, implicit_args): diff --git a/test/polyscope_test.py b/test/polyscope_test.py index 88270b3..0447e3f 100644 --- a/test/polyscope_test.py +++ b/test/polyscope_test.py @@ -2023,7 +2023,7 @@ def test_floating_depth_render_images(self): cam.add_depth_render_image_quantity("render_img2", depths, normals, enabled=True, image_origin='lower_left', color=(0., 1., 0.), material='wax', transparency=0.7) # true floating adder - ps.add_depth_render_image_quantity("render_img3", depths, normals, enabled=True, image_origin='lower_left', color=(0., 1., 0.), material='wax', transparency=0.7, ) + ps.add_depth_render_image_quantity("render_img3", depths, normals, enabled=True, image_origin='lower_left', color=(0., 1., 0.), material='wax', transparency=0.7, allow_fullscreen_compositing=True) ps.show(3) ps.remove_all_structures() @@ -2045,7 +2045,7 @@ def test_floating_color_render_images(self): cam.add_color_render_image_quantity("render_img2", depths, normals, colors, enabled=True, image_origin='lower_left', material='wax', transparency=0.7) # true floating adder - ps.add_color_render_image_quantity("render_img3", depths, normals, colors, enabled=True, image_origin='lower_left', material='wax', transparency=0.7, ) + ps.add_color_render_image_quantity("render_img3", depths, normals, colors, enabled=True, image_origin='lower_left', material='wax', transparency=0.7, allow_fullscreen_compositing=True) ps.show(3) ps.remove_all_structures() @@ -2067,7 +2067,7 @@ def test_floating_scalar_render_images(self): cam.add_scalar_render_image_quantity("render_img2", depths, normals, scalars, enabled=True, image_origin='lower_left', material='wax', transparency=0.7) # true floating adder - ps.add_scalar_render_image_quantity("render_img3", depths, normals, scalars, enabled=True, image_origin='lower_left', material='wax', transparency=0.7) + ps.add_scalar_render_image_quantity("render_img3", depths, normals, scalars, enabled=True, image_origin='lower_left', material='wax', transparency=0.7, allow_fullscreen_compositing=True) ps.show(3) ps.remove_all_structures() @@ -2088,7 +2088,7 @@ def test_floating_raw_color_render_images(self): cam.add_raw_color_render_image_quantity("render_img2", depths, colors, enabled=True, image_origin='lower_left', transparency=0.7) # true floating adder - ps.add_raw_color_render_image_quantity("render_img3", depths, colors, enabled=True, image_origin='lower_left', transparency=0.7) + ps.add_raw_color_render_image_quantity("render_img3", depths, colors, enabled=True, image_origin='lower_left', transparency=0.7, allow_fullscreen_compositing=True) ps.show(3) ps.remove_all_structures() @@ -2109,7 +2109,7 @@ def test_floating_raw_color_alpha_render_images(self): cam.add_raw_color_alpha_render_image_quantity("render_img2", depths, colors, enabled=True, image_origin='lower_left', transparency=0.7, is_premultiplied=True) # true floating adder - ps.add_raw_color_alpha_render_image_quantity("render_img3", depths, colors, enabled=True, image_origin='lower_left', transparency=0.7) + ps.add_raw_color_alpha_render_image_quantity("render_img3", depths, colors, enabled=True, image_origin='lower_left', transparency=0.7, allow_fullscreen_compositing=True) ps.show(3) ps.remove_all_structures() From 8aaad5b0f547ab2b1cd03cd9599d64b88da0b981 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Wed, 8 Nov 2023 23:04:57 -0800 Subject: [PATCH 78/88] fix wrong caps in binding --- src/cpp/imgui.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/cpp/imgui.cpp b/src/cpp/imgui.cpp index 0bc318d..55734a7 100644 --- a/src/cpp/imgui.cpp +++ b/src/cpp/imgui.cpp @@ -461,11 +461,19 @@ void bind_imgui_methods(py::module& m) { m.def("GetFrameHeightWithSpacing", []() { return ImGui::GetFrameHeightWithSpacing(); }); // ID stack/scopes + m.def( + "PushID", [](const char* str_id) { ImGui::PushID(str_id); }, py::arg("str_id")); + m.def( + "PushID", [](int int_id) { ImGui::PushID(int_id); }, py::arg("int_id")); + m.def("PopID", []() { ImGui::PopID(); }); + m.def( + "GetID", [](const char* str_id) { return ImGui::GetID(str_id); }, py::arg("str_id")); + + // these are typos (bad capitalization). kept around to avoid needless breaking changes m.def( "PushId", [](const char* str_id) { ImGui::PushID(str_id); }, py::arg("str_id")); m.def( "PushId", [](int int_id) { ImGui::PushID(int_id); }, py::arg("int_id")); - m.def("PopID", []() { ImGui::PopID(); }); m.def( "GetId", [](const char* str_id) { return ImGui::GetID(str_id); }, py::arg("str_id")); From 5dcc6f4fafef689fcc56b00eee61da790cac753d Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sun, 26 Nov 2023 19:21:52 -0800 Subject: [PATCH 79/88] remove minmax defaults --- src/cpp/imgui.cpp | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/cpp/imgui.cpp b/src/cpp/imgui.cpp index 55734a7..9c399b8 100644 --- a/src/cpp/imgui.cpp +++ b/src/cpp/imgui.cpp @@ -619,8 +619,8 @@ void bind_imgui_methods(py::module& m) { py::arg("label"), py::arg("v"), py::arg("v_speed") = 1.0f, - py::arg("v_min") = 0.0f, - py::arg("v_max") = 0.0f, + py::arg("v_min"), + py::arg("v_max"), py::arg("format") = "%.3f", py::arg("power") = 1.0f); m.def( @@ -639,8 +639,8 @@ void bind_imgui_methods(py::module& m) { py::arg("label"), py::arg("v"), py::arg("v_speed") = 1.0f, - py::arg("v_min") = 0.0f, - py::arg("v_max") = 0.0f, + py::arg("v_min"), + py::arg("v_max"), py::arg("format") = "%.3f", py::arg("power") = 1.0f); m.def( @@ -659,8 +659,8 @@ void bind_imgui_methods(py::module& m) { py::arg("label"), py::arg("v"), py::arg("v_speed") = 1.0f, - py::arg("v_min") = 0.0f, - py::arg("v_max") = 0.0f, + py::arg("v_min"), + py::arg("v_max"), py::arg("format") = "%.3f", py::arg("power") = 1.0f); m.def( @@ -679,8 +679,8 @@ void bind_imgui_methods(py::module& m) { py::arg("label"), py::arg("v"), py::arg("v_speed") = 1.0f, - py::arg("v_min") = 0.0f, - py::arg("v_max") = 0.0f, + py::arg("v_min"), + py::arg("v_max"), py::arg("format") = "%.3f", py::arg("power") = 1.0f); m.def( @@ -709,8 +709,8 @@ void bind_imgui_methods(py::module& m) { py::arg("v_current_min"), py::arg("v_current_max"), py::arg("v_speed") = 1.0f, - py::arg("v_min") = 0.0f, - py::arg("v_max") = 0.0f, + py::arg("v_min"), + py::arg("v_max"), py::arg("format") = "%.3f", py::arg("format_max") = nullptr, py::arg("power") = 1.0f); @@ -816,8 +816,8 @@ void bind_imgui_methods(py::module& m) { }, py::arg("label"), py::arg("v"), - py::arg("v_min") = 0.0f, - py::arg("v_max") = 0.0f, + py::arg("v_min"), + py::arg("v_max"), py::arg("format") = "%.3f", py::arg("power") = 1.0f); m.def( @@ -833,8 +833,8 @@ void bind_imgui_methods(py::module& m) { }, py::arg("label"), py::arg("v"), - py::arg("v_min") = 0.0f, - py::arg("v_max") = 0.0f, + py::arg("v_min"), + py::arg("v_max"), py::arg("format") = "%.3f", py::arg("power") = 1.0f); m.def( @@ -850,8 +850,8 @@ void bind_imgui_methods(py::module& m) { }, py::arg("label"), py::arg("v"), - py::arg("v_min") = 0.0f, - py::arg("v_max") = 0.0f, + py::arg("v_min"), + py::arg("v_max"), py::arg("format") = "%.3f", py::arg("power") = 1.0f); m.def( @@ -867,8 +867,8 @@ void bind_imgui_methods(py::module& m) { }, py::arg("label"), py::arg("v"), - py::arg("v_min") = 0.0f, - py::arg("v_max") = 0.0f, + py::arg("v_min"), + py::arg("v_max"), py::arg("format") = "%.3f", py::arg("power") = 1.0f); From d3bb56e941a67817ec03a673f6255dfbdeccbfb9 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sun, 26 Nov 2023 19:22:25 -0800 Subject: [PATCH 80/88] custom fonts in imgui demo --- test/imgui_demo.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/test/imgui_demo.py b/test/imgui_demo.py index 3349ceb..7861938 100644 --- a/test/imgui_demo.py +++ b/test/imgui_demo.py @@ -31,6 +31,8 @@ def main(): ui_text = "some input text" ui_options = ["option A", "option B", "option C"] ui_options_selected = ui_options[1] + + alt_font = None def my_function(): # ... do something important here ... @@ -149,8 +151,27 @@ def callback(): psim.PopItemWidth() + # Create an annotation window, use a custom font + if False: + psim.SetNextWindowPos((200, 200)) + psim.SetNextWindowSize((100, 50)) + psim.SetNextWindowBgAlpha(0.8) + psim.Begin("Annotation Window", None, psim.ImGuiWindowFlags_NoDecoration) + psim.PushFont(alt_font) + psim.TextUnformatted("annotation text") + psim.PopFont() + psim.End() + polyscope.init() polyscope.set_user_callback(callback) + + + # Load a custom font + if False: + io = psim.GetIO() + alt_font = io.Fonts.AddFontFromFileTTF("your_font_file.otf", 50) + + polyscope.show() if __name__ == '__main__': From a466b0d2535d4e8b913fda57ddc46796033db63e Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sun, 26 Nov 2023 20:33:27 -0800 Subject: [PATCH 81/88] add missing buffer unregister --- src/polyscope/managed_buffer.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/polyscope/managed_buffer.py b/src/polyscope/managed_buffer.py index f86d304..787844a 100644 --- a/src/polyscope/managed_buffer.py +++ b/src/polyscope/managed_buffer.py @@ -14,12 +14,17 @@ class ManagedBuffer: # End users should not call this constructor, use structure.get_buffer("name") or another like it def __init__(self, instance, buffer_type): + self.uniqueID = None # gets overwritten below, setting early so it is defined if __del__ gets called + self.bound_buffer = instance self.buffer_type = buffer_type self.device_buffer_type = self.bound_buffer.get_device_buffer_type() self.buffer_weak_ref = self.bound_buffer.get_generic_weak_handle() self.uniqueID = self.buffer_weak_ref.get_unique_ID() + + def __del__(self): + self.release_mapped_buffer_CUDAOpenGL() def check_ref_still_valid(self): if not self.buffer_weak_ref.is_valid(): From 5cb57b56d476023f678d284608886f91e48a1cc7 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Mon, 27 Nov 2023 02:13:15 -0800 Subject: [PATCH 82/88] update dep --- deps/polyscope | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/polyscope b/deps/polyscope index 92b1dee..92478c3 160000 --- a/deps/polyscope +++ b/deps/polyscope @@ -1 +1 @@ -Subproject commit 92b1deebc7bc3e0019098d82a187eb6d50e27d83 +Subproject commit 92478c32c0aec628372ee0f9163bee4f6d7a744d From 4243a2a8d27fc7dac48f88b229080e532ef3593e Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Mon, 27 Nov 2023 02:13:33 -0800 Subject: [PATCH 83/88] small fixes for camera parameters --- src/polyscope/core.py | 13 ++++++++++--- test/polyscope_test.py | 3 +++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/polyscope/core.py b/src/polyscope/core.py index 5d95130..aa6e63d 100644 --- a/src/polyscope/core.py +++ b/src/polyscope/core.py @@ -428,7 +428,6 @@ def __init__(self, root=None, look_dir=None, up_dir=None, mat=None, instance=Non else: raise ValueError("bad arguments, must pass non-None (root,look_dir,up_dir) or non-None mat") -# TODO still needs tests class CameraParameters: def __init__(self, intrinsics=None, extrinsics=None, instance=None): @@ -451,8 +450,16 @@ def get_right_dir(self): return self.instance.get_right_dir() def get_camera_frame(self): return self.instance.get_camera_frame() def get_fov_vertical_deg(self): return self.instance.get_fov_vertical_deg() def get_aspect(self): return self.instance.get_aspect() - def generate_camera_rays(self): return self.instance.generate_camera_rays() - def generate_camera_ray_corners(self): return self.instance.generate_camera_ray_corners() + + def generate_camera_rays(self, dims, image_origin='upper_left'): + out_rays = self.instance.generate_camera_rays( + int(dims[0]), int(dims[1]), + str_to_image_origin(image_origin) + ) + return out_rays.reshape(dims[0], dims[1], 3) + + def generate_camera_ray_corners(self): + return self.instance.generate_camera_ray_corners() ## Small utilities diff --git a/test/polyscope_test.py b/test/polyscope_test.py index 0447e3f..893a836 100644 --- a/test/polyscope_test.py +++ b/test/polyscope_test.py @@ -1946,6 +1946,9 @@ def test_camera_parameters(self): self.assertTrue(isinstance(params.get_fov_vertical_deg(), float)) self.assertTrue(isinstance(params.get_aspect(), float)) + + rays = params.generate_camera_rays((300,200)) + ray_corners = params.generate_camera_ray_corners() def test_floating_scalar_images(self): From f4f0880adca171a0f0d3806bd8bbe2e36581427d Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Wed, 29 Nov 2023 22:52:49 -0800 Subject: [PATCH 84/88] bind uv seam and islands --- deps/polyscope | 2 +- src/cpp/core.cpp | 1 + src/cpp/surface_mesh.cpp | 9 +++++++-- src/polyscope/common.py | 9 +++++++++ src/polyscope/core.py | 1 + 5 files changed, 19 insertions(+), 3 deletions(-) diff --git a/deps/polyscope b/deps/polyscope index 92478c3..ccb540d 160000 --- a/deps/polyscope +++ b/deps/polyscope @@ -1 +1 @@ -Subproject commit 92478c32c0aec628372ee0f9163bee4f6d7a744d +Subproject commit ccb540db21d2d934e8323ad2f53b4e48d394c84c diff --git a/src/cpp/core.cpp b/src/cpp/core.cpp index 0901311..de446a7 100644 --- a/src/cpp/core.cpp +++ b/src/cpp/core.cpp @@ -409,6 +409,7 @@ PYBIND11_MODULE(polyscope_bindings, m) { py::enum_(m, "ParamVizStyle") .value("checker", ps::ParamVizStyle::CHECKER) + .value("checker_islands", ps::ParamVizStyle::CHECKER_ISLANDS) .value("grid", ps::ParamVizStyle::GRID) .value("local_check", ps::ParamVizStyle::LOCAL_CHECK) .value("local_rad", ps::ParamVizStyle::LOCAL_RAD) diff --git a/src/cpp/surface_mesh.cpp b/src/cpp/surface_mesh.cpp index 6ca8e49..7c5a043 100644 --- a/src/cpp/surface_mesh.cpp +++ b/src/cpp/surface_mesh.cpp @@ -7,6 +7,7 @@ #include "polyscope/polyscope.h" #include "polyscope/surface_mesh.h" +#include "polyscope/curve_network.h" #include "utils.h" @@ -38,14 +39,18 @@ void bind_surface_mesh(py::module& m) { .def("set_grid_colors", &ps::SurfaceCornerParameterizationQuantity::setGridColors, "Set grid colors") .def("set_checker_colors", &ps::SurfaceCornerParameterizationQuantity::setCheckerColors, "Set checker colors") .def("set_checker_size", &ps::SurfaceCornerParameterizationQuantity::setCheckerSize, "Set checker size") - .def("set_color_map", &ps::SurfaceCornerParameterizationQuantity::setColorMap, "Set color map"); + .def("set_color_map", &ps::SurfaceCornerParameterizationQuantity::setColorMap, "Set color map") + .def("set_island_labels", &ps::SurfaceCornerParameterizationQuantity::setIslandLabels) + .def("create_curve_network_from_seams", &ps::SurfaceCornerParameterizationQuantity::createCurveNetworkFromSeams, py::return_value_policy::reference); py::class_(m, "SurfaceVertexParameterizationQuantity") .def("set_enabled", &ps::SurfaceVertexParameterizationQuantity::setEnabled, "Set enabled") .def("set_style", &ps::SurfaceVertexParameterizationQuantity::setStyle, "Set style") .def("set_grid_colors", &ps::SurfaceVertexParameterizationQuantity::setGridColors, "Set grid colors") .def("set_checker_colors", &ps::SurfaceVertexParameterizationQuantity::setCheckerColors, "Set checker colors") .def("set_checker_size", &ps::SurfaceVertexParameterizationQuantity::setCheckerSize, "Set checker size") - .def("set_color_map", &ps::SurfaceVertexParameterizationQuantity::setColorMap, "Set color map"); + .def("set_color_map", &ps::SurfaceVertexParameterizationQuantity::setColorMap, "Set color map") + .def("set_island_labels", &ps::SurfaceVertexParameterizationQuantity::setIslandLabels) + .def("create_curve_network_from_seams", &ps::SurfaceVertexParameterizationQuantity::createCurveNetworkFromSeams, py::return_value_policy::reference); // Vector quantities bindVectorQuantity(m, "SurfaceVertexVectorQuantity"); diff --git a/src/polyscope/common.py b/src/polyscope/common.py index 7cf19d7..931268e 100644 --- a/src/polyscope/common.py +++ b/src/polyscope/common.py @@ -104,6 +104,15 @@ def process_parameterization_args(structure, quantity, parameterization_args): if val is not None: quantity.set_color_map(val) + val = check_and_pop_arg(parameterization_args, 'island_labels') + if val is not None: + if len(val.shape) != 1: raise ValueError("'island_labels' should be an (N_faces,) numpy array") + quantity.set_island_labels(val) + + val = check_and_pop_arg(parameterization_args, 'create_curve_network_from_seams') + if val is not None: + quantity.create_curve_network_from_seams(val) + # Process args, removing them from the dict if they are present def process_image_args(structure, quantity, image_args): diff --git a/src/polyscope/core.py b/src/polyscope/core.py index aa6e63d..4411f9f 100644 --- a/src/polyscope/core.py +++ b/src/polyscope/core.py @@ -626,6 +626,7 @@ def str_to_param_coords_type(s): def str_to_param_viz_style(s): d = { "checker" : psb.ParamVizStyle.checker, + "checker_islands" : psb.ParamVizStyle.checker_islands, "grid" : psb.ParamVizStyle.grid, "local_check" : psb.ParamVizStyle.local_check, "local_rad" : psb.ParamVizStyle.local_rad, From 3ad84d7ed21d1f2dcffb178808002c2fae703d70 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sun, 24 Dec 2023 10:54:47 -0500 Subject: [PATCH 85/88] add missing mark bindings --- src/cpp/surface_mesh.cpp | 4 ++++ src/polyscope/surface_mesh.py | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/src/cpp/surface_mesh.cpp b/src/cpp/surface_mesh.cpp index 7c5a043..7eacdb6 100644 --- a/src/cpp/surface_mesh.cpp +++ b/src/cpp/surface_mesh.cpp @@ -97,6 +97,10 @@ void bind_surface_mesh(py::module& m) { .def("set_halfedge_permutation", &ps::SurfaceMesh::setHalfedgePermutation, "Set halfedge permutation") .def("set_corner_permutation", &ps::SurfaceMesh::setCornerPermutation, "Set corner permutation") + + .def("mark_edges_as_used", &ps::SurfaceMesh::markEdgesAsUsed) + .def("mark_halfedges_as_used", &ps::SurfaceMesh::markHalfedgesAsUsed) + .def("mark_corners_as_used", &ps::SurfaceMesh::markCornersAsUsed) // = quantities diff --git a/src/polyscope/surface_mesh.py b/src/polyscope/surface_mesh.py index 1f2203b..1badab0 100644 --- a/src/polyscope/surface_mesh.py +++ b/src/polyscope/surface_mesh.py @@ -121,6 +121,16 @@ def set_back_face_color(self, val): self.bound_instance.set_back_face_color(glm3(val)) def get_back_face_color(self): return self.bound_instance.get_back_face_color().as_tuple() + + + def mark_edges_as_used(self): + self.bound_instance.mark_edges_as_used() + + def mark_halfedges_as_used(self): + self.bound_instance.mark_halfedges_as_used() + + def mark_corners_as_used(self): + self.bound_instance.mark_corners_as_used() ## Permutations and bases From ddfef9f1f90b5ce0625aea8c157d66cdaa15fc01 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sun, 24 Dec 2023 10:55:07 -0500 Subject: [PATCH 86/88] add missing volume grid bindings, fix data layout order bug --- src/cpp/volume_grid.cpp | 2 ++ src/polyscope/volume_grid.py | 17 ++++++++++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/cpp/volume_grid.cpp b/src/cpp/volume_grid.cpp index b1b1d3f..683bc48 100644 --- a/src/cpp/volume_grid.cpp +++ b/src/cpp/volume_grid.cpp @@ -59,6 +59,8 @@ void bind_volume_grid(py::module& m) { .def("get_material", &ps::VolumeGrid::getMaterial, "Get material") .def("set_edge_width", &ps::VolumeGrid::setEdgeWidth, "Set edge width") .def("get_edge_width", &ps::VolumeGrid::getEdgeWidth, "Get edge width") + .def("set_cube_size_factor", &ps::VolumeGrid::setCubeSizeFactor, "Set cube size factor") + .def("get_cube_size_factor", &ps::VolumeGrid::getCubeSizeFactor, "Get cube size factor") // = quantities diff --git a/src/polyscope/volume_grid.py b/src/polyscope/volume_grid.py index bfb483f..ef7ce7a 100644 --- a/src/polyscope/volume_grid.py +++ b/src/polyscope/volume_grid.py @@ -94,6 +94,12 @@ def set_edge_width(self, val): def get_edge_width(self): return self.bound_instance.get_edge_width() + # Edge width + def set_cube_size_factor(self, val): + self.bound_instance.set_cube_size_factor(val) + def get_cube_size_factor(self): + return self.bound_instance.get_cube_size_factor() + # Material def set_material(self, mat): self.bound_instance.set_material(mat) @@ -114,17 +120,20 @@ def mark_cells_as_used(self): def add_scalar_quantity(self, name, values, defined_on='nodes', datatype="standard", **scalar_args): + # NOTE: notice the .flatten('F') below to flatten in Fortran order. This assumes the input data is indexed like array[xInd,yInd,zInd], + # and converts to the internal x-changes-fastest data layout that the volume grid uses. + if defined_on == 'nodes': if values.shape != self.get_grid_node_dim(): raise ValueError(f"'values' should be a {self.get_grid_node_dim()} array") - q = self.bound_instance.add_node_scalar_quantity(name, values.flatten(), str_to_datatype(datatype)) + q = self.bound_instance.add_node_scalar_quantity(name, values.flatten('F'), str_to_datatype(datatype)) elif defined_on == 'cells': if values.shape != self.get_grid_cell_dim(): raise ValueError(f"'values' should be a {self.get_grid_cell_dim()} array") - q = self.bound_instance.add_cell_scalar_quantity(name, values.flatten(), str_to_datatype(datatype)) + q = self.bound_instance.add_cell_scalar_quantity(name, values.flatten('F'), str_to_datatype(datatype)) else: raise ValueError("bad `defined_on` value {}, should be one of ['nodes', 'cells']".format(defined_on)) @@ -160,7 +169,7 @@ def add_scalar_quantity_from_callable(self, name, func, defined_on='nodes', data check_all_args_processed(self, q, scalar_args) -def register_volume_grid(name, bound_low, bound_high, node_dims, enabled=None, color=None, edge_color=None, edge_width=None, material=None, transparency=None): +def register_volume_grid(name, bound_low, bound_high, node_dims, enabled=None, color=None, edge_color=None, edge_width=None, cube_size_factor=None, material=None, transparency=None): """Register a new volume grid""" @@ -178,6 +187,8 @@ def register_volume_grid(name, bound_low, bound_high, node_dims, enabled=None, c p.set_edge_color(edge_color) if edge_width is not None: p.set_edge_width(edge_width) + if cube_size_factor is not None: + p.set_cube_size_factor(cube_size_factor) if material is not None: p.set_material(material) if transparency is not None: From b5d19cb23e53718fe301ba2295172f5ab70bd33c Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sun, 24 Dec 2023 10:55:12 -0500 Subject: [PATCH 87/88] more tests --- test/polyscope_test.py | 42 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/test/polyscope_test.py b/test/polyscope_test.py index 893a836..a80dd3d 100644 --- a/test/polyscope_test.py +++ b/test/polyscope_test.py @@ -32,6 +32,30 @@ class TestCore(unittest.TestCase): def test_show(self): ps.show(forFrames=3) + def test_frame_tick(self): + for i in range(4): + ps.frame_tick() + + def test_frame_tick_imgui(self): + def callback(): + psim.Button("test button") + ps.set_user_callback(callback) + for i in range(4): + ps.frame_tick() + + def test_unshow(self): + counts = [0] + def callback(): + counts[0] = counts[0] + 1 + if(counts[0] > 2): + ps.unshow() + + ps.set_user_callback(callback) + ps.show(10) + + self.assertLess(counts[0], 4) + + def test_options(self): # Remember, polyscope has global state, so we're actually setting these for the remainder of the tests (lol) @@ -1068,7 +1092,16 @@ def test_options(self): # Transparency p.set_transparency(0.8) self.assertAlmostEqual(0.8, p.get_transparency()) - + + # Mark elements as used + # p.set_corner_permutation(np.random.permutation(p.n_corners())) # not required + p.mark_corners_as_used() + p.set_edge_permutation(np.random.permutation(p.n_edges())) + p.mark_edges_as_used() + p.set_halfedge_permutation(np.random.permutation(p.n_halfedges())) + p.mark_halfedges_as_used() + + # Set with optional arguments p2 = ps.register_surface_mesh("test_mesh", self.generate_verts(), self.generate_faces(), enabled=True, material='wax', color=(1., 0., 0.), edge_color=(0.5, 0.5, 0.5), @@ -1711,6 +1744,11 @@ def test_options(self): ps.show(3) self.assertAlmostEqual(p.get_edge_width(), 1.5) + # Cube size factor + p.set_cube_size_factor(0.5) + ps.show(3) + self.assertAlmostEqual(p.get_cube_size_factor(), 0.5) + # Material p.set_material("candy") self.assertEqual("candy", p.get_material()) @@ -1722,7 +1760,7 @@ def test_options(self): # Set with optional arguments p2 = ps.register_volume_grid("test_grid", (0.,0.,0,), (1., 1., 1.), (10,12,14), - enabled=True, material='wax', color=(1., 0., 0.), edge_color=(0.5, 0.5, 0.5), edge_width=0.5, transparency=0.9) + enabled=True, material='wax', color=(1., 0., 0.), edge_color=(0.5, 0.5, 0.5), edge_width=0.5, cube_size_factor=0.5, transparency=0.9) ps.show(3) From c9f03b4e259033d905c4f66ef96ad4de9b2c2be9 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Wed, 27 Dec 2023 15:45:28 -0500 Subject: [PATCH 88/88] update dep --- deps/polyscope | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/polyscope b/deps/polyscope index ccb540d..07c5695 160000 --- a/deps/polyscope +++ b/deps/polyscope @@ -1 +1 @@ -Subproject commit ccb540db21d2d934e8323ad2f53b4e48d394c84c +Subproject commit 07c5695708b88014c3a87a80b480a077419adf00