diff --git a/deps/polyscope b/deps/polyscope index bdec709..051879c 160000 --- a/deps/polyscope +++ b/deps/polyscope @@ -1 +1 @@ -Subproject commit bdec709a5eaf48f41cb1a7a527de2e065e73dc86 +Subproject commit 051879c5ad82c3469b8a54fd5b534bd557d04490 diff --git a/src/cpp/core.cpp b/src/cpp/core.cpp index d6728a3..d46ae03 100644 --- a/src/cpp/core.cpp +++ b/src/cpp/core.cpp @@ -363,6 +363,37 @@ 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"); + // === Transformation Gizmos + + py::class_(m, "TransformationGizmo") + .def(py::init()) + .def_readonly("name", &ps::TransformationGizmo::name) + .def("remove", &ps::TransformationGizmo::remove, "remove") + .def("set_enabled", &ps::TransformationGizmo::setEnabled, "set enabled") + .def("get_enabled", &ps::TransformationGizmo::getEnabled, "get enabled") + .def("set_transform", [] (ps::TransformationGizmo& g, const Eigen::Matrix4d& T) { g.setTransform(eigen2glm(T)); }, "set transform") + .def("get_transform", [](ps::TransformationGizmo& g) { return glm2eigen(g.getTransform()); }, "get transform") + .def("set_position", &ps::TransformationGizmo::setPosition, "set position") + .def("get_position", [](ps::TransformationGizmo& g) { return glm2eigen(g.getPosition()); }, "get position") + .def("set_allow_translation", &ps::TransformationGizmo::setAllowTranslation, "set allow translation") + .def("get_allow_translation", &ps::TransformationGizmo::getAllowTranslation, "get allow translation") + .def("set_allow_rotation", &ps::TransformationGizmo::setAllowRotation, "set allow rotation") + .def("get_allow_rotation", &ps::TransformationGizmo::getAllowRotation, "get allow rotation") + .def("set_allow_scaling", &ps::TransformationGizmo::setAllowScaling, "set allow scaling") + .def("get_allow_scaling", &ps::TransformationGizmo::getAllowScaling, "get allow scaling") + .def("get_interact_in_local_space", &ps::TransformationGizmo::getInteractInLocalSpace, "get interact in local space") + .def("set_interact_in_local_space", &ps::TransformationGizmo::setInteractInLocalSpace, "set interact in local space") + .def("get_gizmo_size", &ps::TransformationGizmo::getGizmoSize, "get gizmo size") + .def("set_gizmo_size", &ps::TransformationGizmo::setGizmoSize, "set gizmo size") + .def("build_inline_transform_ui", &ps::TransformationGizmo::buildInlineTransformUI, "build inline transform UI") + ; + + m.def("add_transformation_gizmo", [](std::string name) {return ps::addTransformationGizmo(name); }, "add a transformation gizmo", py::return_value_policy::reference); + m.def("get_transformation_gizmo", &ps::getTransformationGizmo, "get a transformation gizmo", py::return_value_policy::reference); + m.def("remove_transformation_gizmo", overload_cast_()(&ps::removeTransformationGizmo), "remove a transformation gizmo by name"); + m.def("remove_transformation_gizmo", overload_cast_()(&ps::removeTransformationGizmo), "remove a transformation gizmo by ptr"); + m.def("remove_all_transformation_gizmos", &ps::removeAllTransformationGizmos, "remove all transformation gizmos"); + // === Camera Parameters py::class_(m, "CameraIntrinsics") .def(py::init<>()) diff --git a/src/cpp/utils.h b/src/cpp/utils.h index 9434b22..6541cc4 100644 --- a/src/cpp/utils.h +++ b/src/cpp/utils.h @@ -136,6 +136,7 @@ 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") .def("set_transform_gizmo_enabled", &StructureT::setTransformGizmoEnabled) .def("get_transform_gizmo_enabled", &StructureT::getTransformGizmoEnabled) + .def("get_transformation_gizmo", &StructureT::getTransformGizmo, py::return_value_policy::reference, "Get the TransformationGizmo associated with this structure") // 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) diff --git a/src/polyscope/core.py b/src/polyscope/core.py index 84fd3b8..59b6d33 100644 --- a/src/polyscope/core.py +++ b/src/polyscope/core.py @@ -631,6 +631,98 @@ def remove_last_scene_slice_plane(): # deprecated psb.remove_last_scene_slice_plane() +## Transformation Gizmo + +class TransformationGizmo: + # This class wraps a _reference_ to the underlying object, whose lifetime is managed by Polyscope + + # End users should not call this constrctor, use add_transformation_gizmo() instead + def __init__(self, instance): + + # Wrap an existing instance + self.bound_gizmo = instance + + def get_name(self): + return self.bound_gizmo.name + + def remove(self): + self.bound_gizmo.remove() + + def set_enabled(self, val): + self.bound_gizmo.set_enabled(val) + + def get_enabled(self): + return self.bound_gizmo.get_enabled() + + def set_transform(self, T): + if not isinstance(T, np.ndarray) or T.shape != (4, 4): + raise ValueError("T should be a 4x4 numpy matrix") + self.bound_gizmo.set_transform(T) + + def get_transform(self): + return self.bound_gizmo.get_transform() + + def set_position(self, p): + self.bound_gizmo.set_position(glm3(p)) + + def get_position(self): + return self.bound_gizmo.get_position() + + def set_allow_translation(self, val): + self.bound_gizmo.set_allow_translation(val) + + def get_allow_translation(self): + return self.bound_gizmo.get_allow_translation() + + def set_allow_rotation(self, val): + self.bound_gizmo.set_allow_rotation(val) + + def get_allow_rotation(self): + return self.bound_gizmo.get_allow_rotation() + + def set_allow_scaling(self, val): + self.bound_gizmo.set_allow_scaling(val) + + def get_allow_scaling(self): + return self.bound_gizmo.get_allow_scaling() + + def get_interact_in_local_space(self): + return self.bound_gizmo.get_interact_in_local_space() + + def set_interact_in_local_space(self, val): + self.bound_gizmo.set_interact_in_local_space(val) + + def get_gizmo_scale(self): + # underlying binding uses "size" + return self.bound_gizmo.get_gizmo_size() + + def set_gizmo_scale(self, val): + # underlying binding uses "size" + self.bound_gizmo.set_gizmo_size(float(val)) + + def build_inline_transform_ui(self): + self.bound_gizmo.build_inline_transform_ui() + +def add_transformation_gizmo(name=None): + if name is None: + # name gets automatically set internally; pass empty string + instance = psb.add_transformation_gizmo("") + else: + instance = psb.add_transformation_gizmo(name) + + return TransformationGizmo(instance) + +def get_transformation_gizmo(name): + instance = psb.get_transformation_gizmo(name) + return TransformationGizmo(instance) + +def remove_transformation_gizmo(name): + psb.remove_transformation_gizmo(name) + +def remove_all_transformation_gizmos(): + psb.remove_all_transformation_gizmos() + + ### Camera Parameters class CameraIntrinsics: diff --git a/src/polyscope/structure.py b/src/polyscope/structure.py index da77842..3585763 100644 --- a/src/polyscope/structure.py +++ b/src/polyscope/structure.py @@ -2,6 +2,7 @@ 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 +from polyscope.core import TransformationGizmo # Base class for common properties and methods on structures class Structure: @@ -62,6 +63,8 @@ def set_transform_gizmo_enabled(self, val): self.bound_instance.set_transform_gizmo_enabled(val) def get_transform_gizmo_enabled(self): return self.bound_instance.get_transform_gizmo_enabled() + def get_transformation_gizmo(self): + return TransformationGizmo(self.bound_instance.get_transformation_gizmo()) ## Managed Buffers diff --git a/test/polyscope_test.py b/test/polyscope_test.py index dbc0fbf..bbf2e46 100644 --- a/test/polyscope_test.py +++ b/test/polyscope_test.py @@ -366,6 +366,93 @@ def test_slice_plane(self): ps.add_slice_plane("test2") ps.remove_all_slice_planes() + def test_transformation_gizmo(self): + + # Create a gizmo + g1 = ps.add_transformation_gizmo("gizmo_1") + self.assertEqual(g1.get_name(), "gizmo_1") + + # Enable/disable + g1.set_enabled(True) + self.assertEqual(True, g1.get_enabled()) + g1.set_enabled(False) + self.assertEqual(False, g1.get_enabled()) + + # Set/get transform + T_I = np.eye(4) + g1.set_transform(T_I) + T_ret = g1.get_transform() + self.assertTrue(isinstance(T_ret, np.ndarray)) + self.assertEqual(T_ret.shape, (4,4)) + self.assertTrue(np.allclose(T_ret, T_I)) + + T2 = np.array([ + [1., 0., 0., 3.], + [0., 0., -1., -2.], + [0., 1., 0., 5.], + [0., 0., 0., 1.] + ]) + g1.set_transform(T2) + self.assertTrue(np.allclose(g1.get_transform(), T2)) + + # Get/set position + g1.set_position(np.array((1.0, 2.0, 3.0))) + self.assertTrue(np.allclose(g1.get_position(), np.array((1.0, 2.0, 3.0)))) + + # Allow toggles + g1.set_allow_translation(True) + self.assertEqual(True, g1.get_allow_translation()) + g1.set_allow_translation(False) + self.assertEqual(False, g1.get_allow_translation()) + + g1.set_allow_rotation(True) + self.assertEqual(True, g1.get_allow_rotation()) + g1.set_allow_rotation(False) + self.assertEqual(False, g1.get_allow_rotation()) + + g1.set_allow_scaling(True) + self.assertEqual(True, g1.get_allow_scaling()) + g1.set_allow_scaling(False) + self.assertEqual(False, g1.get_allow_scaling()) + + # Local-space interaction toggle + g1.set_interact_in_local_space(True) + self.assertEqual(True, g1.get_interact_in_local_space()) + g1.set_interact_in_local_space(False) + self.assertEqual(False, g1.get_interact_in_local_space()) + + # Gizmo size (scale) getter/setter + g1.set_gizmo_scale(0.75) + self.assertAlmostEqual(0.75, g1.get_gizmo_scale()) + + # Retrieve by name + g1b = ps.get_transformation_gizmo("gizmo_1") + self.assertEqual(g1b.get_name(), "gizmo_1") + + ps.show(3) + + # Remove by name + ps.remove_transformation_gizmo("gizmo_1") + + # Create another and remove via instance method + g2 = ps.add_transformation_gizmo("gizmo_2") + self.assertEqual(g2.get_name(), "gizmo_2") + g2.remove() + + # Add with auto-naming + ps.add_transformation_gizmo() + ps.add_transformation_gizmo() + + # Add a couple and clear all + ps.add_transformation_gizmo("gizmo_B") + ps.remove_all_transformation_gizmos() + + # Get the reference for a structure + pt_cloud_0 = ps.register_point_cloud("cloud0", np.zeros((10,3))) + gizmo = pt_cloud_0.get_transformation_gizmo() + self.assertIsInstance(gizmo, ps.TransformationGizmo) + gizmo.set_allow_rotation(False) + ps.remove_all_structures() def test_load_material(self):