From faae5648f10e2738d6b12e681cc9ed20f096130e Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sat, 9 May 2026 18:01:53 -0700 Subject: [PATCH 1/5] add simple triangle mesh support --- CMakeLists.txt | 1 + src/cpp/core.cpp | 2 + src/cpp/simple_triangle_mesh.cpp | 99 ++++++++++++ src/polyscope/__init__.py | 1 + src/polyscope/simple_triangle_mesh.py | 204 +++++++++++++++++++++++++ test/tests/test_all.py | 208 ++++++++++++++++++++++++++ 6 files changed, 515 insertions(+) create mode 100644 src/cpp/simple_triangle_mesh.cpp create mode 100644 src/polyscope/simple_triangle_mesh.py diff --git a/CMakeLists.txt b/CMakeLists.txt index c7679ff..50e82b5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,6 +34,7 @@ add_subdirectory(deps/polyscope) nanobind_add_module(polyscope_bindings src/cpp/core.cpp src/cpp/surface_mesh.cpp + src/cpp/simple_triangle_mesh.cpp src/cpp/point_cloud.cpp src/cpp/curve_network.cpp src/cpp/volume_mesh.cpp diff --git a/src/cpp/core.cpp b/src/cpp/core.cpp index 9f568a9..ca54c34 100644 --- a/src/cpp/core.cpp +++ b/src/cpp/core.cpp @@ -26,6 +26,7 @@ namespace ps = polyscope; // Forward-declare bindings from other files void bind_surface_mesh(nb::module_& m); +void bind_simple_triangle_mesh(nb::module_& m); void bind_point_cloud(nb::module_& m); void bind_curve_network(nb::module_& m); void bind_volume_mesh(nb::module_& m); @@ -732,6 +733,7 @@ NB_MODULE(polyscope_bindings, m) { bind_point_cloud(m); bind_curve_network(m); bind_surface_mesh(m); + bind_simple_triangle_mesh(m); bind_volume_mesh(m); bind_volume_grid(m); bind_sparse_volume_grid(m); diff --git a/src/cpp/simple_triangle_mesh.cpp b/src/cpp/simple_triangle_mesh.cpp new file mode 100644 index 0000000..6e5d4da --- /dev/null +++ b/src/cpp/simple_triangle_mesh.cpp @@ -0,0 +1,99 @@ +#include "Eigen/Dense" + +#include "polyscope/polyscope.h" +#include "polyscope/simple_triangle_mesh.h" + +#include "utils.h" + +void bind_simple_triangle_mesh(nb::module_& m) { + + // == Quantity types + + // Scalar quantities + bindScalarQuantity(m, "SimpleTriangleMeshVertexScalarQuantity") + .def("update_data", &ps::SimpleTriangleMeshVertexScalarQuantity::updateData, + nb::arg("values"), "Update scalar values (count must stay the same)"); + bindScalarQuantity(m, "SimpleTriangleMeshFaceScalarQuantity") + .def("update_data", &ps::SimpleTriangleMeshFaceScalarQuantity::updateData, + nb::arg("values"), "Update scalar values (count must stay the same)"); + + // Color quantities + bindColorQuantity(m, "SimpleTriangleMeshVertexColorQuantity") + .def("update_data", &ps::SimpleTriangleMeshVertexColorQuantity::updateData, + nb::arg("values"), "Update color values (count must stay the same)"); + bindColorQuantity(m, "SimpleTriangleMeshFaceColorQuantity") + .def("update_data", &ps::SimpleTriangleMeshFaceColorQuantity::updateData, + nb::arg("values"), "Update color values (count must stay the same)"); + + + // == Helper classes + nb::class_(m, "SimpleTriangleMeshPickResult") + .def(nb::init<>()) + .def_ro("element_type", &ps::SimpleTriangleMeshPickResult::elementType) + .def_ro("index", &ps::SimpleTriangleMeshPickResult::index); + + + // == Main class + bindStructure(m, "SimpleTriangleMesh") + + // basics + .def("n_vertices", &ps::SimpleTriangleMesh::nVertices, "# vertices") + .def("n_faces", &ps::SimpleTriangleMesh::nFaces, "# faces") + + // update + .def("update_vertex_positions", &ps::SimpleTriangleMesh::updateVertexPositions, + "Update vertex positions (vertex count must stay the same)") + .def("update", &ps::SimpleTriangleMesh::update, + nb::arg("vertices"), nb::arg("faces"), + "Update vertices and faces (counts may change)") + .def("update_mesh", &ps::SimpleTriangleMesh::updateMesh, + nb::arg("vertices"), nb::arg("faces"), + "Update vertices and faces (counts may change), also updates object-space bounds") + .def("reserve", &ps::SimpleTriangleMesh::reserve, + nb::arg("n_verts"), nb::arg("n_faces"), + "Pre-allocate capacity to avoid reallocations on future updates") + + // options + .def("set_color", &ps::SimpleTriangleMesh::setSurfaceColor, "Set surface color") + .def("get_color", &ps::SimpleTriangleMesh::getSurfaceColor, "Get surface color") + .def("set_back_face_policy", &ps::SimpleTriangleMesh::setBackFacePolicy, "Set back face policy") + .def("get_back_face_policy", &ps::SimpleTriangleMesh::getBackFacePolicy, "Get back face policy") + .def("set_back_face_color", &ps::SimpleTriangleMesh::setBackFaceColor, "Set back face color") + .def("get_back_face_color", &ps::SimpleTriangleMesh::getBackFaceColor, "Get back face color") + .def("set_material", &ps::SimpleTriangleMesh::setMaterial, "Set material") + .def("get_material", &ps::SimpleTriangleMesh::getMaterial, "Get material") + .def("set_selection_mode", &ps::SimpleTriangleMesh::setSelectionMode, "Set selection mode") + .def("get_selection_mode", &ps::SimpleTriangleMesh::getSelectionMode, "Get selection mode") + + // picking + .def("interpret_pick_result", &ps::SimpleTriangleMesh::interpretPickResult) + + // quantities — scalars + .def("add_vertex_scalar_quantity", &ps::SimpleTriangleMesh::addVertexScalarQuantity, + "Add a scalar quantity at vertices", nb::arg("name"), nb::arg("values"), + nb::arg("data_type") = ps::DataType::STANDARD, nb::rv_policy::reference) + .def("add_face_scalar_quantity", &ps::SimpleTriangleMesh::addFaceScalarQuantity, + "Add a scalar quantity at faces", nb::arg("name"), nb::arg("values"), + nb::arg("data_type") = ps::DataType::STANDARD, nb::rv_policy::reference) + + // quantities — colors + .def("add_vertex_color_quantity", &ps::SimpleTriangleMesh::addVertexColorQuantity, + "Add a color quantity at vertices", nb::arg("name"), nb::arg("values"), nb::rv_policy::reference) + .def("add_face_color_quantity", &ps::SimpleTriangleMesh::addFaceColorQuantity, + "Add a color quantity at faces", nb::arg("name"), nb::arg("values"), nb::rv_policy::reference); + + + // == Module-level functions + m.def("register_simple_triangle_mesh", + &ps::registerSimpleTriangleMesh, + nb::arg("name"), nb::arg("vertices"), nb::arg("faces"), + "Register a simple triangle mesh", nb::rv_policy::reference); + + m.def("remove_simple_triangle_mesh", &ps::removeSimpleTriangleMesh, + nb::arg("name"), nb::arg("error_if_absent") = false, + "Remove a simple triangle mesh by name"); + m.def("get_simple_triangle_mesh", &ps::getSimpleTriangleMesh, + nb::arg("name") = "", "Get a simple triangle mesh by name", nb::rv_policy::reference); + m.def("has_simple_triangle_mesh", &ps::hasSimpleTriangleMesh, + nb::arg("name") = "", "Check for a simple triangle mesh by name"); +} diff --git a/src/polyscope/__init__.py b/src/polyscope/__init__.py index 493b30e..749db01 100644 --- a/src/polyscope/__init__.py +++ b/src/polyscope/__init__.py @@ -51,6 +51,7 @@ from polyscope.device_interop import * # noqa F403 from polyscope.surface_mesh import * # noqa F403 +from polyscope.simple_triangle_mesh import * # noqa F403 from polyscope.point_cloud import * # noqa F403 from polyscope.curve_network import * # noqa F403 from polyscope.volume_mesh import * # noqa F403 diff --git a/src/polyscope/simple_triangle_mesh.py b/src/polyscope/simple_triangle_mesh.py new file mode 100644 index 0000000..e15f467 --- /dev/null +++ b/src/polyscope/simple_triangle_mesh.py @@ -0,0 +1,204 @@ +from typing import Any, Literal, cast + +import sys +import polyscope_bindings as psb + +from polyscope.core import glm3 +from polyscope.enums import to_enum, from_enum +from polyscope.structure import Structure +from polyscope.common import ( + QuantityArgsBase, + process_quantity_args, + ScalarQuantityArgs, + ScalarArgsBase, + process_scalar_args, + ColorQuantityArgs, + ColorArgsBase, + process_color_args, + check_all_args_processed, +) + +import numpy as np +from numpy.typing import NDArray, ArrayLike + +if sys.version_info >= (3, 11): + from typing import Unpack +else: + from typing_extensions import Unpack + + +class SimpleTriangleMesh(Structure): + # This class wraps a _reference_ to the underlying object, whose lifetime is managed by Polyscope + bound_instance: psb.SimpleTriangleMesh + + def __init__( + self, + name: str | None = None, + vertices: ArrayLike | None = None, + faces: ArrayLike | None = None, + instance: psb.SimpleTriangleMesh | None = None, + ) -> None: + super().__init__() + + if instance is not None: + self.bound_instance = instance + else: + assert name is not None + assert vertices is not None + assert faces is not None + + vertices_arr = np.array(vertices, dtype=np.float32) + faces_arr = np.array(faces, dtype=np.int32) + + if vertices_arr.ndim != 2 or vertices_arr.shape[1] != 3: + raise ValueError( + "simple triangle mesh vertices should have shape (N, 3); got " + str(vertices_arr.shape) + ) + if faces_arr.ndim != 2 or faces_arr.shape[1] != 3: + raise ValueError( + "simple triangle mesh faces should have shape (F, 3); got " + str(faces_arr.shape) + ) + + self.bound_instance = psb.register_simple_triangle_mesh(name, vertices_arr, faces_arr) + + def n_vertices(self) -> int: + return self.bound_instance.n_vertices() + + def n_faces(self) -> int: + return self.bound_instance.n_faces() + + def update_vertex_positions(self, vertices: ArrayLike) -> None: + """Update vertex positions. Vertex count must stay the same.""" + vertices_arr = np.array(vertices, dtype=np.float32) + self.bound_instance.update_vertex_positions(vertices_arr) + + def update(self, vertices: ArrayLike, faces: ArrayLike) -> None: + """Update vertices and faces; counts may change.""" + vertices_arr = np.array(vertices, dtype=np.float32) + faces_arr = np.array(faces, dtype=np.int32) + self.bound_instance.update(vertices_arr, faces_arr) + + def reserve(self, n_verts: int, n_faces: int) -> None: + """Pre-allocate capacity to avoid reallocations on future updates.""" + self.bound_instance.reserve(n_verts, n_faces) + + # Picking + def append_pick_data(self, pick_result: Any) -> None: + struct_result = self.bound_instance.interpret_pick_result(pick_result.raw_result) + pick_result.structure_data["element_type"] = from_enum(struct_result.element_type) + pick_result.structure_data["index"] = struct_result.index + + ## Options + + def set_color(self, val: ArrayLike) -> "SimpleTriangleMesh": + self.bound_instance.set_color(glm3(val)) + return self + + def get_color(self) -> tuple[float, float, float]: + return self.bound_instance.get_color().as_tuple() + + def set_back_face_policy(self, val: str) -> "SimpleTriangleMesh": + self.bound_instance.set_back_face_policy(to_enum(psb.BackFacePolicy, val)) + return self + + def get_back_face_policy(self) -> str: + return from_enum(self.bound_instance.get_back_face_policy()) + + def set_back_face_color(self, val: ArrayLike) -> "SimpleTriangleMesh": + self.bound_instance.set_back_face_color(glm3(val)) + return self + + def get_back_face_color(self) -> tuple[float, float, float]: + return self.bound_instance.get_back_face_color().as_tuple() + + def set_material(self, mat: str) -> "SimpleTriangleMesh": + self.bound_instance.set_material(mat) + return self + + def get_material(self) -> str: + return self.bound_instance.get_material() + + def set_selection_mode(self, val: str) -> "SimpleTriangleMesh": + self.bound_instance.set_selection_mode(to_enum(psb.MeshSelectionMode, val)) + return self + + def get_selection_mode(self) -> str: + return from_enum(self.bound_instance.get_selection_mode()) + + ## Quantities + + def add_vertex_scalar_quantity( + self, + name: str, + values: ArrayLike, + datatype: Literal["standard", "symmetric", "magnitude"] | str = "standard", + **scalar_args: Unpack[ScalarQuantityArgs], + ) -> psb.SimpleTriangleMeshVertexScalarQuantity: + values_arr = np.asarray(values, dtype=np.float32) + q = self.bound_instance.add_vertex_scalar_quantity(name, values_arr, to_enum(psb.DataType, datatype)) + process_quantity_args(self, q, cast(QuantityArgsBase, scalar_args)) + process_scalar_args(self, q, cast(ScalarArgsBase, scalar_args)) + check_all_args_processed(self, q, scalar_args) + return q + + def add_face_scalar_quantity( + self, + name: str, + values: ArrayLike, + datatype: Literal["standard", "symmetric", "magnitude"] | str = "standard", + **scalar_args: Unpack[ScalarQuantityArgs], + ) -> psb.SimpleTriangleMeshFaceScalarQuantity: + values_arr = np.asarray(values, dtype=np.float32) + q = self.bound_instance.add_face_scalar_quantity(name, values_arr, to_enum(psb.DataType, datatype)) + process_quantity_args(self, q, cast(QuantityArgsBase, scalar_args)) + process_scalar_args(self, q, cast(ScalarArgsBase, scalar_args)) + check_all_args_processed(self, q, scalar_args) + return q + + def add_vertex_color_quantity( + self, + name: str, + values: ArrayLike, + **color_args: Unpack[ColorQuantityArgs], + ) -> psb.SimpleTriangleMeshVertexColorQuantity: + values_arr = np.asarray(values, dtype=np.float32) + if values_arr.ndim != 2 or values_arr.shape[1] != 3: + raise ValueError("'values' should be an Nx3 array") + q = self.bound_instance.add_vertex_color_quantity(name, values_arr) + process_quantity_args(self, q, cast(QuantityArgsBase, color_args)) + process_color_args(self, q, cast(ColorArgsBase, color_args)) + check_all_args_processed(self, q, color_args) + return q + + def add_face_color_quantity( + self, + name: str, + values: ArrayLike, + **color_args: Unpack[ColorQuantityArgs], + ) -> psb.SimpleTriangleMeshFaceColorQuantity: + values_arr = np.asarray(values, dtype=np.float32) + if values_arr.ndim != 2 or values_arr.shape[1] != 3: + raise ValueError("'values' should be an Nx3 array") + q = self.bound_instance.add_face_color_quantity(name, values_arr) + process_quantity_args(self, q, cast(QuantityArgsBase, color_args)) + process_color_args(self, q, cast(ColorArgsBase, color_args)) + check_all_args_processed(self, q, color_args) + return q + + +def register_simple_triangle_mesh( + name: str, vertices: ArrayLike, faces: ArrayLike +) -> SimpleTriangleMesh: + return SimpleTriangleMesh(name=name, vertices=vertices, faces=faces) + + +def remove_simple_triangle_mesh(name: str = "", error_if_absent: bool = False) -> None: + psb.remove_simple_triangle_mesh(name, error_if_absent) + + +def get_simple_triangle_mesh(name: str = "") -> SimpleTriangleMesh: + return SimpleTriangleMesh(instance=psb.get_simple_triangle_mesh(name)) + + +def has_simple_triangle_mesh(name: str = "") -> bool: + return psb.has_simple_triangle_mesh(name) diff --git a/test/tests/test_all.py b/test/tests/test_all.py index 311db83..c3e3d5e 100644 --- a/test/tests/test_all.py +++ b/test/tests/test_all.py @@ -3264,3 +3264,211 @@ def generate_scalar(n_pts=10): # pass ps.show(3) + + +def make_stm(n_verts=20, n_faces=30, seed=42): + """Helper: random simple triangle mesh with given counts.""" + np.random.seed(seed) + verts = np.random.rand(n_verts, 3).astype(np.float32) + faces = np.random.randint(0, n_verts, size=(n_faces, 3)).astype(np.int32) + return verts, faces + + +class TestSimpleTriangleMesh(unittest.TestCase): + def generate_verts(self, n_pts=10): + np.random.seed(777) + return np.random.rand(n_pts, 3) + + def generate_faces(self, n_pts=10): + np.random.seed(777) + return np.random.randint(0, n_pts, size=(2 * n_pts, 3)) + + def test_add_remove(self): + # add + m = ps.register_simple_triangle_mesh("test_stm", self.generate_verts(), self.generate_faces()) + self.assertTrue(ps.has_simple_triangle_mesh("test_stm")) + self.assertFalse(ps.has_simple_triangle_mesh("nope")) + self.assertEqual(m.n_vertices(), 10) + self.assertEqual(m.n_faces(), 20) + + # remove by name + ps.register_simple_triangle_mesh("test_stm2", self.generate_verts(), self.generate_faces()) + ps.remove_simple_triangle_mesh("test_stm2") + self.assertTrue(ps.has_simple_triangle_mesh("test_stm")) + self.assertFalse(ps.has_simple_triangle_mesh("test_stm2")) + + # remove by ref + c = ps.register_simple_triangle_mesh("test_stm2", self.generate_verts(), self.generate_faces()) + c.remove() + self.assertTrue(ps.has_simple_triangle_mesh("test_stm")) + self.assertFalse(ps.has_simple_triangle_mesh("test_stm2")) + + # get by name + ps.register_simple_triangle_mesh("test_stm3", self.generate_verts(), self.generate_faces()) + p = ps.get_simple_triangle_mesh("test_stm3") + self.assertTrue(isinstance(p, ps.SimpleTriangleMesh)) + + ps.remove_all_structures() + + def test_render(self): + ps.register_simple_triangle_mesh("test_stm", self.generate_verts(), self.generate_faces()) + ps.show(3) + ps.remove_all_structures() + + def test_options(self): + p = ps.register_simple_triangle_mesh("test_stm", self.generate_verts(), self.generate_faces()) + + # 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]) + + # Back face policy + p.set_back_face_policy("different") + self.assertEqual("different", p.get_back_face_policy()) + p.set_back_face_policy("custom") + self.assertEqual("custom", p.get_back_face_policy()) + p.set_back_face_color((0.25, 0.25, 0.25)) + self.assertEqual((0.25, 0.25, 0.25), p.get_back_face_color()) + p.set_back_face_policy("cull") + + # Material + p.set_material("candy") + self.assertEqual("candy", p.get_material()) + p.set_material("clay") + + # Selection mode + p.set_selection_mode("vertices_only") + self.assertEqual("vertices_only", p.get_selection_mode()) + p.set_selection_mode("faces_only") + self.assertEqual("faces_only", p.get_selection_mode()) + + ps.show(3) + ps.remove_all_structures() + + def test_update(self): + p = ps.register_simple_triangle_mesh("test_stm", self.generate_verts(), self.generate_faces()) + + # update vertex positions (same count) + new_verts = np.random.rand(10, 3).astype(np.float32) + p.update_vertex_positions(new_verts) + ps.show(3) + + # update both vertices and faces (counts may change) + new_verts2 = np.random.rand(8, 3).astype(np.float32) + new_faces2 = np.random.randint(0, 8, size=(12, 3)).astype(np.int32) + p.update(new_verts2, new_faces2) + self.assertEqual(p.n_vertices(), 8) + self.assertEqual(p.n_faces(), 12) + ps.show(3) + + ps.remove_all_structures() + + def test_reserve(self): + p = ps.register_simple_triangle_mesh("test_stm", self.generate_verts(), self.generate_faces()) + p.reserve(100, 200) # should not raise + ps.remove_all_structures() + + def test_scalar(self): + p = ps.register_simple_triangle_mesh("test_stm", self.generate_verts(), self.generate_faces()) + n_verts = p.n_vertices() + n_faces = p.n_faces() + + # vertex scalar + vals = np.random.rand(n_verts).astype(np.float32) + p.add_vertex_scalar_quantity("vert_scalar", vals, enabled=True) + ps.show(3) + + # vertex scalar with options + p.add_vertex_scalar_quantity("vert_scalar_sym", vals, datatype="symmetric") + p.add_vertex_scalar_quantity("vert_scalar_mag", vals, datatype="magnitude") + ps.show(3) + + # face scalar + f_vals = np.random.rand(n_faces).astype(np.float32) + p.add_face_scalar_quantity("face_scalar", f_vals, enabled=True) + ps.show(3) + + ps.remove_all_structures() + + def test_color(self): + p = ps.register_simple_triangle_mesh("test_stm", self.generate_verts(), self.generate_faces()) + n_verts = p.n_vertices() + n_faces = p.n_faces() + + # vertex color + cols = np.random.rand(n_verts, 3).astype(np.float32) + p.add_vertex_color_quantity("vert_color", cols, enabled=True) + ps.show(3) + + # face color + f_cols = np.random.rand(n_faces, 3).astype(np.float32) + p.add_face_color_quantity("face_color", f_cols, enabled=True) + ps.show(3) + + ps.remove_all_structures() + + def test_update_sizes_and_quantities(self): + sizes = [ + (5, 8), # small + (50, 80), # grow big + (10, 15), # shrink back down + (200, 350), # grow very large + (3, 4), # shrink to minimum + (100, 180), # mid-size to finish + ] + + verts0, faces0 = make_stm(*sizes[0]) + p = ps.register_simple_triangle_mesh("stm_sz", verts0, faces0) + + # Add all four quantity types before any update — they'll be replaced + # on each iteration as the size changes. + v_scalar = p.add_vertex_scalar_quantity( + "v_scalar", np.zeros(sizes[0][0], dtype=np.float32), enabled=True) + f_scalar = p.add_face_scalar_quantity( + "f_scalar", np.zeros(sizes[0][1], dtype=np.float32), enabled=True) + v_color = p.add_vertex_color_quantity( + "v_color", np.zeros((sizes[0][0], 3), dtype=np.float32), enabled=True) + f_color = p.add_face_color_quantity( + "f_color", np.zeros((sizes[0][1], 3), dtype=np.float32), enabled=True) + + ps.show(3) + + for n_verts, n_faces in sizes[1:]: + verts, faces = make_stm(n_verts, n_faces, seed=n_verts) + + # Update the mesh geometry (counts change) + p.update(verts, faces) + self.assertEqual(p.n_vertices(), n_verts) + self.assertEqual(p.n_faces(), n_faces) + + # Re-add quantities with the new size (replaces old ones) + v_scalar_data = np.random.rand(n_verts).astype(np.float32) + f_scalar_data = np.random.rand(n_faces).astype(np.float32) + v_color_data = np.random.rand(n_verts, 3).astype(np.float32) + f_color_data = np.random.rand(n_faces, 3).astype(np.float32) + + v_scalar = p.add_vertex_scalar_quantity("v_scalar", v_scalar_data, enabled=True) + f_scalar = p.add_face_scalar_quantity("f_scalar", f_scalar_data, enabled=True) + v_color = p.add_vertex_color_quantity("v_color", v_color_data, enabled=True) + f_color = p.add_face_color_quantity("f_color", f_color_data, enabled=True) + + ps.show(3) + + # Now test update_data (same size, in-place update) + v_scalar.update_data(np.random.rand(n_verts).astype(np.float32)) + f_scalar.update_data(np.random.rand(n_faces).astype(np.float32)) + v_color.update_data(np.random.rand(n_verts, 3).astype(np.float32)) + f_color.update_data(np.random.rand(n_faces, 3).astype(np.float32)) + + ps.show(3) + + ps.remove_all_structures() From 7736ecc77fe2961f00be1651e8fb2a843da2be14 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sat, 9 May 2026 18:03:21 -0700 Subject: [PATCH 2/5] updates for new managed buffer --- src/cpp/gaussian_particles_structure.cpp | 18 ++++--- src/cpp/managed_buffer.cpp | 66 ++++++++++++------------ 2 files changed, 45 insertions(+), 39 deletions(-) diff --git a/src/cpp/gaussian_particles_structure.cpp b/src/cpp/gaussian_particles_structure.cpp index c0df096..d95a73f 100644 --- a/src/cpp/gaussian_particles_structure.cpp +++ b/src/cpp/gaussian_particles_structure.cpp @@ -110,14 +110,20 @@ void GaussianParticles::ensureImagebuffersAllocated() { // re-allocate buffers depths->setTextureSize(currImageWidth, currImageHeight); - depths->ensureHostBufferAllocated(); - depths->data = std::vector(currImageWidth * currImageHeight, 0.0f); - depths->markHostBufferUpdated(); + { + std::vector depthZeros(currImageWidth * currImageHeight, 0.0f); + depths->resize(depthZeros.size()); + depths->setDataHost(depthZeros); + depths->markHostBufferUpdated(); + } colors->setTextureSize(currImageWidth, currImageHeight); - colors->ensureHostBufferAllocated(); - colors->data = std::vector(currImageWidth * currImageHeight, glm::vec4(0.0f)); - colors->markHostBufferUpdated(); + { + std::vector colorZeros(currImageWidth * currImageHeight, glm::vec4(0.0f)); + colors->resize(colorZeros.size()); + colors->setDataHost(colorZeros); + colors->markHostBufferUpdated(); + } if (imageToScreenProgram) { imageToScreenProgram->setTextureFromBuffer("t_depth", depths->getRenderTextureBuffer().get()); diff --git a/src/cpp/managed_buffer.cpp b/src/cpp/managed_buffer.cpp index b9c6f14..3bc3dbf 100644 --- a/src/cpp/managed_buffer.cpp +++ b/src/cpp/managed_buffer.cpp @@ -15,7 +15,7 @@ nb::class_> bind_managed_buffer_T(nb::module_& m, p return nb::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("has_data", [](const ps::render::ManagedBuffer& b) { return b.size() > 0; }) .def("summary_string", &ps::render::ManagedBuffer::summaryString) .def("get_device_buffer_type", &ps::render::ManagedBuffer::getDeviceBufferType) .def("get_generic_weak_handle", @@ -65,8 +65,8 @@ void bind_managed_buffer(nb::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.ensureHostBufferPopulated(); + for(uint32_t i = 0; i < s.size(); i++) s.setHostValue(i, d(i)); s.markHostBufferUpdated(); }) ; @@ -74,8 +74,8 @@ void bind_managed_buffer(nb::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.ensureHostBufferPopulated(); + for(uint32_t i = 0; i < s.size(); i++) s.setHostValue(i, d(i)); s.markHostBufferUpdated(); }) ; @@ -83,8 +83,8 @@ void bind_managed_buffer(nb::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.ensureHostBufferPopulated(); + for(uint32_t i = 0; i < s.size(); i++) s.setHostValue(i, {d(i,0), d(i,1)}); s.markHostBufferUpdated(); }) ; @@ -92,8 +92,8 @@ void bind_managed_buffer(nb::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.ensureHostBufferPopulated(); + for(uint32_t i = 0; i < s.size(); i++) s.setHostValue(i, {d(i,0), d(i,1), d(i,2)}); s.markHostBufferUpdated(); }) ; @@ -101,8 +101,8 @@ void bind_managed_buffer(nb::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.ensureHostBufferPopulated(); + for(uint32_t i = 0; i < s.size(); i++) s.setHostValue(i, {d(i,0), d(i,1), d(i,2), d(i,3)}); s.markHostBufferUpdated(); }) ; @@ -112,11 +112,11 @@ void bind_managed_buffer(nb::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(); + s.ensureHostBufferPopulated(); 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)}; - } + std::array val; + for(uint32_t k = 0; k < 2; k++) val[k] = {d[k](i,0), d[k](i,1), d[k](i,2)}; + s.setHostValue(i, val); } s.markHostBufferUpdated(); }) @@ -128,11 +128,11 @@ void bind_managed_buffer(nb::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(); + s.ensureHostBufferPopulated(); 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)}; - } + std::array val; + for(uint32_t k = 0; k < 3; k++) val[k] = {d[k](i,0), d[k](i,1), d[k](i,2)}; + s.setHostValue(i, val); } s.markHostBufferUpdated(); }) @@ -143,11 +143,11 @@ void bind_managed_buffer(nb::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(); + s.ensureHostBufferPopulated(); 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)}; - } + std::array val; + for(uint32_t k = 0; k < 4; k++) val[k] = {d[k](i,0), d[k](i,1), d[k](i,2)}; + s.setHostValue(i, val); } s.markHostBufferUpdated(); }) @@ -156,8 +156,8 @@ void bind_managed_buffer(nb::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.ensureHostBufferPopulated(); + for(uint32_t i = 0; i < s.size(); i++) s.setHostValue(i, d(i)); s.markHostBufferUpdated(); }) ; @@ -165,8 +165,8 @@ void bind_managed_buffer(nb::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.ensureHostBufferPopulated(); + for(uint32_t i = 0; i < s.size(); i++) s.setHostValue(i, d(i)); s.markHostBufferUpdated(); }) ; @@ -174,8 +174,8 @@ void bind_managed_buffer(nb::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.ensureHostBufferPopulated(); + for(uint32_t i = 0; i < s.size(); i++) s.setHostValue(i, {d(i,0), d(i,1)}); s.markHostBufferUpdated(); }) ; @@ -184,8 +184,8 @@ void bind_managed_buffer(nb::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.ensureHostBufferPopulated(); + for(uint32_t i = 0; i < s.size(); i++) s.setHostValue(i, {d(i,0), d(i,1), d(i,2)}); s.markHostBufferUpdated(); }) ; @@ -193,8 +193,8 @@ void bind_managed_buffer(nb::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.ensureHostBufferPopulated(); + for(uint32_t i = 0; i < s.size(); i++) s.setHostValue(i, {d(i,0), d(i,1), d(i,2), d(i,3)}); s.markHostBufferUpdated(); }) ; From 921cc1959e9329fd89f63ecd2368fc04c258be40 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sat, 9 May 2026 18:03:27 -0700 Subject: [PATCH 3/5] update dep --- deps/polyscope | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/polyscope b/deps/polyscope index 0c3dd68..07b0621 160000 --- a/deps/polyscope +++ b/deps/polyscope @@ -1 +1 @@ -Subproject commit 0c3dd68b9851417e6b2b976d347adc3250026122 +Subproject commit 07b06212ccedeb608a998e77eff76163879f56b0 From 07a9260d8452338e9afa28a2b329b0e4b6e254d4 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sat, 9 May 2026 22:48:36 -0700 Subject: [PATCH 4/5] update names --- deps/polyscope | 2 +- src/cpp/simple_triangle_mesh.cpp | 7 +-- src/polyscope/simple_triangle_mesh.py | 8 +-- test/tests/test_all.py | 83 +++++++++++++++------------ 4 files changed, 52 insertions(+), 48 deletions(-) diff --git a/deps/polyscope b/deps/polyscope index 07b0621..b02f85d 160000 --- a/deps/polyscope +++ b/deps/polyscope @@ -1 +1 @@ -Subproject commit 07b06212ccedeb608a998e77eff76163879f56b0 +Subproject commit b02f85d15a086bf14a62c6586af6b80806922894 diff --git a/src/cpp/simple_triangle_mesh.cpp b/src/cpp/simple_triangle_mesh.cpp index 6e5d4da..330305d 100644 --- a/src/cpp/simple_triangle_mesh.cpp +++ b/src/cpp/simple_triangle_mesh.cpp @@ -43,13 +43,10 @@ void bind_simple_triangle_mesh(nb::module_& m) { // update .def("update_vertex_positions", &ps::SimpleTriangleMesh::updateVertexPositions, "Update vertex positions (vertex count must stay the same)") - .def("update", &ps::SimpleTriangleMesh::update, - nb::arg("vertices"), nb::arg("faces"), - "Update vertices and faces (counts may change)") .def("update_mesh", &ps::SimpleTriangleMesh::updateMesh, nb::arg("vertices"), nb::arg("faces"), - "Update vertices and faces (counts may change), also updates object-space bounds") - .def("reserve", &ps::SimpleTriangleMesh::reserve, + "Update vertices and faces (counts may change)") + .def("reserve_mesh_capacity", &ps::SimpleTriangleMesh::reserveMeshCapacity, nb::arg("n_verts"), nb::arg("n_faces"), "Pre-allocate capacity to avoid reallocations on future updates") diff --git a/src/polyscope/simple_triangle_mesh.py b/src/polyscope/simple_triangle_mesh.py index e15f467..9866d9f 100644 --- a/src/polyscope/simple_triangle_mesh.py +++ b/src/polyscope/simple_triangle_mesh.py @@ -72,15 +72,15 @@ def update_vertex_positions(self, vertices: ArrayLike) -> None: vertices_arr = np.array(vertices, dtype=np.float32) self.bound_instance.update_vertex_positions(vertices_arr) - def update(self, vertices: ArrayLike, faces: ArrayLike) -> None: + def update_mesh(self, vertices: ArrayLike, faces: ArrayLike) -> None: """Update vertices and faces; counts may change.""" vertices_arr = np.array(vertices, dtype=np.float32) faces_arr = np.array(faces, dtype=np.int32) - self.bound_instance.update(vertices_arr, faces_arr) + self.bound_instance.update_mesh(vertices_arr, faces_arr) - def reserve(self, n_verts: int, n_faces: int) -> None: + def reserve_mesh_capacity(self, n_verts: int, n_faces: int) -> None: """Pre-allocate capacity to avoid reallocations on future updates.""" - self.bound_instance.reserve(n_verts, n_faces) + self.bound_instance.reserve_mesh_capacity(n_verts, n_faces) # Picking def append_pick_data(self, pick_result: Any) -> None: diff --git a/test/tests/test_all.py b/test/tests/test_all.py index c3e3d5e..81b4b61 100644 --- a/test/tests/test_all.py +++ b/test/tests/test_all.py @@ -46,11 +46,12 @@ def callback(): # Make sure that unshow worked, and we stopped short of 10 loop iterations self.assertLess(counts[0], 4) - # Make sure unshow doesn't mess up subsequent calls counts[0] = 0 + def callback(): counts[0] = counts[0] + 1 + ps.set_user_callback(callback) ps.show(3) self.assertEqual(counts[0], 3) @@ -2354,19 +2355,21 @@ def sphere_sdf(pts): class TestSparseVolumeGrid(unittest.TestCase): - def generate_test_grid(self, name="test_sparse_grid"): # Create a small set of occupied cells - occupied_cells = np.array([ - [0, 0, 0], - [1, 0, 0], - [0, 1, 0], - [0, 0, 1], - [1, 1, 0], - [1, 0, 1], - [0, 1, 1], - [1, 1, 1], - ], dtype=np.int32) + occupied_cells = np.array( + [ + [0, 0, 0], + [1, 0, 0], + [0, 1, 0], + [0, 0, 1], + [1, 1, 0], + [1, 0, 1], + [0, 1, 1], + [1, 1, 1], + ], + dtype=np.int32, + ) origin = (0.0, 0.0, 0.0) grid_cell_width = (0.1, 0.1, 0.1) return ps.register_sparse_volume_grid(name, origin, grid_cell_width, occupied_cells), occupied_cells @@ -2535,12 +2538,20 @@ def test_node_scalar(self): p.add_scalar_quantity("test_vals", node_values, defined_on="nodes", node_indices=node_indices) p.add_scalar_quantity("test_vals2", node_values, defined_on="nodes", node_indices=node_indices, enabled=True) p.add_scalar_quantity( - "test_vals_with_range", node_values, defined_on="nodes", node_indices=node_indices, - vminmax=(-5.0, 5.0), enabled=True + "test_vals_with_range", + node_values, + defined_on="nodes", + node_indices=node_indices, + vminmax=(-5.0, 5.0), + enabled=True, ) p.add_scalar_quantity( - "test_vals_with_datatype", node_values, defined_on="nodes", node_indices=node_indices, - enabled=True, datatype="symmetric" + "test_vals_with_datatype", + node_values, + defined_on="nodes", + node_indices=node_indices, + enabled=True, + datatype="symmetric", ) ps.show(3) @@ -3266,7 +3277,7 @@ def generate_scalar(n_pts=10): ps.show(3) -def make_stm(n_verts=20, n_faces=30, seed=42): +def make_simple_triangle_mesn(n_verts=20, n_faces=30, seed=42): """Helper: random simple triangle mesh with given counts.""" np.random.seed(seed) verts = np.random.rand(n_verts, 3).astype(np.float32) @@ -3365,7 +3376,7 @@ def test_update(self): # update both vertices and faces (counts may change) new_verts2 = np.random.rand(8, 3).astype(np.float32) new_faces2 = np.random.randint(0, 8, size=(12, 3)).astype(np.int32) - p.update(new_verts2, new_faces2) + p.update_mesh(new_verts2, new_faces2) self.assertEqual(p.n_vertices(), 8) self.assertEqual(p.n_faces(), 12) ps.show(3) @@ -3374,7 +3385,7 @@ def test_update(self): def test_reserve(self): p = ps.register_simple_triangle_mesh("test_stm", self.generate_verts(), self.generate_faces()) - p.reserve(100, 200) # should not raise + p.reserve_mesh_capacity(100, 200) # should not raise ps.remove_all_structures() def test_scalar(self): @@ -3418,48 +3429,44 @@ def test_color(self): def test_update_sizes_and_quantities(self): sizes = [ - (5, 8), # small - (50, 80), # grow big - (10, 15), # shrink back down + (5, 8), # small + (50, 80), # grow big + (10, 15), # shrink back down (200, 350), # grow very large - (3, 4), # shrink to minimum + (3, 4), # shrink to minimum (100, 180), # mid-size to finish ] - verts0, faces0 = make_stm(*sizes[0]) + verts0, faces0 = make_simple_triangle_mesn(*sizes[0]) p = ps.register_simple_triangle_mesh("stm_sz", verts0, faces0) # Add all four quantity types before any update — they'll be replaced # on each iteration as the size changes. - v_scalar = p.add_vertex_scalar_quantity( - "v_scalar", np.zeros(sizes[0][0], dtype=np.float32), enabled=True) - f_scalar = p.add_face_scalar_quantity( - "f_scalar", np.zeros(sizes[0][1], dtype=np.float32), enabled=True) - v_color = p.add_vertex_color_quantity( - "v_color", np.zeros((sizes[0][0], 3), dtype=np.float32), enabled=True) - f_color = p.add_face_color_quantity( - "f_color", np.zeros((sizes[0][1], 3), dtype=np.float32), enabled=True) + v_scalar = p.add_vertex_scalar_quantity("v_scalar", np.zeros(sizes[0][0], dtype=np.float32), enabled=True) + f_scalar = p.add_face_scalar_quantity("f_scalar", np.zeros(sizes[0][1], dtype=np.float32), enabled=True) + v_color = p.add_vertex_color_quantity("v_color", np.zeros((sizes[0][0], 3), dtype=np.float32), enabled=True) + f_color = p.add_face_color_quantity("f_color", np.zeros((sizes[0][1], 3), dtype=np.float32), enabled=True) ps.show(3) for n_verts, n_faces in sizes[1:]: - verts, faces = make_stm(n_verts, n_faces, seed=n_verts) + verts, faces = make_simple_triangle_mesn(n_verts, n_faces, seed=n_verts) # Update the mesh geometry (counts change) - p.update(verts, faces) + p.update_mesh(verts, faces) self.assertEqual(p.n_vertices(), n_verts) self.assertEqual(p.n_faces(), n_faces) # Re-add quantities with the new size (replaces old ones) v_scalar_data = np.random.rand(n_verts).astype(np.float32) f_scalar_data = np.random.rand(n_faces).astype(np.float32) - v_color_data = np.random.rand(n_verts, 3).astype(np.float32) - f_color_data = np.random.rand(n_faces, 3).astype(np.float32) + v_color_data = np.random.rand(n_verts, 3).astype(np.float32) + f_color_data = np.random.rand(n_faces, 3).astype(np.float32) v_scalar = p.add_vertex_scalar_quantity("v_scalar", v_scalar_data, enabled=True) f_scalar = p.add_face_scalar_quantity("f_scalar", f_scalar_data, enabled=True) - v_color = p.add_vertex_color_quantity("v_color", v_color_data, enabled=True) - f_color = p.add_face_color_quantity("f_color", f_color_data, enabled=True) + v_color = p.add_vertex_color_quantity("v_color", v_color_data, enabled=True) + f_color = p.add_face_color_quantity("f_color", f_color_data, enabled=True) ps.show(3) From 79909674f98d133abb479b90c2e3fe63f608d46b Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sun, 10 May 2026 09:26:37 -0700 Subject: [PATCH 5/5] fix quantity adder function naming --- src/polyscope/simple_triangle_mesh.py | 63 +++++++++++++-------------- test/tests/test_all.py | 32 +++++++------- 2 files changed, 46 insertions(+), 49 deletions(-) diff --git a/src/polyscope/simple_triangle_mesh.py b/src/polyscope/simple_triangle_mesh.py index 9866d9f..ea91218 100644 --- a/src/polyscope/simple_triangle_mesh.py +++ b/src/polyscope/simple_triangle_mesh.py @@ -127,59 +127,56 @@ def get_selection_mode(self) -> str: ## Quantities - def add_vertex_scalar_quantity( + def add_scalar_quantity( self, name: str, values: ArrayLike, + defined_on: Literal["vertices", "faces"] | str = "vertices", datatype: Literal["standard", "symmetric", "magnitude"] | str = "standard", **scalar_args: Unpack[ScalarQuantityArgs], - ) -> psb.SimpleTriangleMeshVertexScalarQuantity: + ): values_arr = np.asarray(values, dtype=np.float32) - q = self.bound_instance.add_vertex_scalar_quantity(name, values_arr, to_enum(psb.DataType, datatype)) - process_quantity_args(self, q, cast(QuantityArgsBase, scalar_args)) - process_scalar_args(self, q, cast(ScalarArgsBase, scalar_args)) - check_all_args_processed(self, q, scalar_args) - return q + if len(values_arr.shape) != 1: + raise ValueError("'values' should be a length-N array") + + if defined_on == "vertices": + if values_arr.shape[0] != self.n_vertices(): + raise ValueError("'values' should be a length n_vertices array") + q = self.bound_instance.add_vertex_scalar_quantity(name, values_arr, to_enum(psb.DataType, datatype)) + elif defined_on == "faces": + if values_arr.shape[0] != self.n_faces(): + raise ValueError("'values' should be a length n_faces array") + q = self.bound_instance.add_face_scalar_quantity(name, values_arr, to_enum(psb.DataType, datatype)) + else: + raise ValueError("bad `defined_on` value {}, should be one of ['vertices', 'faces']".format(defined_on)) - def add_face_scalar_quantity( - self, - name: str, - values: ArrayLike, - datatype: Literal["standard", "symmetric", "magnitude"] | str = "standard", - **scalar_args: Unpack[ScalarQuantityArgs], - ) -> psb.SimpleTriangleMeshFaceScalarQuantity: - values_arr = np.asarray(values, dtype=np.float32) - q = self.bound_instance.add_face_scalar_quantity(name, values_arr, to_enum(psb.DataType, datatype)) process_quantity_args(self, q, cast(QuantityArgsBase, scalar_args)) process_scalar_args(self, q, cast(ScalarArgsBase, scalar_args)) check_all_args_processed(self, q, scalar_args) return q - def add_vertex_color_quantity( + def add_color_quantity( self, name: str, values: ArrayLike, + defined_on: Literal["vertices", "faces"] | str = "vertices", **color_args: Unpack[ColorQuantityArgs], - ) -> psb.SimpleTriangleMeshVertexColorQuantity: + ): values_arr = np.asarray(values, dtype=np.float32) if values_arr.ndim != 2 or values_arr.shape[1] != 3: raise ValueError("'values' should be an Nx3 array") - q = self.bound_instance.add_vertex_color_quantity(name, values_arr) - process_quantity_args(self, q, cast(QuantityArgsBase, color_args)) - process_color_args(self, q, cast(ColorArgsBase, color_args)) - check_all_args_processed(self, q, color_args) - return q - def add_face_color_quantity( - self, - name: str, - values: ArrayLike, - **color_args: Unpack[ColorQuantityArgs], - ) -> psb.SimpleTriangleMeshFaceColorQuantity: - values_arr = np.asarray(values, dtype=np.float32) - if values_arr.ndim != 2 or values_arr.shape[1] != 3: - raise ValueError("'values' should be an Nx3 array") - q = self.bound_instance.add_face_color_quantity(name, values_arr) + if defined_on == "vertices": + if values_arr.shape[0] != self.n_vertices(): + raise ValueError("'values' should be a length n_vertices array") + q = self.bound_instance.add_vertex_color_quantity(name, values_arr) + elif defined_on == "faces": + if values_arr.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_arr) + else: + raise ValueError("bad `defined_on` value {}, should be one of ['vertices', 'faces']".format(defined_on)) + process_quantity_args(self, q, cast(QuantityArgsBase, color_args)) process_color_args(self, q, cast(ColorArgsBase, color_args)) check_all_args_processed(self, q, color_args) diff --git a/test/tests/test_all.py b/test/tests/test_all.py index 81b4b61..734b0cd 100644 --- a/test/tests/test_all.py +++ b/test/tests/test_all.py @@ -3395,17 +3395,17 @@ def test_scalar(self): # vertex scalar vals = np.random.rand(n_verts).astype(np.float32) - p.add_vertex_scalar_quantity("vert_scalar", vals, enabled=True) + p.add_scalar_quantity("vert_scalar", vals, enabled=True) ps.show(3) # vertex scalar with options - p.add_vertex_scalar_quantity("vert_scalar_sym", vals, datatype="symmetric") - p.add_vertex_scalar_quantity("vert_scalar_mag", vals, datatype="magnitude") + p.add_scalar_quantity("vert_scalar_sym", vals, datatype="symmetric") + p.add_scalar_quantity("vert_scalar_mag", vals, datatype="magnitude") ps.show(3) # face scalar f_vals = np.random.rand(n_faces).astype(np.float32) - p.add_face_scalar_quantity("face_scalar", f_vals, enabled=True) + p.add_scalar_quantity("face_scalar", f_vals, defined_on='faces', enabled=True) ps.show(3) ps.remove_all_structures() @@ -3417,12 +3417,12 @@ def test_color(self): # vertex color cols = np.random.rand(n_verts, 3).astype(np.float32) - p.add_vertex_color_quantity("vert_color", cols, enabled=True) + p.add_color_quantity("vert_color", cols, enabled=True) ps.show(3) # face color f_cols = np.random.rand(n_faces, 3).astype(np.float32) - p.add_face_color_quantity("face_color", f_cols, enabled=True) + p.add_color_quantity("face_color", f_cols, defined_on='faces', enabled=True) ps.show(3) ps.remove_all_structures() @@ -3437,20 +3437,20 @@ def test_update_sizes_and_quantities(self): (100, 180), # mid-size to finish ] - verts0, faces0 = make_simple_triangle_mesn(*sizes[0]) + verts0, faces0 = make_simple_triangle_mesh(*sizes[0]) p = ps.register_simple_triangle_mesh("stm_sz", verts0, faces0) # Add all four quantity types before any update — they'll be replaced # on each iteration as the size changes. - v_scalar = p.add_vertex_scalar_quantity("v_scalar", np.zeros(sizes[0][0], dtype=np.float32), enabled=True) - f_scalar = p.add_face_scalar_quantity("f_scalar", np.zeros(sizes[0][1], dtype=np.float32), enabled=True) - v_color = p.add_vertex_color_quantity("v_color", np.zeros((sizes[0][0], 3), dtype=np.float32), enabled=True) - f_color = p.add_face_color_quantity("f_color", np.zeros((sizes[0][1], 3), dtype=np.float32), enabled=True) + v_scalar = p.add_scalar_quantity("v_scalar", np.zeros(sizes[0][0], dtype=np.float32), enabled=True) + f_scalar = p.add_scalar_quantity("f_scalar", np.zeros(sizes[0][1], dtype=np.float32), defined_on='faces', enabled=True) + v_color = p.add_color_quantity("v_color", np.zeros((sizes[0][0], 3), dtype=np.float32), enabled=True) + f_color = p.add_color_quantity("f_color", np.zeros((sizes[0][1], 3), dtype=np.float32), defined_on='faces', enabled=True) ps.show(3) for n_verts, n_faces in sizes[1:]: - verts, faces = make_simple_triangle_mesn(n_verts, n_faces, seed=n_verts) + verts, faces = make_simple_triangle_mesh(n_verts, n_faces, seed=n_verts) # Update the mesh geometry (counts change) p.update_mesh(verts, faces) @@ -3463,10 +3463,10 @@ def test_update_sizes_and_quantities(self): v_color_data = np.random.rand(n_verts, 3).astype(np.float32) f_color_data = np.random.rand(n_faces, 3).astype(np.float32) - v_scalar = p.add_vertex_scalar_quantity("v_scalar", v_scalar_data, enabled=True) - f_scalar = p.add_face_scalar_quantity("f_scalar", f_scalar_data, enabled=True) - v_color = p.add_vertex_color_quantity("v_color", v_color_data, enabled=True) - f_color = p.add_face_color_quantity("f_color", f_color_data, enabled=True) + v_scalar = p.add_scalar_quantity("v_scalar", v_scalar_data, enabled=True) + f_scalar = p.add_scalar_quantity("f_scalar", f_scalar_data, defined_on='faces', enabled=True) + v_color = p.add_color_quantity("v_color", v_color_data, enabled=True) + f_color = p.add_color_quantity("f_color", f_color_data, defined_on='faces', enabled=True) ps.show(3)