From 70983d660c43f136325d4979f20a4d987b1e283a Mon Sep 17 00:00:00 2001 From: David Thompson Date: Tue, 11 Mar 2025 11:07:01 -0400 Subject: [PATCH 01/20] Add `pyproject.toml` for pip wheel generation. This adds `pyproject.toml` (which uses `scikit-build-core` to build a wheel when you run `pip install --no-clean .` in the top-level source directory). When CMake is run via `pip install`, the `SMTK_PYTHON_MODULEDIR` is modified so modules are installed into the proper location in the package. --- CMakeLists.txt | 28 ++++++++++++++++------------ pyproject.toml | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 12 deletions(-) create mode 100644 pyproject.toml diff --git a/CMakeLists.txt b/CMakeLists.txt index 93e704374a..82b0b037e0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -432,19 +432,23 @@ if(SMTK_ENABLE_PYTHON_WRAPPING) # Note that SMTK_PYTHON_MODULEDIR may be provided if SMTK is being # built as a submodule or as part of a superbuild. if (NOT DEFINED SMTK_PYTHON_MODULEDIR) - if (SMTK_INSTALL_PYTHON_TO_SITE_PACKAGES) - execute_process( - COMMAND - "$" - -c "import site; print(site.getsitepackages())[-1]" - RESULT_VARIABLE SMTK_PYTHON_MODULEDIR - ) - elseif(WIN32) - set(SMTK_PYTHON_MODULEDIR - "bin/Lib/site-packages") + if (DEFINED SKBUILD) + set(SMTK_PYTHON_MODULEDIR "${SKBUILD_PLATLIB_DIR}") else() - set(SMTK_PYTHON_MODULEDIR - "${CMAKE_INSTALL_LIBDIR}/python${Python3_VERSION_MAJOR}.${Python3_VERSION_MINOR}/site-packages") + if (SMTK_INSTALL_PYTHON_TO_SITE_PACKAGES) + execute_process( + COMMAND + "$" + -c "import site; print(site.getsitepackages())[-1]" + RESULT_VARIABLE SMTK_PYTHON_MODULEDIR + ) + elseif(WIN32) + set(SMTK_PYTHON_MODULEDIR + "bin/Lib/site-packages") + else() + set(SMTK_PYTHON_MODULEDIR + "${CMAKE_INSTALL_LIBDIR}/python${Python3_VERSION_MAJOR}.${Python3_VERSION_MINOR}/site-packages") + endif() endif() endif() diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000000..2d1ec4bf5c --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,38 @@ +[build-system] +requires = ["scikit-build-core"] +build-backend = "scikit_build_core.build" + +[project] +name = "smtk" +version = "25.03" # This gets rewritten to 25.3 somewhere. I assume that's OK? + +[tool.scikit-build] +cmake.version = ">=3.20" +ninja.version = ">=1.11" +ninja.make-fallback = false + +cmake.verbose = true +logging.level = "INFO" +cmake.args = [] +cmake.build-type = "Debug" + +# wheel.packages = [ 'smtk/python' ] +wheel.license-files = [ 'LICENSE.txt' ] + +[tool.scikit-build.cmake.define] + +SMTK_ENABLE_TESTING = true +SMTK_ENABLE_PYTHON_WRAPPING = true +SMTK_ENABLE_OPERATION_THREADS = true +# These are hardwired at the moment; they come from the superbuild. +units_DIR = '/stage/bld/aeva/superbuild/install/lib/cmake/units-24.07.0' +Eigen3_DIR = '/stage/bld/aeva/superbuild/install/share/eigen3/cmake' + +SMTK_NO_SYSTEM_BOOST = false +SMTK_ENABLE_VTK_SUPPORT = false +SMTK_ENABLE_PARAVIEW_SUPPORT = false +SMTK_ENABLE_QT_SUPPORT = false +SMTK_ENABLE_APPLICATIONS = false +SMTK_ENABLE_EXAMPLES = false +SMTK_ENABLE_POLYGON_SESSION = false +SMTK_ENABLE_VTK_SESSION = false -- GitLab From 6901241cb43aaa1da95856bbea3f0c419f1a3350 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Tue, 11 Mar 2025 11:10:52 -0400 Subject: [PATCH 02/20] Wrap additional methods of the operation registrar. --- smtk/operation/pybind11/PybindRegistrar.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/smtk/operation/pybind11/PybindRegistrar.h b/smtk/operation/pybind11/PybindRegistrar.h index 2c0eec1595..2e8b98894a 100644 --- a/smtk/operation/pybind11/PybindRegistrar.h +++ b/smtk/operation/pybind11/PybindRegistrar.h @@ -22,8 +22,12 @@ inline py::class_< smtk::operation::Registrar > pybind11_init_smtk_operation_Reg py::class_< smtk::operation::Registrar > instance(m, "Registrar"); instance .def(py::init<>()) + .def_static("registerTo", (void (*)(std::shared_ptr<::smtk::common::Managers> const &)) &smtk::operation::Registrar::registerTo) + .def_static("unregisterFrom", (void (*)(std::shared_ptr<::smtk::common::Managers> const &)) &smtk::operation::Registrar::unregisterFrom) .def_static("registerTo", (void (*)(std::shared_ptr<::smtk::operation::Manager> const &)) &smtk::operation::Registrar::registerTo) .def_static("unregisterFrom", (void (*)(std::shared_ptr<::smtk::operation::Manager> const &)) &smtk::operation::Registrar::unregisterFrom) + .def_static("registerTo", (void (*)(std::shared_ptr<::smtk::view::Manager> const &)) &smtk::operation::Registrar::registerTo) + .def_static("unregisterFrom", (void (*)(std::shared_ptr<::smtk::view::Manager> const &)) &smtk::operation::Registrar::unregisterFrom) ; return instance; } -- GitLab From 5a38d10dd0192e6becf07f454e0988a769868b00 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Tue, 11 Mar 2025 11:11:35 -0400 Subject: [PATCH 03/20] =?UTF-8?q?Add=20python=20methods=20to=20double-item?= =?UTF-8?q?s=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit … for getting/setting values via python lists/tuples. The same should be done for integer and string items and may also be done for reference items. --- smtk/attribute/pybind11/PybindDoubleItem.h | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/smtk/attribute/pybind11/PybindDoubleItem.h b/smtk/attribute/pybind11/PybindDoubleItem.h index ec93395482..6dfd79bc9d 100644 --- a/smtk/attribute/pybind11/PybindDoubleItem.h +++ b/smtk/attribute/pybind11/PybindDoubleItem.h @@ -24,6 +24,20 @@ inline PySharedPtrClass< smtk::attribute::DoubleItem, smtk::attribute::ValueItem .def("setValue", (bool (smtk::attribute::DoubleItem::*)(double const &)) &smtk::attribute::DoubleItem::setValue) .def("setValue", (bool (smtk::attribute::DoubleItem::*)(::size_t, double const &)) &smtk::attribute::DoubleItem::setValue) .def("setValue", (bool (smtk::attribute::DoubleItem::*)(::size_t, double const &, const std::string&)) &smtk::attribute::DoubleItem::setValue) + .def("setValues", [&](smtk::attribute::DoubleItem* self, const std::vector& values) + { + return self->setValues(values.begin(), values.end()); + }, py::arg("values")) + .def("values", [&](smtk::attribute::DoubleItem* self) -> std::vector + { + std::vector values; + values.reserve(self->numberOfValues()); + for (const auto& vv : *self) + { + values.push_back(vv); + } + return values; + }) .def("hasExplicitUnits", &smtk::attribute::DoubleItem::hasExplicitUnits) .def(py::init<::smtk::attribute::DoubleItem const &>()) .def("type", &smtk::attribute::DoubleItem::type) -- GitLab From 878984952e07305d03e29e62c5fe1f27639670e5 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Wed, 11 Jun 2025 19:22:01 -0400 Subject: [PATCH 04/20] Add python bindings for trame-smtk to use. This adds some methods to use pointers as unique SMTK view/item/attribute IDs. --- smtk/attribute/pybind11/PybindDoubleItem.h | 14 ------ smtk/attribute/pybind11/PybindItem.h | 26 +++++++++++ .../pybind11/PybindValueItemTemplate.h | 43 ++++++++++++++++++- .../pybind11/PybindPersistentObject.h | 26 +++++++++++ smtk/view/Configuration.h | 5 ++- smtk/view/pybind11/PybindView.h | 26 +++++++++++ 6 files changed, 124 insertions(+), 16 deletions(-) diff --git a/smtk/attribute/pybind11/PybindDoubleItem.h b/smtk/attribute/pybind11/PybindDoubleItem.h index 6dfd79bc9d..ec93395482 100644 --- a/smtk/attribute/pybind11/PybindDoubleItem.h +++ b/smtk/attribute/pybind11/PybindDoubleItem.h @@ -24,20 +24,6 @@ inline PySharedPtrClass< smtk::attribute::DoubleItem, smtk::attribute::ValueItem .def("setValue", (bool (smtk::attribute::DoubleItem::*)(double const &)) &smtk::attribute::DoubleItem::setValue) .def("setValue", (bool (smtk::attribute::DoubleItem::*)(::size_t, double const &)) &smtk::attribute::DoubleItem::setValue) .def("setValue", (bool (smtk::attribute::DoubleItem::*)(::size_t, double const &, const std::string&)) &smtk::attribute::DoubleItem::setValue) - .def("setValues", [&](smtk::attribute::DoubleItem* self, const std::vector& values) - { - return self->setValues(values.begin(), values.end()); - }, py::arg("values")) - .def("values", [&](smtk::attribute::DoubleItem* self) -> std::vector - { - std::vector values; - values.reserve(self->numberOfValues()); - for (const auto& vv : *self) - { - values.push_back(vv); - } - return values; - }) .def("hasExplicitUnits", &smtk::attribute::DoubleItem::hasExplicitUnits) .def(py::init<::smtk::attribute::DoubleItem const &>()) .def("type", &smtk::attribute::DoubleItem::type) diff --git a/smtk/attribute/pybind11/PybindItem.h b/smtk/attribute/pybind11/PybindItem.h index c756d835f3..a03aa1d084 100644 --- a/smtk/attribute/pybind11/PybindItem.h +++ b/smtk/attribute/pybind11/PybindItem.h @@ -20,8 +20,16 @@ #include "smtk/io/Logger.h" #include "smtk/simulation/UserData.h" +#include +#include + namespace py = pybind11; +namespace +{ + std::unordered_set allItems; +} + inline PySharedPtrClass< smtk::attribute::Item > pybind11_init_smtk_attribute_Item(py::module &m) { PySharedPtrClass< smtk::attribute::Item > instance(m, "Item"); @@ -81,6 +89,24 @@ inline PySharedPtrClass< smtk::attribute::Item > pybind11_init_smtk_attribute_It auto result = item.assign(sourceItem, options, logger); return result.success(); }, py::arg("sourceItem"), py::arg("options"), py::arg("logger")) + .def("pointer", [](const smtk::attribute::Item& self) + { + std::ostringstream addr; + addr << std::hex << &self; + allItems.insert(&self); + return addr.str(); + }) + .def_static("fromPointer", [](const std::string& ptrStr) + { + char* end = const_cast(ptrStr.c_str() + ptrStr.size()); + // NOLINTNEXTLINE(performance-no-int-to-ptr) + auto* ptr = reinterpret_cast(strtoull(ptrStr.c_str(), &end, 16)); + if (ptr && allItems.find(ptr) != allItems.end()) + { + return ptr->shared_from_this(); + } + return smtk::attribute::Item::Ptr(); + }) .def_static("type2String", &smtk::attribute::Item::type2String, py::arg("t")) .def_static("string2Type", &smtk::attribute::Item::string2Type, py::arg("s")) ; diff --git a/smtk/attribute/pybind11/PybindValueItemTemplate.h b/smtk/attribute/pybind11/PybindValueItemTemplate.h index 7377993b39..c730ab90ae 100644 --- a/smtk/attribute/pybind11/PybindValueItemTemplate.h +++ b/smtk/attribute/pybind11/PybindValueItemTemplate.h @@ -40,6 +40,20 @@ inline PySharedPtrClass< smtk::attribute::ValueItemTemplate, smtk::attribut .def("value", (int (smtk::attribute::ValueItemTemplate::*)(::size_t) const) &smtk::attribute::ValueItemTemplate::value, py::arg("element") = 0) .def("value", (int (smtk::attribute::ValueItemTemplate::*)(smtk::io::Logger&) const) &smtk::attribute::ValueItemTemplate::value, py::arg("log")) .def("value", (int (smtk::attribute::ValueItemTemplate::*)(::size_t, smtk::io::Logger&) const) &smtk::attribute::ValueItemTemplate::value, py::arg("element"), py::arg("log")) + .def("setValues", [&](smtk::attribute::ValueItemTemplate* self, const std::vector& values) + { + return self->setValues(values.begin(), values.end()); + }, py::arg("values")) + .def("values", [&](smtk::attribute::ValueItemTemplate* self) -> std::vector + { + std::vector values; + values.reserve(self->numberOfValues()); + for (const auto& vv : *self) + { + values.push_back(vv); + } + return values; + }) ; return instance; } @@ -66,6 +80,20 @@ inline PySharedPtrClass, smtk::attrib .def("value", (double (smtk::attribute::ValueItemTemplate::*)(::size_t) const) &smtk::attribute::ValueItemTemplate::value, py::arg("element") = 0) .def("value", (double (smtk::attribute::ValueItemTemplate::*)(smtk::io::Logger&) const) &smtk::attribute::ValueItemTemplate::value, py::arg("log")) .def("value", (double (smtk::attribute::ValueItemTemplate::*)(::size_t, smtk::io::Logger&) const) &smtk::attribute::ValueItemTemplate::value, py::arg("element"), py::arg("log")) + .def("setValues", [&](smtk::attribute::ValueItemTemplate* self, const std::vector& values) + { + return self->setValues(values.begin(), values.end()); + }, py::arg("values")) + .def("values", [&](smtk::attribute::ValueItemTemplate* self) -> std::vector + { + std::vector values; + values.reserve(self->numberOfValues()); + for (const auto& vv : *self) + { + values.push_back(vv); + } + return values; + }) ; return instance; } @@ -92,9 +120,22 @@ inline PySharedPtrClass, smtk::a .def("value", (std::string (smtk::attribute::ValueItemTemplate::*)(::size_t) const) &smtk::attribute::ValueItemTemplate::value, py::arg("element") = 0) .def("value", (std::string (smtk::attribute::ValueItemTemplate::*)(smtk::io::Logger&) const) &smtk::attribute::ValueItemTemplate::value, py::arg("log")) .def("value", (std::string (smtk::attribute::ValueItemTemplate::*)(::size_t, smtk::io::Logger&) const) &smtk::attribute::ValueItemTemplate::value, py::arg("element"), py::arg("log")) + .def("setValues", [&](smtk::attribute::ValueItemTemplate* self, const std::vector& values) + { + return self->setValues(values.begin(), values.end()); + }, py::arg("values")) + .def("values", [&](smtk::attribute::ValueItemTemplate* self) -> std::vector + { + std::vector values; + values.reserve(self->numberOfValues()); + for (const auto& vv : *self) + { + values.push_back(vv); + } + return values; + }) ; return instance; - } #endif diff --git a/smtk/resource/pybind11/PybindPersistentObject.h b/smtk/resource/pybind11/PybindPersistentObject.h index 0738bc43be..280fb60cbb 100644 --- a/smtk/resource/pybind11/PybindPersistentObject.h +++ b/smtk/resource/pybind11/PybindPersistentObject.h @@ -17,8 +17,16 @@ #include "smtk/common/pybind11/PybindUUIDTypeCaster.h" +#include +#include + namespace py = pybind11; +namespace +{ + std::unordered_set allObjects; +} + inline PySharedPtrClass< smtk::resource::PersistentObject > pybind11_init_smtk_resource_PersistentObject(py::module &m) { PySharedPtrClass< smtk::resource::PersistentObject > instance(m, "PersistentObject"); @@ -34,6 +42,24 @@ inline PySharedPtrClass< smtk::resource::PersistentObject > pybind11_init_smtk_r .def("classHierarchy", &smtk::resource::PersistentObject::classHierarchy) .def("matchesType", &smtk::resource::PersistentObject::matchesType, py::arg("candidate")) .def("generationsFromBase", &smtk::resource::PersistentObject::generationsFromBase, py::arg("base")) + .def("pointer", [](const smtk::resource::PersistentObject& self) + { + std::ostringstream addr; + addr << std::hex << &self; + allObjects.insert(&self); + return addr.str(); + }) + .def_static("fromPointer", [](const std::string& ptrStr) + { + char* end = const_cast(ptrStr.c_str() + ptrStr.size()); + // NOLINTNEXTLINE(performance-no-int-to-ptr) + auto* ptr = reinterpret_cast(strtoull(ptrStr.c_str(), &end, 16)); + if (ptr && allObjects.find(ptr) != allObjects.end()) + { + return ptr->shared_from_this(); + } + return smtk::resource::PersistentObject::Ptr(); + }) ; return instance; } diff --git a/smtk/view/Configuration.h b/smtk/view/Configuration.h index fa9b94370a..aac6c155d6 100644 --- a/smtk/view/Configuration.h +++ b/smtk/view/Configuration.h @@ -17,6 +17,7 @@ #include "smtk/CoreExports.h" #include "smtk/PublicPointerDefs.h" +#include "smtk/SharedFromThis.h" #include #include @@ -25,9 +26,11 @@ namespace smtk namespace view { /// @brief Configure a view, specifying types and attributes, without specifying a UI library. -class SMTKCORE_EXPORT Configuration +class SMTKCORE_EXPORT Configuration : smtkEnableSharedPtr(Configuration) { public: + smtkTypeMacroBase(smtk::view::Configuration); + /// @brief Configure one item in a view, which may contain children. class SMTKCORE_EXPORT Component { diff --git a/smtk/view/pybind11/PybindView.h b/smtk/view/pybind11/PybindView.h index e4132bdcd4..1e998ef62f 100644 --- a/smtk/view/pybind11/PybindView.h +++ b/smtk/view/pybind11/PybindView.h @@ -15,8 +15,16 @@ #include "smtk/view/Configuration.h" +#include +#include + namespace py = pybind11; +namespace +{ + std::unordered_set allViews; +} + inline PySharedPtrClass< smtk::view::Configuration > pybind11_init_smtk_view_View(py::module &m) { PySharedPtrClass< smtk::view::Configuration > instance(m, "View"); @@ -30,6 +38,24 @@ inline PySharedPtrClass< smtk::view::Configuration > pybind11_init_smtk_view_Vie .def("iconName", &smtk::view::Configuration::iconName) .def("setIconName", &smtk::view::Configuration::setIconName, py::arg("name")) .def("details", (smtk::view::Configuration::Component& (smtk::view::Configuration::*)()) &smtk::view::Configuration::details, py::return_value_policy::reference) + .def("pointer", [](const smtk::view::Configuration& self) + { + std::ostringstream addr; + addr << std::hex << &self; + allViews.insert(&self); + return addr.str(); + }) + .def_static("fromPointer", [](const std::string& ptrStr) + { // FIXME: DO NOT COMMIT THIS + char* end = const_cast(ptrStr.c_str() + ptrStr.size()); + // NOLINTNEXTLINE(performance-no-int-to-ptr) + auto* ptr = reinterpret_cast(strtoull(ptrStr.c_str(), &end, 16)); + if (ptr && allViews.find(ptr) != allViews.end()) + { + return ptr->shared_from_this(); + } + return smtk::view::Configuration::Ptr(); + }) ; py::class_< smtk::view::Configuration::Component >(instance, "Component") .def(py::init<::std::string const &>()) -- GitLab From 2d085bc37f548190b0a50005de242e522353ca5f Mon Sep 17 00:00:00 2001 From: David Thompson Date: Sun, 15 Jun 2025 01:24:54 -0400 Subject: [PATCH 05/20] Add EditAttributeItem operation. --- smtk/attribute/CMakeLists.txt | 3 +- smtk/attribute/Registrar.cxx | 16 +- .../attribute/operators/EditAttributeItem.cxx | 150 ++++++++++++++++++ smtk/attribute/operators/EditAttributeItem.h | 42 +++++ .../attribute/operators/EditAttributeItem.sbt | 62 ++++++++ smtk/attribute/operators/Signal.sbt | 2 +- 6 files changed, 271 insertions(+), 4 deletions(-) create mode 100644 smtk/attribute/operators/EditAttributeItem.cxx create mode 100644 smtk/attribute/operators/EditAttributeItem.h create mode 100644 smtk/attribute/operators/EditAttributeItem.sbt diff --git a/smtk/attribute/CMakeLists.txt b/smtk/attribute/CMakeLists.txt index f1bdfbbe07..257ede2e9f 100644 --- a/smtk/attribute/CMakeLists.txt +++ b/smtk/attribute/CMakeLists.txt @@ -75,12 +75,13 @@ set(jsonAttributeSrcs set(attributeOperators Associate Dissociate + EditAttributeItem Export Import Read Signal Write - ) +) set(attributeHeaders ${jsonAttributeHeaders} diff --git a/smtk/attribute/Registrar.cxx b/smtk/attribute/Registrar.cxx index 0efb66f9f1..95799c3d71 100644 --- a/smtk/attribute/Registrar.cxx +++ b/smtk/attribute/Registrar.cxx @@ -19,6 +19,7 @@ #include "smtk/attribute/operators/Associate.h" #include "smtk/attribute/operators/Dissociate.h" +#include "smtk/attribute/operators/EditAttributeItem.h" #include "smtk/attribute/operators/Export.h" #include "smtk/attribute/operators/Import.h" #include "smtk/attribute/operators/Read.h" @@ -43,8 +44,19 @@ namespace attribute { namespace { -typedef std::tuple OperationList; -} +// clang-format off +using OperationList = std::tuple< + Associate, + Dissociate, + EditAttributeItem, + Export, + Import, + Read, + Signal, + Write +>; +// clang-format on +} // namespace void Registrar::registerTo(const smtk::common::Managers::Ptr& managers) { diff --git a/smtk/attribute/operators/EditAttributeItem.cxx b/smtk/attribute/operators/EditAttributeItem.cxx new file mode 100644 index 0000000000..e76bc2af88 --- /dev/null +++ b/smtk/attribute/operators/EditAttributeItem.cxx @@ -0,0 +1,150 @@ +//========================================================================= +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//========================================================================= + +#include "smtk/attribute/operators/EditAttributeItem.h" + +#include "smtk/attribute/operators/EditAttributeItem_xml.h" + +#include "smtk/operation/MarkGeometry.h" + +#include "smtk/attribute/Attribute.h" +#include "smtk/attribute/ComponentItem.h" +#include "smtk/attribute/IntItem.h" +#include "smtk/attribute/Resource.h" +#include "smtk/attribute/ResourceItem.h" +#include "smtk/attribute/StringItem.h" +#include "smtk/attribute/VoidItem.h" + +#include "smtk/geometry/Geometry.h" + +#include "smtk/io/Logger.h" + +namespace smtk +{ +namespace attribute +{ + +EditAttributeItem::Result EditAttributeItem::operateInternal() +{ + bool didModify = false; + auto params = this->parameters(); + auto attrib = params->associations()->valueAs(); + if (!attrib) + { + return this->createResult(smtk::operation::Operation::Outcome::FAILED); + } + auto item = attrib->itemAtPath(params->findString("item path")->value()); + if (!item) + { + smtkErrorMacro( + this->log(), "No item at path \"" << params->findString("item path")->value() << "\"."); + return this->createResult(smtk::operation::Operation::Outcome::FAILED); + } + auto enableItem = params->findInt("enable"); + int enable = enableItem->isEnabled() ? enableItem->value() : -1; + if (enable >= 0) + { + if (!item->isOptional()) + { + smtkErrorMacro(this->log(), "Cannot enable/disable a non-optional item."); + return this->createResult(smtk::operation::Operation::Outcome::FAILED); + } + int current = item->isEnabled() ? 1 : 0; + if (current != enable) + { + item->setIsEnabled(enable); + didModify = true; + } + // else: It is not an error to request a non-change. Silently ignore. + } + + int extend = params->findInt("extend")->value(); + if (extend >= 0) + { + auto vitem = std::dynamic_pointer_cast(item); + if (!vitem) + { + // TODO: Handle ReferenceItem. + smtkErrorMacro(this->log(), "Cannot extend an item of type \"" << item->typeName() << "\"."); + return this->createResult(smtk::operation::Operation::Outcome::FAILED); + } + if (!vitem->setNumberOfValues(extend)) + { + smtkErrorMacro(this->log(), "Cannot extend item to length " << extend << "."); + return this->createResult(smtk::operation::Operation::Outcome::FAILED); + } + didModify = true; + } + + auto valueItem = params->findString("value"); + if (valueItem->numberOfValues() > 0) + { + auto vitem = std::dynamic_pointer_cast(item); + if (!vitem) + { + // TODO: Handle ReferenceItem. + smtkErrorMacro(this->log(), "Cannot extend an item of type \"" << item->typeName() << "\"."); + return this->createResult(smtk::operation::Operation::Outcome::FAILED); + } + if (vitem->numberOfValues() != valueItem->numberOfValues()) + { + smtkErrorMacro( + this->log(), + "Cannot set values with mismatching lengths " << vitem->numberOfValues() << " vs " + << valueItem->numberOfValues() << "."); + return this->createResult(smtk::operation::Operation::Outcome::FAILED); + } + std::size_t numVals = vitem->numberOfValues(); + for (std::size_t ii = 0; ii < numVals; ++ii) + { + if (vitem->valueAsString(ii) != valueItem->value(ii)) + { + if (!vitem->setValueFromString(ii, valueItem->value(ii))) + { + smtkErrorMacro( + this->log(), "Cannot set value " << ii << " to " << valueItem->value(ii) << "."); + return this->createResult(smtk::operation::Operation::Outcome::FAILED); + } + didModify = true; + } + } + } + auto result = this->createResult(smtk::operation::Operation::Outcome::SUCCEEDED); + auto modOut = result->findComponent("modified"); + if (didModify) + { + modOut->appendValue(attrib); + + // Force any geometry on the modified attribute to be updated. + smtk::operation::MarkGeometry marker; + auto* rsrc = dynamic_cast(attrib->parentResource()); + if (rsrc) + { + // Only empty the cache entry if there is a geometry backend + // for the resource. + auto& geom = rsrc->geometry(); + if (geom) + { + marker.markModified(attrib); + } + } + } + + return result; +} + +void EditAttributeItem::generateSummary(Operation::Result& /*unused*/) {} + +const char* EditAttributeItem::xmlDescription() const +{ + return EditAttributeItem_xml; +} +} // namespace attribute +} // namespace smtk diff --git a/smtk/attribute/operators/EditAttributeItem.h b/smtk/attribute/operators/EditAttributeItem.h new file mode 100644 index 0000000000..a0914d4109 --- /dev/null +++ b/smtk/attribute/operators/EditAttributeItem.h @@ -0,0 +1,42 @@ +//========================================================================= +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//========================================================================= +#ifndef smtk_operation_operators_EditAttributeItem_h +#define smtk_operation_operators_EditAttributeItem_h + +#include "smtk/operation/XMLOperation.h" + +namespace smtk +{ +namespace attribute +{ + +/**\brief A "dummy" operation used to mark an attribute as created, modified, or expunged. + + This operation does nothing internally except pass the components + which are associated to itself into its result's "created", "modified", + or "expunged" entries. + */ +class SMTKCORE_EXPORT EditAttributeItem : public smtk::operation::XMLOperation +{ +public: + smtkTypeMacro(smtk::attribute::EditAttributeItem); + smtkCreateMacro(EditAttributeItem); + smtkSharedFromThisMacro(smtk::operation::Operation); + smtkSuperclassMacro(smtk::operation::XMLOperation); + +protected: + Result operateInternal() override; + void generateSummary(Operation::Result&) override; + const char* xmlDescription() const override; +}; +} // namespace attribute +} // namespace smtk + +#endif // smtk_operation_operators_EditAttributeItem_h diff --git a/smtk/attribute/operators/EditAttributeItem.sbt b/smtk/attribute/operators/EditAttributeItem.sbt new file mode 100644 index 0000000000..0a735d2708 --- /dev/null +++ b/smtk/attribute/operators/EditAttributeItem.sbt @@ -0,0 +1,62 @@ + + + + + + + + + + Make a change to one item of an attribute. + + + + + The attribute containing the item to be edited. + + + + + + The path to the item whose value or enabled status will be edited. + + + + + If enabled and the item is optional, its state will set to true or false + depending on the value of this item (positive for true and zero for false). + It is an error for this to be enabled and its value to be negative. + + -1 + + + + If this value is non-negative and the item is extensible, set its number of + values to the requested size. This may fail depending on the number of values + required by the item. + + -1 + + + + The vector of values the item should hold. + + + The vector of values the item should hold. + The length of this vector may be zero (if no edits to item values are to be made). + If of non-zero length, the length must match the item's current size unless the + "extend" item is non-negative – in which case the length must match that value. + + + + + + + + + + + + + + diff --git a/smtk/attribute/operators/Signal.sbt b/smtk/attribute/operators/Signal.sbt index 7301fad7e6..74bb1fa19a 100644 --- a/smtk/attribute/operators/Signal.sbt +++ b/smtk/attribute/operators/Signal.sbt @@ -1,5 +1,5 @@ - + -- GitLab From fff93bcbf181661ad09554dd2640ed926bd50d93 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Sun, 15 Jun 2025 02:34:02 -0400 Subject: [PATCH 06/20] Provide an operation manager launch() method in python. --- smtk/operation/pybind11/PybindManager.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/smtk/operation/pybind11/PybindManager.h b/smtk/operation/pybind11/PybindManager.h index 1066926bbb..67b804d6f5 100644 --- a/smtk/operation/pybind11/PybindManager.h +++ b/smtk/operation/pybind11/PybindManager.h @@ -57,6 +57,10 @@ inline PySharedPtrClass< smtk::operation::Manager > pybind11_init_smtk_operation }, py::arg("module")) .def("managers", &smtk::operation::Manager::managers) .def("setManagers", &smtk::operation::Manager::setManagers, py::arg("managers")) + .def("launch", [](smtk::operation::Manager& manager, const std::shared_ptr& op) + { + manager.launchers()(op); + }, py::arg("operation")) ; return instance; } -- GitLab From 33e87bdc58a08739c6ef5aa530e8645b61a8ac49 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Thu, 19 Jun 2025 17:49:07 -0400 Subject: [PATCH 07/20] Preliminary support for python `with` contexts Add bindings for `__enter__` and `__exit__` to persistent objects and `smtk::common::Managers` objects so that python `with` statements can use them. These return the shared pointer to the object as the context manager (since that ensures the object remains alive during the life of the context). --- smtk/common/pybind11/PybindManagers.h | 18 ++++++++++++++++++ .../resource/pybind11/PybindPersistentObject.h | 16 ++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/smtk/common/pybind11/PybindManagers.h b/smtk/common/pybind11/PybindManagers.h index 4ee9f7c3f2..7f62b789af 100644 --- a/smtk/common/pybind11/PybindManagers.h +++ b/smtk/common/pybind11/PybindManagers.h @@ -37,6 +37,24 @@ inline PySharedPtrClass< smtk::common::Managers > pybind11_init_smtk_common_Mana PySharedPtrClass< smtk::common::Managers > instance(m, "Managers"); instance .def_static("create", (std::shared_ptr (*)()) &smtk::common::Managers::create) + .def("__enter__", [](smtk::common::Managers& self) + { + // Keep the instance alive for the duration of the context. + return self.shared_from_this(); + }, "Enter the runtime context related to this object." + ) + .def("__exit__", [](smtk::common::Managers& self, + const std::optional& exc_type, + const std::optional& exc_value, + const std::optional& traceback + ) + { + (void)self; + (void)exc_type; + (void)exc_value; + (void)traceback; + }, "Exit the runtime context related to this object." + ) .def("insert_or_assign", [](smtk::common::Managers& managers, std::shared_ptr& operationManager) { std::cerr << "Deprecated after 23.08; use insertOrAssign() instead.\n"; diff --git a/smtk/resource/pybind11/PybindPersistentObject.h b/smtk/resource/pybind11/PybindPersistentObject.h index 280fb60cbb..e48964fe50 100644 --- a/smtk/resource/pybind11/PybindPersistentObject.h +++ b/smtk/resource/pybind11/PybindPersistentObject.h @@ -31,6 +31,22 @@ inline PySharedPtrClass< smtk::resource::PersistentObject > pybind11_init_smtk_r { PySharedPtrClass< smtk::resource::PersistentObject > instance(m, "PersistentObject"); instance + .def("__enter__", [](smtk::resource::PersistentObject& self) + { + return self.shared_from_this(); + }, "Enter the runtime context related to this object." + ) + .def("__exit__", [](smtk::resource::PersistentObject& self, + const std::optional& exc_type, + const std::optional& exc_value, + const std::optional& traceback) + { + (void)self; + (void)exc_type; + (void)exc_value; + (void)traceback; + }, "Exit the runtime context related to this object." + ) .def("deepcopy", (smtk::resource::PersistentObject & (smtk::resource::PersistentObject::*)(::smtk::resource::PersistentObject const &)) &smtk::resource::PersistentObject::operator=) .def("typeName", &smtk::resource::PersistentObject::typeName) .def("id", &smtk::resource::PersistentObject::id) -- GitLab From 41b1df3de05eba914d90931b6f257eff552a48f9 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Sat, 21 Jun 2025 18:29:47 -0400 Subject: [PATCH 08/20] Support for ParaView 6 (but still Qt5). --- doc/release/notes/paraview-6-support.rst | 5 +++++ smtk/extension/paraview/widgets/pqConePropertyWidget.cxx | 4 ++++ smtk/extension/paraview/widgets/pqDiskPropertyWidget.cxx | 4 ++++ smtk/extension/paraview/widgets/pqSMTKBoxItemWidget.cxx | 4 ++++ smtk/extension/paraview/widgets/pqSMTKConeItemWidget.cxx | 4 ++++ smtk/extension/paraview/widgets/pqSMTKDiskItemWidget.cxx | 4 ++++ .../paraview/widgets/pqSMTKInfiniteCylinderItemWidget.cxx | 4 ++++ smtk/extension/paraview/widgets/pqSMTKTransformWidget.cxx | 4 ++++ smtk/extension/vtk/source/vtkConeFrustum.cxx | 4 ++++ smtk/extension/vtk/source/vtkDisk.cxx | 4 ++++ smtk/extension/vtk/source/vtkImplicitConeFrustum.cxx | 4 ++++ smtk/extension/vtk/source/vtkImplicitDisk.cxx | 4 ++++ smtk/extension/vtk/widgets/vtkConeRepresentation.cxx | 6 +++++- smtk/extension/vtk/widgets/vtkDiskRepresentation.cxx | 6 +++++- smtk/session/vtk/Geometry.cxx | 5 ++++- smtk/session/vtk/operators/Export.cxx | 4 ++++ 16 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 doc/release/notes/paraview-6-support.rst diff --git a/doc/release/notes/paraview-6-support.rst b/doc/release/notes/paraview-6-support.rst new file mode 100644 index 0000000000..bfb7eb9be1 --- /dev/null +++ b/doc/release/notes/paraview-6-support.rst @@ -0,0 +1,5 @@ +ParaView Extensions +=================== + +SMTK's ParaView extensions now support building with ParaView 6 +(but still require ParaView to be built using Qt5, not Qt6). diff --git a/smtk/extension/paraview/widgets/pqConePropertyWidget.cxx b/smtk/extension/paraview/widgets/pqConePropertyWidget.cxx index 40bf0d04f0..fa40964ff8 100644 --- a/smtk/extension/paraview/widgets/pqConePropertyWidget.cxx +++ b/smtk/extension/paraview/widgets/pqConePropertyWidget.cxx @@ -22,7 +22,11 @@ #include "vtkCommand.h" #include "vtkMath.h" #include "vtkVector.h" +#include "vtkVersionMacros.h" + +#if VTK_VERSION_NUMBER < VTK_VERSION_CHECK(9, 5, 0) #include "vtkVectorOperators.h" +#endif class pqConePropertyWidget::Internals { diff --git a/smtk/extension/paraview/widgets/pqDiskPropertyWidget.cxx b/smtk/extension/paraview/widgets/pqDiskPropertyWidget.cxx index 175a650fd2..d4089a7dff 100644 --- a/smtk/extension/paraview/widgets/pqDiskPropertyWidget.cxx +++ b/smtk/extension/paraview/widgets/pqDiskPropertyWidget.cxx @@ -23,7 +23,11 @@ #include "vtkCommand.h" #include "vtkMath.h" #include "vtkVector.h" +#include "vtkVersionMacros.h" + +#if VTK_VERSION_NUMBER < VTK_VERSION_CHECK(9, 5, 0) #include "vtkVectorOperators.h" +#endif #include diff --git a/smtk/extension/paraview/widgets/pqSMTKBoxItemWidget.cxx b/smtk/extension/paraview/widgets/pqSMTKBoxItemWidget.cxx index ea4c28625e..f6506a87d1 100644 --- a/smtk/extension/paraview/widgets/pqSMTKBoxItemWidget.cxx +++ b/smtk/extension/paraview/widgets/pqSMTKBoxItemWidget.cxx @@ -33,7 +33,11 @@ #include "vtkSMPropertyHelper.h" #include "vtkSMProxy.h" #include "vtkVector.h" +#include "vtkVersionMacros.h" + +#if VTK_VERSION_NUMBER < VTK_VERSION_CHECK(9, 5, 0) #include "vtkVectorOperators.h" +#endif #include diff --git a/smtk/extension/paraview/widgets/pqSMTKConeItemWidget.cxx b/smtk/extension/paraview/widgets/pqSMTKConeItemWidget.cxx index ba4f013478..c482078297 100644 --- a/smtk/extension/paraview/widgets/pqSMTKConeItemWidget.cxx +++ b/smtk/extension/paraview/widgets/pqSMTKConeItemWidget.cxx @@ -32,7 +32,11 @@ #include "vtkSMPropertyHelper.h" #include "vtkSMProxy.h" #include "vtkVector.h" +#include "vtkVersionMacros.h" + +#if VTK_VERSION_NUMBER < VTK_VERSION_CHECK(9, 5, 0) #include "vtkVectorOperators.h" +#endif using qtItem = smtk::extension::qtItem; using qtAttributeItemInfo = smtk::extension::qtAttributeItemInfo; diff --git a/smtk/extension/paraview/widgets/pqSMTKDiskItemWidget.cxx b/smtk/extension/paraview/widgets/pqSMTKDiskItemWidget.cxx index f004ab4412..d8fcf88bf5 100644 --- a/smtk/extension/paraview/widgets/pqSMTKDiskItemWidget.cxx +++ b/smtk/extension/paraview/widgets/pqSMTKDiskItemWidget.cxx @@ -32,7 +32,11 @@ #include "vtkSMPropertyHelper.h" #include "vtkSMProxy.h" #include "vtkVector.h" +#include "vtkVersionMacros.h" + +#if VTK_VERSION_NUMBER < VTK_VERSION_CHECK(9, 5, 0) #include "vtkVectorOperators.h" +#endif using qtItem = smtk::extension::qtItem; using qtAttributeItemInfo = smtk::extension::qtAttributeItemInfo; diff --git a/smtk/extension/paraview/widgets/pqSMTKInfiniteCylinderItemWidget.cxx b/smtk/extension/paraview/widgets/pqSMTKInfiniteCylinderItemWidget.cxx index 73c5ca6466..849306c00e 100644 --- a/smtk/extension/paraview/widgets/pqSMTKInfiniteCylinderItemWidget.cxx +++ b/smtk/extension/paraview/widgets/pqSMTKInfiniteCylinderItemWidget.cxx @@ -32,7 +32,11 @@ #include "vtkSMPropertyHelper.h" #include "vtkSMProxy.h" #include "vtkVector.h" +#include "vtkVersionMacros.h" + +#if VTK_VERSION_NUMBER < VTK_VERSION_CHECK(9, 5, 0) #include "vtkVectorOperators.h" +#endif using qtItem = smtk::extension::qtItem; using qtAttributeItemInfo = smtk::extension::qtAttributeItemInfo; diff --git a/smtk/extension/paraview/widgets/pqSMTKTransformWidget.cxx b/smtk/extension/paraview/widgets/pqSMTKTransformWidget.cxx index bcc8f398db..7a7a563ca9 100644 --- a/smtk/extension/paraview/widgets/pqSMTKTransformWidget.cxx +++ b/smtk/extension/paraview/widgets/pqSMTKTransformWidget.cxx @@ -42,7 +42,11 @@ #include "vtkSMPropertyHelper.h" #include "vtkSMProxy.h" #include "vtkVector.h" +#include "vtkVersionMacros.h" + +#if VTK_VERSION_NUMBER < VTK_VERSION_CHECK(9, 5, 0) #include "vtkVectorOperators.h" +#endif #include diff --git a/smtk/extension/vtk/source/vtkConeFrustum.cxx b/smtk/extension/vtk/source/vtkConeFrustum.cxx index 135fb784b1..da49834aee 100644 --- a/smtk/extension/vtk/source/vtkConeFrustum.cxx +++ b/smtk/extension/vtk/source/vtkConeFrustum.cxx @@ -23,7 +23,11 @@ #include "vtkStreamingDemandDrivenPipeline.h" #include "vtkTransform.h" #include "vtkVector.h" +#include "vtkVersionMacros.h" + +#if VTK_VERSION_NUMBER < VTK_VERSION_CHECK(9, 5, 0) #include "vtkVectorOperators.h" +#endif #include diff --git a/smtk/extension/vtk/source/vtkDisk.cxx b/smtk/extension/vtk/source/vtkDisk.cxx index c8663d85b9..9fdb268773 100644 --- a/smtk/extension/vtk/source/vtkDisk.cxx +++ b/smtk/extension/vtk/source/vtkDisk.cxx @@ -23,7 +23,11 @@ #include "vtkStreamingDemandDrivenPipeline.h" #include "vtkTransform.h" #include "vtkVector.h" +#include "vtkVersionMacros.h" + +#if VTK_VERSION_NUMBER < VTK_VERSION_CHECK(9, 5, 0) #include "vtkVectorOperators.h" +#endif #include diff --git a/smtk/extension/vtk/source/vtkImplicitConeFrustum.cxx b/smtk/extension/vtk/source/vtkImplicitConeFrustum.cxx index b6c5b26b15..90660fc0c2 100644 --- a/smtk/extension/vtk/source/vtkImplicitConeFrustum.cxx +++ b/smtk/extension/vtk/source/vtkImplicitConeFrustum.cxx @@ -14,7 +14,11 @@ #include "vtkObjectFactory.h" #include "vtkPlane.h" #include "vtkTransform.h" +#include "vtkVersionMacros.h" + +#if VTK_VERSION_NUMBER < VTK_VERSION_CHECK(9, 5, 0) #include "vtkVectorOperators.h" +#endif #include diff --git a/smtk/extension/vtk/source/vtkImplicitDisk.cxx b/smtk/extension/vtk/source/vtkImplicitDisk.cxx index 9987a06482..83f4b69039 100644 --- a/smtk/extension/vtk/source/vtkImplicitDisk.cxx +++ b/smtk/extension/vtk/source/vtkImplicitDisk.cxx @@ -14,7 +14,11 @@ #include "vtkObjectFactory.h" #include "vtkPlane.h" #include "vtkTransform.h" +#include "vtkVersionMacros.h" + +#if VTK_VERSION_NUMBER < VTK_VERSION_CHECK(9, 5, 0) #include "vtkVectorOperators.h" +#endif #include diff --git a/smtk/extension/vtk/widgets/vtkConeRepresentation.cxx b/smtk/extension/vtk/widgets/vtkConeRepresentation.cxx index 7cc17f04a5..4da5b12524 100644 --- a/smtk/extension/vtk/widgets/vtkConeRepresentation.cxx +++ b/smtk/extension/vtk/widgets/vtkConeRepresentation.cxx @@ -44,9 +44,13 @@ #include "vtkSphereSource.h" #include "vtkTransform.h" #include "vtkTubeFilter.h" -#include "vtkVectorOperators.h" +#include "vtkVersionMacros.h" #include "vtkWindow.h" +#if VTK_VERSION_NUMBER < VTK_VERSION_CHECK(9, 5, 0) +#include "vtkVectorOperators.h" +#endif + #include #include //for FLT_EPSILON diff --git a/smtk/extension/vtk/widgets/vtkDiskRepresentation.cxx b/smtk/extension/vtk/widgets/vtkDiskRepresentation.cxx index 80d9fb99ee..b8a488ecec 100644 --- a/smtk/extension/vtk/widgets/vtkDiskRepresentation.cxx +++ b/smtk/extension/vtk/widgets/vtkDiskRepresentation.cxx @@ -45,9 +45,13 @@ #include "vtkSphereSource.h" #include "vtkTransform.h" #include "vtkTubeFilter.h" -#include "vtkVectorOperators.h" +#include "vtkVersionMacros.h" #include "vtkWindow.h" +#if VTK_VERSION_NUMBER < VTK_VERSION_CHECK(9, 5, 0) +#include "vtkVectorOperators.h" +#endif + #include #include //for FLT_EPSILON diff --git a/smtk/session/vtk/Geometry.cxx b/smtk/session/vtk/Geometry.cxx index 7af37760cf..2f890d1410 100644 --- a/smtk/session/vtk/Geometry.cxx +++ b/smtk/session/vtk/Geometry.cxx @@ -33,6 +33,7 @@ #include "vtkPolyData.h" #include "vtkSmartPointer.h" #include "vtkUnstructuredGrid.h" +#include "vtkVersionMacros.h" namespace smtk { @@ -99,10 +100,12 @@ void Geometry::queryGeometry(const smtk::resource::PersistentObject::Ptr& obj, C switch (data->GetDataObjectType()) { case VTK_COMPOSITE_DATA_SET: - case VTK_MULTIGROUP_DATA_SET: case VTK_MULTIBLOCK_DATA_SET: +#if VTK_VERSION_NUMBER < VTK_VERSION_CHECK(9, 5, 0) + case VTK_MULTIGROUP_DATA_SET: case VTK_HIERARCHICAL_DATA_SET: case VTK_HIERARCHICAL_BOX_DATA_SET: +#endif // The VTK session doesn't support composite data yet: entry.m_geometry = nullptr; entry.m_generation = Invalid; diff --git a/smtk/session/vtk/operators/Export.cxx b/smtk/session/vtk/operators/Export.cxx index de129824c0..4a4f26a6c2 100644 --- a/smtk/session/vtk/operators/Export.cxx +++ b/smtk/session/vtk/operators/Export.cxx @@ -45,7 +45,11 @@ #include "vtkXMLImageDataWriter.h" #include "vtkVector.h" +#include "vtkVersionMacros.h" + +#if VTK_VERSION_NUMBER < VTK_VERSION_CHECK(9, 5, 0) #include "vtkVectorOperators.h" +#endif SMTK_THIRDPARTY_PRE_INCLUDE #include "boost/filesystem.hpp" -- GitLab From 65fac187b78f8014a983150fed901dcc1f636280 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Mon, 23 Jun 2025 21:45:04 -0400 Subject: [PATCH 09/20] Operations for creating, deleting, and renaming attributes. --- doc/release/notes/attribute-operations.rst | 8 ++ smtk/attribute/CMakeLists.txt | 3 + smtk/attribute/Registrar.cxx | 36 ++++---- smtk/attribute/operators/CreateAttribute.cxx | 76 +++++++++++++++++ smtk/attribute/operators/CreateAttribute.h | 38 +++++++++ smtk/attribute/operators/CreateAttribute.sbt | 35 ++++++++ smtk/attribute/operators/DeleteAttribute.cxx | 84 +++++++++++++++++++ smtk/attribute/operators/DeleteAttribute.h | 41 +++++++++ smtk/attribute/operators/DeleteAttribute.sbt | 28 +++++++ smtk/attribute/operators/EditAttributeItem.h | 10 ++- .../attribute/operators/EditAttributeItem.sbt | 2 +- smtk/attribute/operators/RenameAttribute.cxx | 80 ++++++++++++++++++ smtk/attribute/operators/RenameAttribute.h | 38 +++++++++ smtk/attribute/operators/RenameAttribute.sbt | 35 ++++++++ 14 files changed, 493 insertions(+), 21 deletions(-) create mode 100644 doc/release/notes/attribute-operations.rst create mode 100644 smtk/attribute/operators/CreateAttribute.cxx create mode 100644 smtk/attribute/operators/CreateAttribute.h create mode 100644 smtk/attribute/operators/CreateAttribute.sbt create mode 100644 smtk/attribute/operators/DeleteAttribute.cxx create mode 100644 smtk/attribute/operators/DeleteAttribute.h create mode 100644 smtk/attribute/operators/DeleteAttribute.sbt create mode 100644 smtk/attribute/operators/RenameAttribute.cxx create mode 100644 smtk/attribute/operators/RenameAttribute.h create mode 100644 smtk/attribute/operators/RenameAttribute.sbt diff --git a/doc/release/notes/attribute-operations.rst b/doc/release/notes/attribute-operations.rst new file mode 100644 index 0000000000..0104a9b133 --- /dev/null +++ b/doc/release/notes/attribute-operations.rst @@ -0,0 +1,8 @@ +Attribute System +================ + +The attribute system now provides operations to create, +delete, and rename attributes. +These were previously performed by GUI code (and still are +in the Qt GUI extensions), but in preparation for Trame +support, these operations are being made available. diff --git a/smtk/attribute/CMakeLists.txt b/smtk/attribute/CMakeLists.txt index 257ede2e9f..c5717dd8e6 100644 --- a/smtk/attribute/CMakeLists.txt +++ b/smtk/attribute/CMakeLists.txt @@ -74,11 +74,14 @@ set(jsonAttributeSrcs set(attributeOperators Associate + CreateAttribute + DeleteAttribute Dissociate EditAttributeItem Export Import Read + RenameAttribute Signal Write ) diff --git a/smtk/attribute/Registrar.cxx b/smtk/attribute/Registrar.cxx index 95799c3d71..b441cecb78 100644 --- a/smtk/attribute/Registrar.cxx +++ b/smtk/attribute/Registrar.cxx @@ -18,11 +18,14 @@ #include "smtk/attribute/UpdateManager.h" #include "smtk/attribute/operators/Associate.h" +#include "smtk/attribute/operators/CreateAttribute.h" +#include "smtk/attribute/operators/DeleteAttribute.h" #include "smtk/attribute/operators/Dissociate.h" #include "smtk/attribute/operators/EditAttributeItem.h" #include "smtk/attribute/operators/Export.h" #include "smtk/attribute/operators/Import.h" #include "smtk/attribute/operators/Read.h" +#include "smtk/attribute/operators/RenameAttribute.h" #include "smtk/attribute/operators/Signal.h" #include "smtk/attribute/operators/Write.h" @@ -30,9 +33,11 @@ #include "smtk/attribute/PythonRule.h" #endif +#include "smtk/operation/groups/DeleterGroup.h" #include "smtk/operation/groups/ExporterGroup.h" #include "smtk/operation/groups/ImporterGroup.h" #include "smtk/operation/groups/InternalGroup.h" +#include "smtk/operation/groups/NamingGroup.h" #include "smtk/operation/groups/ReaderGroup.h" #include "smtk/operation/groups/WriterGroup.h" @@ -47,11 +52,14 @@ namespace // clang-format off using OperationList = std::tuple< Associate, + CreateAttribute, + DeleteAttribute, Dissociate, EditAttributeItem, Export, Import, Read, + RenameAttribute, Signal, Write >; @@ -118,32 +126,28 @@ void Registrar::registerTo(const smtk::operation::Manager::Ptr& operationManager { operationManager->registerOperations(); - smtk::operation::ReaderGroup(operationManager) - .registerOperation(); - - smtk::operation::ExporterGroup(operationManager) - .registerOperation(); - - smtk::operation::ImporterGroup(operationManager) - .registerOperation(); - - smtk::operation::WriterGroup(operationManager) - .registerOperation(); - + // clang-format off + smtk::operation::ReaderGroup(operationManager).registerOperation(); + smtk::operation::ExporterGroup(operationManager).registerOperation(); + smtk::operation::ImporterGroup(operationManager).registerOperation(); + smtk::operation::WriterGroup(operationManager).registerOperation(); + smtk::operation::NamingGroup(operationManager).registerOperation(); smtk::operation::InternalGroup(operationManager).registerOperation(); + smtk::operation::DeleterGroup(operationManager).registerOperation(); + // clang-format on } void Registrar::unregisterFrom(const smtk::operation::Manager::Ptr& operationManager) { + // clang-format off smtk::operation::ReaderGroup(operationManager).unregisterOperation(); - smtk::operation::ExporterGroup(operationManager).unregisterOperation(); - smtk::operation::ImporterGroup(operationManager).unregisterOperation(); - smtk::operation::WriterGroup(operationManager).unregisterOperation(); - + smtk::operation::NamingGroup(operationManager).unregisterOperation(); smtk::operation::InternalGroup(operationManager).unregisterOperation(); + smtk::operation::DeleterGroup(operationManager).unregisterOperation(); + // clang-format on operationManager->unregisterOperations(); } diff --git a/smtk/attribute/operators/CreateAttribute.cxx b/smtk/attribute/operators/CreateAttribute.cxx new file mode 100644 index 0000000000..be325eb0db --- /dev/null +++ b/smtk/attribute/operators/CreateAttribute.cxx @@ -0,0 +1,76 @@ +//========================================================================= +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//========================================================================= + +#include "smtk/attribute/operators/CreateAttribute.h" + +#include "smtk/attribute/operators/CreateAttribute_xml.h" + +#include "smtk/operation/MarkGeometry.h" + +#include "smtk/attribute/Attribute.h" +#include "smtk/attribute/ComponentItem.h" +#include "smtk/attribute/IntItem.h" +#include "smtk/attribute/Resource.h" +#include "smtk/attribute/ResourceItem.h" +#include "smtk/attribute/StringItem.h" +#include "smtk/attribute/VoidItem.h" + +#include "smtk/geometry/Geometry.h" + +#include "smtk/io/Logger.h" + +namespace smtk +{ +namespace attribute +{ + +CreateAttribute::Result CreateAttribute::operateInternal() +{ + auto params = this->parameters(); + auto resource = params->associations()->valueAs(); + if (!resource) + { + return this->createResult(smtk::operation::Operation::Outcome::FAILED); + } + auto attribute = resource->createAttribute(params->findString("definition")->value()); + if (!attribute) + { + smtkErrorMacro( + this->log(), + "Could not create an attribute of type \"" << params->findString("definition")->value() + << "\"."); + return this->createResult(smtk::operation::Operation::Outcome::FAILED); + } + + auto result = this->createResult(smtk::operation::Operation::Outcome::SUCCEEDED); + auto created = result->findComponent("created"); + created->appendValue(attribute); + + // Force any geometry on the created attribute to be updated. + smtk::operation::MarkGeometry marker; + // Only empty the cache entry if there is a geometry backend + // for the resource. + auto& geom = resource->geometry(); + if (geom) + { + marker.markModified(attribute); + } + + return result; +} + +void CreateAttribute::generateSummary(Operation::Result& /*unused*/) {} + +const char* CreateAttribute::xmlDescription() const +{ + return CreateAttribute_xml; +} +} // namespace attribute +} // namespace smtk diff --git a/smtk/attribute/operators/CreateAttribute.h b/smtk/attribute/operators/CreateAttribute.h new file mode 100644 index 0000000000..9cc753f4ba --- /dev/null +++ b/smtk/attribute/operators/CreateAttribute.h @@ -0,0 +1,38 @@ +//========================================================================= +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//========================================================================= +#ifndef smtk_operation_operators_CreateAttribute_h +#define smtk_operation_operators_CreateAttribute_h + +#include "smtk/operation/XMLOperation.h" + +namespace smtk +{ +namespace attribute +{ + +/**\brief An operation to create an attribute according to a concrete definition. + */ +class SMTKCORE_EXPORT CreateAttribute : public smtk::operation::XMLOperation +{ +public: + smtkTypeMacro(smtk::attribute::CreateAttribute); + smtkCreateMacro(CreateAttribute); + smtkSharedFromThisMacro(smtk::operation::Operation); + smtkSuperclassMacro(smtk::operation::XMLOperation); + +protected: + Result operateInternal() override; + void generateSummary(Operation::Result&) override; + const char* xmlDescription() const override; +}; +} // namespace attribute +} // namespace smtk + +#endif // smtk_operation_operators_CreateAttribute_h diff --git a/smtk/attribute/operators/CreateAttribute.sbt b/smtk/attribute/operators/CreateAttribute.sbt new file mode 100644 index 0000000000..4582d0d16b --- /dev/null +++ b/smtk/attribute/operators/CreateAttribute.sbt @@ -0,0 +1,35 @@ + + + + + + + + + + Create an attribute of the given type. + + + + + The resource in which to create the resource. + + + + + + The name of a concrete attribute definition type to create. + + + + + + + + + + + + diff --git a/smtk/attribute/operators/DeleteAttribute.cxx b/smtk/attribute/operators/DeleteAttribute.cxx new file mode 100644 index 0000000000..1b97176162 --- /dev/null +++ b/smtk/attribute/operators/DeleteAttribute.cxx @@ -0,0 +1,84 @@ +//========================================================================= +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//========================================================================= + +#include "smtk/attribute/operators/DeleteAttribute.h" + +#include "smtk/attribute/operators/DeleteAttribute_xml.h" + +#include "smtk/operation/MarkGeometry.h" + +#include "smtk/attribute/Attribute.h" +#include "smtk/attribute/ComponentItem.h" +#include "smtk/attribute/IntItem.h" +#include "smtk/attribute/Resource.h" +#include "smtk/attribute/ResourceItem.h" +#include "smtk/attribute/StringItem.h" +#include "smtk/attribute/VoidItem.h" + +#include "smtk/geometry/Geometry.h" + +#include "smtk/io/Logger.h" + +namespace smtk +{ +namespace attribute +{ + +DeleteAttribute::Result DeleteAttribute::operateInternal() +{ + auto params = this->parameters(); + int numFailed = 0; + auto result = this->createResult(smtk::operation::Operation::Outcome::SUCCEEDED); + auto expunged = result->findComponent("expunged"); + smtk::operation::MarkGeometry marker; + for (const auto& obj : *params->associations()) + { + if (auto attribute = std::dynamic_pointer_cast(obj)) + { + auto rsrc = attribute->attributeResource(); + auto& geom = rsrc->geometry(); + if (!rsrc->removeAttribute(attribute)) + { + smtkErrorMacro( + this->log(), + "Could not delete attribute " << attribute->name() << " of type \"" << attribute->type() + << "\"."); + ++numFailed; + } + else + { + expunged->appendValue(attribute); + // Force any geometry on the created attribute to be updated. + // Only empty the cache entry if there is a geometry backend + // for the resource. + if (geom) + { + marker.markModified(attribute); + } + } + } + } + + if (numFailed > 0) + { + setOutcome(result, smtk::operation::Operation::Outcome::FAILED); + } + + return result; +} + +void DeleteAttribute::generateSummary(Operation::Result& /*unused*/) {} + +const char* DeleteAttribute::xmlDescription() const +{ + return DeleteAttribute_xml; +} +} // namespace attribute +} // namespace smtk diff --git a/smtk/attribute/operators/DeleteAttribute.h b/smtk/attribute/operators/DeleteAttribute.h new file mode 100644 index 0000000000..0edcaca918 --- /dev/null +++ b/smtk/attribute/operators/DeleteAttribute.h @@ -0,0 +1,41 @@ +//========================================================================= +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//========================================================================= +#ifndef smtk_operation_operators_DeleteAttribute_h +#define smtk_operation_operators_DeleteAttribute_h + +#include "smtk/operation/XMLOperation.h" + +namespace smtk +{ +namespace attribute +{ + +/**\brief An operation to delete an attribute. + * + * No checking is performed to ensure the attribute is unreferenced/unassociated + * before deletion. + */ +class SMTKCORE_EXPORT DeleteAttribute : public smtk::operation::XMLOperation +{ +public: + smtkTypeMacro(smtk::attribute::DeleteAttribute); + smtkCreateMacro(DeleteAttribute); + smtkSharedFromThisMacro(smtk::operation::Operation); + smtkSuperclassMacro(smtk::operation::XMLOperation); + +protected: + Result operateInternal() override; + void generateSummary(Operation::Result&) override; + const char* xmlDescription() const override; +}; +} // namespace attribute +} // namespace smtk + +#endif // smtk_operation_operators_DeleteAttribute_h diff --git a/smtk/attribute/operators/DeleteAttribute.sbt b/smtk/attribute/operators/DeleteAttribute.sbt new file mode 100644 index 0000000000..385a221909 --- /dev/null +++ b/smtk/attribute/operators/DeleteAttribute.sbt @@ -0,0 +1,28 @@ + + + + + + + + + + Delete the associated attribute(s). + + + + + The attribute(s) to delete. + + + + + + + + + + + diff --git a/smtk/attribute/operators/EditAttributeItem.h b/smtk/attribute/operators/EditAttributeItem.h index a0914d4109..640fa25266 100644 --- a/smtk/attribute/operators/EditAttributeItem.h +++ b/smtk/attribute/operators/EditAttributeItem.h @@ -17,11 +17,13 @@ namespace smtk namespace attribute { -/**\brief A "dummy" operation used to mark an attribute as created, modified, or expunged. +/**\brief An operation to edit one item of one attribute. - This operation does nothing internally except pass the components - which are associated to itself into its result's "created", "modified", - or "expunged" entries. + If the item is optional, it may be enabled/disabled. + If the item is extensible, the number of values may be modified. + If the item has values, those values may be modified. + If any edits are performed, the operation succeeds and + the corresponding attribute is marked modified. */ class SMTKCORE_EXPORT EditAttributeItem : public smtk::operation::XMLOperation { diff --git a/smtk/attribute/operators/EditAttributeItem.sbt b/smtk/attribute/operators/EditAttributeItem.sbt index 0a735d2708..92c389f59c 100644 --- a/smtk/attribute/operators/EditAttributeItem.sbt +++ b/smtk/attribute/operators/EditAttributeItem.sbt @@ -1,6 +1,6 @@ - + diff --git a/smtk/attribute/operators/RenameAttribute.cxx b/smtk/attribute/operators/RenameAttribute.cxx new file mode 100644 index 0000000000..da35ef22bf --- /dev/null +++ b/smtk/attribute/operators/RenameAttribute.cxx @@ -0,0 +1,80 @@ +//========================================================================= +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//========================================================================= + +#include "smtk/attribute/operators/RenameAttribute.h" + +#include "smtk/attribute/operators/RenameAttribute_xml.h" + +#include "smtk/attribute/Attribute.h" +#include "smtk/attribute/ComponentItem.h" +#include "smtk/attribute/IntItem.h" +#include "smtk/attribute/Resource.h" +#include "smtk/attribute/ResourceItem.h" +#include "smtk/attribute/StringItem.h" +#include "smtk/attribute/VoidItem.h" + +#include "smtk/geometry/Geometry.h" + +#include "smtk/io/Logger.h" + +namespace smtk +{ +namespace attribute +{ + +RenameAttribute::Result RenameAttribute::operateInternal() +{ + auto params = this->parameters(); + auto attrItem = params->associations(); + auto nameItem = params->findString("name"); + if (attrItem->numberOfValues() != nameItem->numberOfValues()) + { + return this->createResult(smtk::operation::Operation::Outcome::FAILED); + } + + std::size_t nn = nameItem->numberOfValues(); + std::size_t numFailed = 0; + auto result = this->createResult(smtk::operation::Operation::Outcome::SUCCEEDED); + auto modified = result->findComponent("modified"); + for (std::size_t ii = 0; ii < nn; ++ii) + { + auto attribute = attrItem->valueAs(ii); + if (!attribute) + { + continue; + } + if (!attribute->attributeResource()->rename(attribute, nameItem->value(ii))) + { + smtkErrorMacro( + this->log(), + "Failed to rename \"" << attribute->name() << "\" to \"" << nameItem->value(ii) << "\"."); + ++numFailed; + } + else + { + modified->appendValue(attribute); + } + } + + if (numFailed > 0) + { + smtk::operation::setOutcome(result, smtk::operation::Operation::Outcome::FAILED); + } + return result; +} + +void RenameAttribute::generateSummary(Operation::Result& /*unused*/) {} + +const char* RenameAttribute::xmlDescription() const +{ + return RenameAttribute_xml; +} +} // namespace attribute +} // namespace smtk diff --git a/smtk/attribute/operators/RenameAttribute.h b/smtk/attribute/operators/RenameAttribute.h new file mode 100644 index 0000000000..622e5e6544 --- /dev/null +++ b/smtk/attribute/operators/RenameAttribute.h @@ -0,0 +1,38 @@ +//========================================================================= +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//========================================================================= +#ifndef smtk_operation_operators_RenameAttribute_h +#define smtk_operation_operators_RenameAttribute_h + +#include "smtk/operation/XMLOperation.h" + +namespace smtk +{ +namespace attribute +{ + +/**\brief An operation to create an attribute according to a concrete definition. + */ +class SMTKCORE_EXPORT RenameAttribute : public smtk::operation::XMLOperation +{ +public: + smtkTypeMacro(smtk::attribute::RenameAttribute); + smtkCreateMacro(RenameAttribute); + smtkSharedFromThisMacro(smtk::operation::Operation); + smtkSuperclassMacro(smtk::operation::XMLOperation); + +protected: + Result operateInternal() override; + void generateSummary(Operation::Result&) override; + const char* xmlDescription() const override; +}; +} // namespace attribute +} // namespace smtk + +#endif // smtk_operation_operators_RenameAttribute_h diff --git a/smtk/attribute/operators/RenameAttribute.sbt b/smtk/attribute/operators/RenameAttribute.sbt new file mode 100644 index 0000000000..c44b1a0db2 --- /dev/null +++ b/smtk/attribute/operators/RenameAttribute.sbt @@ -0,0 +1,35 @@ + + + + + + + + + + Rename the associated attribute(s). + + + + + The attribute(s) to rename. + + + + + + The desired attribute name(s). + + + + + + + + + + + + -- GitLab From bcce21113c9730703d79091d0a8bc6ab11cbbd8d Mon Sep 17 00:00:00 2001 From: David Thompson Date: Fri, 27 Jun 2025 18:21:26 -0400 Subject: [PATCH 10/20] Wrap the operation-observer's `Key::release()` method. --- smtk/operation/pybind11/PybindObserver.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/smtk/operation/pybind11/PybindObserver.h b/smtk/operation/pybind11/PybindObserver.h index f58768114a..469b18b713 100644 --- a/smtk/operation/pybind11/PybindObserver.h +++ b/smtk/operation/pybind11/PybindObserver.h @@ -12,6 +12,7 @@ #define pybind_smtk_operation_Observer_h #include +#include #include "smtk/attribute/Attribute.h" @@ -41,6 +42,7 @@ inline py::class_< smtk::operation::Observers > pybind11_init_smtk_operation_Obs py::class_< smtk::operation::Observers::Key >(instance, "Key") .def(py::init<>()) .def("assigned", &smtk::operation::Observers::Key::assigned) + .def("release", &smtk::operation::Observers::Key::release) ; return instance; } -- GitLab From 4da1c773af82a4bd9393ebb8397a10df1c818c01 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Fri, 27 Jun 2025 18:23:13 -0400 Subject: [PATCH 11/20] Add a test of python operation observers. --- smtk/operation/testing/python/CMakeLists.txt | 1 + .../testing/python/testOperationObserver.py | 110 ++++++++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 smtk/operation/testing/python/testOperationObserver.py diff --git a/smtk/operation/testing/python/CMakeLists.txt b/smtk/operation/testing/python/CMakeLists.txt index 218eec6174..1eaf764fcb 100644 --- a/smtk/operation/testing/python/CMakeLists.txt +++ b/smtk/operation/testing/python/CMakeLists.txt @@ -9,6 +9,7 @@ set(smtkOperationPythonDataTests if (SMTK_ENABLE_POLYGON_SESSION) list(APPEND smtkOperationPythonDataTests testOperationHandler + testOperationObserver ) endif() diff --git a/smtk/operation/testing/python/testOperationObserver.py b/smtk/operation/testing/python/testOperationObserver.py new file mode 100644 index 0000000000..7591dead2e --- /dev/null +++ b/smtk/operation/testing/python/testOperationObserver.py @@ -0,0 +1,110 @@ +# ============================================================================= +# +# Copyright (c) Kitware, Inc. +# All rights reserved. +# See LICENSE.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even +# the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the above copyright notice for more information. +# +# ============================================================================= +import smtk +import smtk.string +import smtk.common +import smtk.io +import smtk.resource +import smtk.attribute +import smtk.operation +import smtk.session.polygon +import smtk.testing + +invokedCount = 0 +message = 'test' +addSelf = False + + +def observer(op, event, result): + global message, invokedCount + invokedCount += 1 + print(f'{message}: Observer invoked ({invokedCount}) for {op.typeName()} {str(event)}') + return 0 + + +class TestOperationObserver(smtk.testing.TestCase): + + def setUp(self): + self.ctxt = smtk.applicationContext() + self.resource_manager = smtk.resource.Manager.create() + self.operation_manager = smtk.operation.Manager.create() + self.operation_manager.registerResourceManager(self.resource_manager) + self.ctxt.insertOrAssign(self.resource_manager) + self.ctxt.insertOrAssign(self.operation_manager) + + smtk.resource.Registrar.registerTo(self.ctxt) + # smtk.resource.Registrar.registerTo(self.resource_manager) + + smtk.operation.Registrar.registerTo(self.ctxt) + smtk.operation.Registrar.registerTo(self.operation_manager) + + # smtk.attribute.Registrar.registerTo(self.ctxt) + smtk.attribute.Registrar.registerTo(self.resource_manager) + smtk.attribute.Registrar.registerTo(self.operation_manager) + + smtk.session.polygon.Registrar.registerTo(self.operation_manager) + smtk.session.polygon.Registrar.registerTo(self.resource_manager) + + self.key = self.operation_manager.observers().insert(observer, 'Test observer') + + def testFreeObserver(self): + import os + global message, invokedCount + + invokedCount = 0 + fpath = [smtk.testing.DATA_DIR, 'model', + '2d', 'smtk', 'epic-trex-drummer.smtk'] + op = self.operation_manager.createOperation( + 'smtk::session::polygon::Read') + print('op is', op) + op.parameters().find('filename').setValue(os.path.join(*fpath)) + print(os.path.join(*fpath)) + invokedCount = 0 + message = 'testFreeObserver' + res = op.operate() + if res.find('outcome').value(0) != int(smtk.operation.Operation.SUCCEEDED): + raise RuntimeError + self.assertEqual( + invokedCount, 2, 'Expected to be called exactly twice.') + + def methodObserver(self, op, event, result): + self.invokedCount += 1 + print( + f'{self.message}: Observer invoked ({self.invokedCount}) for {op.typeName()} {str(event)}') + if event == smtk.operation.EventType.WILL_OPERATE: + # Abort the operation + return 1 + return 0 + + def testMethodObserver(self): + import os + self.key = self.operation_manager.observers().insert( + self.methodObserver, 'Test method observer') + fpath = [smtk.testing.DATA_DIR, 'model', + '2d', 'smtk', 'epic-trex-drummer.smtk'] + op = self.operation_manager.createOperation( + 'smtk::session::polygon::Read') + print('op is', op) + op.parameters().find('filename').setValue(os.path.join(*fpath)) + print(os.path.join(*fpath)) + self.invokedCount = 0 + self.message = 'testFreeObserver' + res = op.operate() + self.assertEqual(smtk.operation.outcome(res), smtk.operation.Operation.CANCELED, + 'Expected to be canceled.') + self.assertEqual(self.invokedCount, 2, + 'Expected to be called exactly once.') + + +if __name__ == '__main__': + smtk.testing.process_arguments() + smtk.testing.main() -- GitLab From 8c77e49dc23f13060f6b4085fcc0449a01c29a07 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Mon, 30 Jun 2025 12:55:13 -0400 Subject: [PATCH 12/20] Update some test images for ParaView 6. New colormap defaults require updates. This does not update the cone widget image as there are other issues with that test. --- data/baseline/smtk/widgets/BoxWidget_1.png | 3 +++ data/baseline/smtk/widgets/FrameWidget_1.png | 3 +++ data/baseline/smtk/widgets/LineWidget_1.png | 4 ++-- data/baseline/smtk/widgets/LineWidget_2.png | 3 +++ data/baseline/smtk/widgets/PlaneWidget_1.png | 4 ++-- data/baseline/smtk/widgets/PlaneWidget_2.png | 3 +++ data/baseline/smtk/widgets/PointWidget_1.png | 3 +++ data/baseline/smtk/widgets/SphereWidget_1.png | 3 +++ data/baseline/smtk/widgets/SplineWidget_1.png | 4 ++-- data/baseline/smtk/widgets/SplineWidget_3.png | 3 +++ 10 files changed, 27 insertions(+), 6 deletions(-) create mode 100644 data/baseline/smtk/widgets/BoxWidget_1.png create mode 100644 data/baseline/smtk/widgets/FrameWidget_1.png create mode 100644 data/baseline/smtk/widgets/LineWidget_2.png create mode 100644 data/baseline/smtk/widgets/PlaneWidget_2.png create mode 100644 data/baseline/smtk/widgets/PointWidget_1.png create mode 100644 data/baseline/smtk/widgets/SphereWidget_1.png create mode 100644 data/baseline/smtk/widgets/SplineWidget_3.png diff --git a/data/baseline/smtk/widgets/BoxWidget_1.png b/data/baseline/smtk/widgets/BoxWidget_1.png new file mode 100644 index 0000000000..51a1c3731e --- /dev/null +++ b/data/baseline/smtk/widgets/BoxWidget_1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c2ca3417aa7139f5af78b450856ebd3698aa94e6b78a71dc492f7867df3ca849 +size 2756 diff --git a/data/baseline/smtk/widgets/FrameWidget_1.png b/data/baseline/smtk/widgets/FrameWidget_1.png new file mode 100644 index 0000000000..46fed62a49 --- /dev/null +++ b/data/baseline/smtk/widgets/FrameWidget_1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6d73cc6b84eb19103d0a7e598a83cfc80d3ac7cfb116af2ec20f25f129bc8814 +size 5160 diff --git a/data/baseline/smtk/widgets/LineWidget_1.png b/data/baseline/smtk/widgets/LineWidget_1.png index 9de74e00f3..8e1dbdbe4a 100644 --- a/data/baseline/smtk/widgets/LineWidget_1.png +++ b/data/baseline/smtk/widgets/LineWidget_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1d73cb3f41b34360f25039f95cc9b5b29702eb575daa268b353de8d0b2ac869f -size 2217 +oid sha256:758ca2ffa6f601289b48f6aa1f6b2ae8afe16a6db487331c4c8e28d44bc3a999 +size 1740 diff --git a/data/baseline/smtk/widgets/LineWidget_2.png b/data/baseline/smtk/widgets/LineWidget_2.png new file mode 100644 index 0000000000..8e1dbdbe4a --- /dev/null +++ b/data/baseline/smtk/widgets/LineWidget_2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:758ca2ffa6f601289b48f6aa1f6b2ae8afe16a6db487331c4c8e28d44bc3a999 +size 1740 diff --git a/data/baseline/smtk/widgets/PlaneWidget_1.png b/data/baseline/smtk/widgets/PlaneWidget_1.png index 71a94393f2..cb66a13836 100644 --- a/data/baseline/smtk/widgets/PlaneWidget_1.png +++ b/data/baseline/smtk/widgets/PlaneWidget_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:614a4cb449f52276865cdbddfbe8bd3ab7c88fd3183cefad9667917642976fd2 -size 4230 +oid sha256:1a5098fe68c8264572ae7dd393ad985061dd99bc8968493393ace6f7a0f5883d +size 3895 diff --git a/data/baseline/smtk/widgets/PlaneWidget_2.png b/data/baseline/smtk/widgets/PlaneWidget_2.png new file mode 100644 index 0000000000..cb66a13836 --- /dev/null +++ b/data/baseline/smtk/widgets/PlaneWidget_2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1a5098fe68c8264572ae7dd393ad985061dd99bc8968493393ace6f7a0f5883d +size 3895 diff --git a/data/baseline/smtk/widgets/PointWidget_1.png b/data/baseline/smtk/widgets/PointWidget_1.png new file mode 100644 index 0000000000..5d412046b5 --- /dev/null +++ b/data/baseline/smtk/widgets/PointWidget_1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a07ebc9105e2cd07944992192741de5577eee2cb5eee33bec3b5dfbef6668c66 +size 1288 diff --git a/data/baseline/smtk/widgets/SphereWidget_1.png b/data/baseline/smtk/widgets/SphereWidget_1.png new file mode 100644 index 0000000000..a395164d97 --- /dev/null +++ b/data/baseline/smtk/widgets/SphereWidget_1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c0d4d6948b7837a7e5e8e83ce1f33e39275860eabcc789a0950d9d2b229f0cac +size 22182 diff --git a/data/baseline/smtk/widgets/SplineWidget_1.png b/data/baseline/smtk/widgets/SplineWidget_1.png index 95407a738e..67bc8e21fe 100644 --- a/data/baseline/smtk/widgets/SplineWidget_1.png +++ b/data/baseline/smtk/widgets/SplineWidget_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3e6beb7d67e5c9f145601737aa59969dd4f4b3ccaa77e9c7699c43bfb90218c0 -size 6702 +oid sha256:dde9e0ad172e656ce1f6d10d9cab734a42697bc5b7efb8486499221abdb04d5b +size 5986 diff --git a/data/baseline/smtk/widgets/SplineWidget_3.png b/data/baseline/smtk/widgets/SplineWidget_3.png new file mode 100644 index 0000000000..67bc8e21fe --- /dev/null +++ b/data/baseline/smtk/widgets/SplineWidget_3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dde9e0ad172e656ce1f6d10d9cab734a42697bc5b7efb8486499221abdb04d5b +size 5986 -- GitLab From 18dff10cf4f2874fff65a2d1db3826b96f83f000 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Mon, 30 Jun 2025 13:01:24 -0400 Subject: [PATCH 13/20] Fix typename macros for attribute system. The attribute Item class and its children did not properly declare type names (either the macro was missing, did not include the full namespace, or improperly inherited a name causing it to appear twice in the type hierarchy). --- smtk/attribute/CustomItem.h | 20 +++++++++++++++++-- smtk/attribute/DateTimeItem.h | 1 + smtk/attribute/DirectoryItem.h | 1 + smtk/attribute/DoubleItem.h | 1 + smtk/attribute/FileItem.h | 1 + smtk/attribute/FileSystemItem.h | 1 + smtk/attribute/GroupItem.h | 1 + smtk/attribute/IntItem.h | 1 + smtk/attribute/ModelEntityItem.h | 1 + smtk/attribute/ReferenceItem.h | 4 ++-- smtk/attribute/ResourceItem.h | 2 +- smtk/attribute/StringItem.h | 1 + smtk/attribute/ValueItem.h | 1 + smtk/attribute/ValueItemTemplate.h | 13 ++++++++++++ smtk/attribute/VoidItem.h | 1 + .../testing/cxx/UnitTestTypeHierarchy.cxx | 8 ++++++++ 16 files changed, 53 insertions(+), 5 deletions(-) diff --git a/smtk/attribute/CustomItem.h b/smtk/attribute/CustomItem.h index 4d27f7f060..7b97ecd7c0 100644 --- a/smtk/attribute/CustomItem.h +++ b/smtk/attribute/CustomItem.h @@ -37,7 +37,8 @@ namespace attribute class SMTKCORE_EXPORT CustomItemBase : public Item { public: - smtkTypeMacro(CustomItemBase); + smtkTypeMacro(smtk::attribute::CustomItemBase); + smtkSuperclassMacro(smtk::attribute::Item); CustomItemBase(smtk::attribute::Attribute* owningAttribute, int itemPosition) : Item(owningAttribute, itemPosition) @@ -62,8 +63,23 @@ template class CustomItem : public CustomItemBase { public: - typedef std::shared_ptr Ptr; + smtkTypedefs(smtk::attribute::CustomItem); + std::string typeName() const override + { + std::ostringstream tname; + tname << "smtk::attribute::CustomItem<" << smtk::common::typeName() << ">"; + return tname.str(); + } + smtk::string::Token typeToken() const override { return smtk::string::Token(this->typeName()); } + smtkInheritanceHierarchy(smtk::attribute::CustomItem); + smtkSuperclassMacro(smtk::attribute::CustomItemBase); +private: + // Prevent smtk::common::typeName() from grabbing our subclass's type_name + // by changing its access specifier: + using CustomItemBase::type_name; + +public: static Ptr New(const std::string& myName) { return Ptr(new ItemType(myName)); } CustomItem(smtk::attribute::Attribute* owningAttribute, int itemPosition) diff --git a/smtk/attribute/DateTimeItem.h b/smtk/attribute/DateTimeItem.h index 33c8f3b941..e128dca5a2 100644 --- a/smtk/attribute/DateTimeItem.h +++ b/smtk/attribute/DateTimeItem.h @@ -32,6 +32,7 @@ class SMTKCORE_EXPORT DateTimeItem : public Item public: smtkTypeMacro(smtk::attribute::DateTimeItem); + smtkSuperclassMacro(smtk::attribute::Item); ~DateTimeItem() override; Item::Type type() const override; diff --git a/smtk/attribute/DirectoryItem.h b/smtk/attribute/DirectoryItem.h index 600b0f715c..15fefcbacc 100644 --- a/smtk/attribute/DirectoryItem.h +++ b/smtk/attribute/DirectoryItem.h @@ -31,6 +31,7 @@ class SMTKCORE_EXPORT DirectoryItem : public FileSystemItem public: smtkTypeMacro(smtk::attribute::DirectoryItem); + smtkSuperclassMacro(smtk::attribute::FileSystemItem); ~DirectoryItem() override; Item::Type type() const override; diff --git a/smtk/attribute/DoubleItem.h b/smtk/attribute/DoubleItem.h index c582d9775b..3c998cd143 100644 --- a/smtk/attribute/DoubleItem.h +++ b/smtk/attribute/DoubleItem.h @@ -29,6 +29,7 @@ class SMTKCORE_EXPORT DoubleItem : public ValueItemTemplate public: smtkTypeMacro(smtk::attribute::DoubleItem); + smtkSuperclassMacro(smtk::attribute::ValueItemTemplate); ~DoubleItem() override; Item::Type type() const override; diff --git a/smtk/attribute/FileItem.h b/smtk/attribute/FileItem.h index b38a8e5a7d..2b54b085b6 100644 --- a/smtk/attribute/FileItem.h +++ b/smtk/attribute/FileItem.h @@ -31,6 +31,7 @@ class SMTKCORE_EXPORT FileItem : public FileSystemItem public: smtkTypeMacro(smtk::attribute::FileItem); + smtkSuperclassMacro(smtk::attribute::FileSystemItem); ~FileItem() override; Item::Type type() const override; diff --git a/smtk/attribute/FileSystemItem.h b/smtk/attribute/FileSystemItem.h index 1ad5eb5033..6d8301028b 100644 --- a/smtk/attribute/FileSystemItem.h +++ b/smtk/attribute/FileSystemItem.h @@ -34,6 +34,7 @@ public: typedef std::vector::const_iterator const_iterator; smtkTypeMacro(smtk::attribute::FileSystemItem); + smtkSuperclassMacro(smtk::attribute::Item); ~FileSystemItem() override; Item::Type type() const override = 0; diff --git a/smtk/attribute/GroupItem.h b/smtk/attribute/GroupItem.h index ed4e6ab1d8..da91f2005e 100644 --- a/smtk/attribute/GroupItem.h +++ b/smtk/attribute/GroupItem.h @@ -53,6 +53,7 @@ public: typedef std::vector>::const_iterator const_iterator; smtkTypeMacro(smtk::attribute::GroupItem); + smtkSuperclassMacro(smtk::attribute::Item); ~GroupItem() override; Item::Type type() const override; diff --git a/smtk/attribute/IntItem.h b/smtk/attribute/IntItem.h index 46a826a4b7..26721b2e1f 100644 --- a/smtk/attribute/IntItem.h +++ b/smtk/attribute/IntItem.h @@ -29,6 +29,7 @@ class SMTKCORE_EXPORT IntItem : public ValueItemTemplate public: smtkTypeMacro(smtk::attribute::IntItem); + smtkSuperclassMacro(smtk::attribute::ValueItemTemplate); ~IntItem() override; Item::Type type() const override; diff --git a/smtk/attribute/ModelEntityItem.h b/smtk/attribute/ModelEntityItem.h index a987ffc9fe..4139885948 100644 --- a/smtk/attribute/ModelEntityItem.h +++ b/smtk/attribute/ModelEntityItem.h @@ -47,6 +47,7 @@ class SMTKCORE_EXPORT ModelEntityItem : public ComponentItem public: smtkTypeMacro(smtk::attribute::ModelEntityItem); + smtkSuperclassMacro(smtk::attribute::ComponentItem); ~ModelEntityItem() override; using ComponentItem::appendValue; diff --git a/smtk/attribute/ReferenceItem.h b/smtk/attribute/ReferenceItem.h index b1cfe87bd2..80f645250c 100644 --- a/smtk/attribute/ReferenceItem.h +++ b/smtk/attribute/ReferenceItem.h @@ -120,8 +120,8 @@ public: /// and the second one is the id of the component link. using Key = std::pair; - smtkTypeMacro(ReferenceItem); - smtkSuperclassMacro(Item); + smtkTypeMacro(smtk::attribute::ReferenceItem); + smtkSuperclassMacro(smtk::attribute::Item); ReferenceItem(const ReferenceItem&); ~ReferenceItem() override; diff --git a/smtk/attribute/ResourceItem.h b/smtk/attribute/ResourceItem.h index bd3ae59f74..d50fcef330 100644 --- a/smtk/attribute/ResourceItem.h +++ b/smtk/attribute/ResourceItem.h @@ -40,7 +40,7 @@ public: using const_iterator = ReferenceItemConstIteratorTemplate; using value_type = ResourcePtr; smtkTypeMacro(smtk::attribute::ResourceItem); - smtkSuperclassMacro(ReferenceItem); + smtkSuperclassMacro(smtk::attribute::ReferenceItem); /// Destructor ~ResourceItem() override; diff --git a/smtk/attribute/StringItem.h b/smtk/attribute/StringItem.h index 2a283e914c..3b4b3edc60 100644 --- a/smtk/attribute/StringItem.h +++ b/smtk/attribute/StringItem.h @@ -29,6 +29,7 @@ class SMTKCORE_EXPORT StringItem : public ValueItemTemplate public: smtkTypeMacro(smtk::attribute::StringItem); + smtkSuperclassMacro(smtk::attribute::ValueItemTemplate); ~StringItem() override; Item::Type type() const override; diff --git a/smtk/attribute/ValueItem.h b/smtk/attribute/ValueItem.h index 93e5ea4ffc..6dccc030c1 100644 --- a/smtk/attribute/ValueItem.h +++ b/smtk/attribute/ValueItem.h @@ -38,6 +38,7 @@ class SMTKCORE_EXPORT ValueItem : public smtk::attribute::Item { public: smtkTypeMacro(smtk::attribute::ValueItem); + smtkSuperclassMacro(smtk::attribute::Item); friend class ValueItemDefinition; ~ValueItem() override; diff --git a/smtk/attribute/ValueItemTemplate.h b/smtk/attribute/ValueItemTemplate.h index 4e903d7abd..aaaff065b1 100644 --- a/smtk/attribute/ValueItemTemplate.h +++ b/smtk/attribute/ValueItemTemplate.h @@ -39,6 +39,14 @@ public: typedef typename value_type::const_iterator const_iterator; typedef ValueItemDefinitionTemplate DefType; + smtkSuperclassMacro(smtk::attribute::ValueItem); + std::string typeName() const override + { + std::ostringstream tname; + tname << "smtk::attribute::ValueItemTemplate<" << smtk::common::typeName() << ">"; + return tname.str(); + } + ~ValueItemTemplate() override = default; const_iterator begin() const { return m_values.begin(); } const_iterator end() const { return m_values.end(); } @@ -131,6 +139,11 @@ protected: const std::vector m_dummy; //(1, DataT()); std::string streamValue(const DataT& val) const; + +private: + // Prevent smtk::common::typeName() from grabbing our subclass's type_name + // by changing its access specifier: + using ValueItem::type_name; }; template diff --git a/smtk/attribute/VoidItem.h b/smtk/attribute/VoidItem.h index 45aaf7517b..40e34878f0 100644 --- a/smtk/attribute/VoidItem.h +++ b/smtk/attribute/VoidItem.h @@ -30,6 +30,7 @@ class SMTKCORE_EXPORT VoidItem : public Item public: smtkTypeMacro(smtk::attribute::VoidItem); + smtkSuperclassMacro(smtk::attribute::Item); ~VoidItem() override; Item::Type type() const override; diff --git a/smtk/common/testing/cxx/UnitTestTypeHierarchy.cxx b/smtk/common/testing/cxx/UnitTestTypeHierarchy.cxx index c8bf95d2cb..605649dede 100644 --- a/smtk/common/testing/cxx/UnitTestTypeHierarchy.cxx +++ b/smtk/common/testing/cxx/UnitTestTypeHierarchy.cxx @@ -14,6 +14,7 @@ #include "smtk/attribute/Attribute.h" #include "smtk/attribute/Definition.h" +#include "smtk/attribute/DoubleItem.h" #include "smtk/attribute/Resource.h" #include "smtk/common/testing/cxx/helpers.h" @@ -150,6 +151,13 @@ int UnitTestTypeHierarchy(int /*unused*/, char** const /*unused*/) "smtk::resource::PersistentObject" }), "Failed resource hierarchy"); + test( + validateHierarchy({ "smtk::attribute::DoubleItem", + "smtk::attribute::ValueItemTemplate", + "smtk::attribute::ValueItem", + "smtk::attribute::Item" }), + "Failed attribute-item hierarchy"); + test( validateHierarchy( { "smtk::model::Entity", "smtk::resource::Component", "smtk::resource::PersistentObject" }), -- GitLab From 0c5d4d1260cb5b06f1a468fe31b266b68e049f38 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Mon, 30 Jun 2025 13:08:11 -0400 Subject: [PATCH 14/20] Improve organization of smtkCore module. The `CMakeLists.txt` files in `common` and `view` required you to list every class twice (the header file and the implementation file). Provide a variable for classes that properly inserts these files if they exist. --- smtk/common/CMakeLists.txt | 140 +++++++++++++++++-------------------- smtk/view/CMakeLists.txt | 8 ++- 2 files changed, 71 insertions(+), 77 deletions(-) diff --git a/smtk/common/CMakeLists.txt b/smtk/common/CMakeLists.txt index 132b5029b9..f4982d2777 100644 --- a/smtk/common/CMakeLists.txt +++ b/smtk/common/CMakeLists.txt @@ -1,91 +1,81 @@ # set up sources to build +set(commonClasses + Archive + Categories + Color + CompilerInformation + DateTime + DateTimeZonePair + Environment + Extension + Factory + FileLocation + Generator + GeometryUtilities + InfixExpressionError + InfixExpressionEvaluation + InfixExpressionGrammar + InfixExpressionGrammarImpl + Instances + Links + Managers + Observers + Paths + RangeDetector + RuntimeTypeContainer + Singleton + Status + StringUtil + ThreadPool + TimeZone + timezonespec + TypeHierarchy + TypeMap + TypeName + TypeContainer + TypeTraits + URL + UUID + UUIDGenerator + VersionNumber + WeakReferenceWrapper + + categories/Actions + categories/Evaluators + categories/Grammar + + json/Helper + json/jsonLinks + json/jsonTypeMap + json/jsonUUID + json/jsonVersionNumber + + testing/cxx/helpers + + update/Factory +) + set(commonSrcs - Archive.cxx - Categories.cxx - Color.cxx - DateTime.cxx - DateTimeZonePair.cxx - Environment.cxx - Extension.cxx - FileLocation.cxx - InfixExpressionGrammar.cxx - Managers.cxx - Paths.cxx - RuntimeTypeContainer.cxx - Status.cxx - StringUtil.cxx - TimeZone.cxx - timezonespec.cxx - TypeContainer.cxx - URL.cxx - UUID.cxx - UUIDGenerator.cxx - VersionNumber.cxx - json/Helper.cxx - json/jsonLinks.cxx - json/jsonUUID.cxx - json/jsonVersionNumber.cxx ) set(commonHeaders - Archive.h - Categories.h - Color.h - CompilerInformation.h - DateTime.h - DateTimeZonePair.h Deprecation.h - Environment.h - Extension.h - Factory.h - FileLocation.h - Generator.h - GeometryUtilities.h - InfixExpressionError.h - InfixExpressionEvaluation.h - InfixExpressionGrammar.h - InfixExpressionGrammarImpl.h - Instances.h - Links.h - Managers.h - Observers.h - Paths.h Processing.h - RangeDetector.h - RuntimeTypeContainer.h - Singleton.h - Status.h - StringUtil.h - ThreadPool.h - TimeZone.h - timezonespec.h - TypeHierarchy.h - TypeMap.h - TypeName.h - TypeContainer.h - TypeTraits.h - URL.h - UUID.h - UUIDGenerator.h - VersionNumber.h VersionMacros.h Visit.h - WeakReferenceWrapper.h - categories/Actions.h - categories/Evaluators.h - categories/Grammar.h - json/Helper.h - json/jsonLinks.h - json/jsonTypeMap.h - json/jsonUUID.h - json/jsonVersionNumber.h - testing/cxx/helpers.h - - update/Factory.h ${CMAKE_CURRENT_BINARY_DIR}/Version.h ) +foreach(class ${commonClasses}) + if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${class}.h") + list(APPEND commonHeaders "${class}.h") + endif() + if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${class}.cxx") + list(APPEND commonSrcs "${class}.cxx") + endif() +endforeach() + if (APPLE) set(commonSrcs ${commonSrcs} PathsHelperMacOSX.mm) set(commonHeaders ${commonHeaders} PathsHelperMacOSX.h) diff --git a/smtk/view/CMakeLists.txt b/smtk/view/CMakeLists.txt index 433fcb8569..36e95e97d7 100644 --- a/smtk/view/CMakeLists.txt +++ b/smtk/view/CMakeLists.txt @@ -56,8 +56,12 @@ set(viewHeaders ) foreach(class ${viewClasses}) - list(APPEND viewHeaders ${class}.h) - list(APPEND viewSrcs ${class}.cxx) + if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${class}.h") + list(APPEND viewHeaders "${class}.h") + endif() + if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${class}.cxx") + list(APPEND viewSrcs "${class}.cxx") + endif() endforeach() if (SMTK_ENABLE_PYTHON_WRAPPING) -- GitLab From ad54cd23437ba6d77f1960e2944d084891a8ae4a Mon Sep 17 00:00:00 2001 From: David Thompson Date: Mon, 30 Jun 2025 13:23:46 -0400 Subject: [PATCH 15/20] Add the `Singletons` container class. This is adapted from the `token` library[1] which should eventually subsume `smtk::string::Token`. [1]: https://gitlab.kitware.com/utils/token --- smtk/common/CMakeLists.txt | 1 + smtk/common/Singleton.h | 6 +++ smtk/common/Singletons.cxx | 75 ++++++++++++++++++++++++++++++++++++++ smtk/common/Singletons.h | 63 ++++++++++++++++++++++++++++++++ 4 files changed, 145 insertions(+) create mode 100644 smtk/common/Singletons.cxx create mode 100644 smtk/common/Singletons.h diff --git a/smtk/common/CMakeLists.txt b/smtk/common/CMakeLists.txt index f4982d2777..5781f46359 100644 --- a/smtk/common/CMakeLists.txt +++ b/smtk/common/CMakeLists.txt @@ -24,6 +24,7 @@ set(commonClasses RangeDetector RuntimeTypeContainer Singleton + Singletons Status StringUtil ThreadPool diff --git a/smtk/common/Singleton.h b/smtk/common/Singleton.h index b2b4924a96..1149253114 100644 --- a/smtk/common/Singleton.h +++ b/smtk/common/Singleton.h @@ -15,6 +15,12 @@ namespace smtk { namespace common { + +/// This is a base class for objects that should have a single instance per process. +/// +/// The template provides an `instance()` method for creating or fetching the instance. +/// This is distinct from the the Singletons (plural) class also in `smtk/common`, which +/// is a container that can hold a single instance of any given type for access as needed. template class Singleton { diff --git a/smtk/common/Singletons.cxx b/smtk/common/Singletons.cxx new file mode 100644 index 0000000000..e5e0fc6c0b --- /dev/null +++ b/smtk/common/Singletons.cxx @@ -0,0 +1,75 @@ +//========================================================================= +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//========================================================================= + +#include "smtk/common/Singletons.h" + +#include "smtk/common/TypeContainer.h" + +#include + +namespace smtk +{ +namespace common +{ +namespace +{ + +// rely on default initialization to 0 for static variables +unsigned int s_singletonsCleanupCounter; +TypeContainer* s_singletons; +std::mutex* s_singletonsMutex; + +} // anonymous namespace + +namespace detail +{ + +singletonsCleanup::singletonsCleanup() +{ + if (++s_singletonsCleanupCounter == 1) + { + s_singletons = nullptr; + s_singletonsMutex = new std::mutex; + } +} + +singletonsCleanup::~singletonsCleanup() +{ + if (--s_singletonsCleanupCounter == 0) + { + finalizeSingletons(); + delete s_singletonsMutex; + } +} + +} // namespace detail + +TypeContainer& singletons() +{ + std::lock_guard guard(*s_singletonsMutex); + if (!s_singletons) + { + s_singletons = new TypeContainer; + } + return *s_singletons; +} + +void finalizeSingletons() +{ + // Per @michael.migliore this can cause exceptions on macos in cases + // when the mutex is destroyed before finalizeSingletons() is called: + // std::lock_guard guard(s_singletonsMutex); + + delete s_singletons; + s_singletons = nullptr; +} + +} // namespace common +} // namespace smtk diff --git a/smtk/common/Singletons.h b/smtk/common/Singletons.h new file mode 100644 index 0000000000..2b47975457 --- /dev/null +++ b/smtk/common/Singletons.h @@ -0,0 +1,63 @@ +//========================================================================= +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//========================================================================= + +#ifndef smtk_common_Singletons_h +#define smtk_common_Singletons_h + +#include "smtk/common/TypeContainer.h" // For API and exports. + +namespace smtk +{ +namespace common +{ + +/// Return a container of singleton objects indexed by their type. +/// +/// Because the index is based on the type of the object being +/// contained (it is a checksum computed on the typename-string), +/// there can be only zero or one objects of a given type in the +/// container. +SMTKCORE_EXPORT TypeContainer& singletons(); + +/// Destroy the container holding all registered singleton objects. +/// +/// The destructors of any contained objects will be called. +/// This function is invoked at exit, but if your application +/// needs to ensure objects are released before the other +/// destructors are called (since no ordering is guaranteed for +/// statically-allocated objects), you may call this at any time. +/// +/// Libraries should not invoke this function; if your library +/// uses this singleton container, your code run at exit should +/// simply remove any stored objects rather than forcing all of +/// the application's singletons to be destroyed. +SMTKCORE_EXPORT void finalizeSingletons(); + +namespace detail +{ + +// Implementation detail for Schwarz counter idiom. +class SMTKCORE_EXPORT singletonsCleanup +{ +public: + singletonsCleanup(); + singletonsCleanup(const singletonsCleanup& other) = delete; + singletonsCleanup& operator=(const singletonsCleanup& rhs) = delete; + ~singletonsCleanup(); +}; + +static singletonsCleanup singletonsCleanupInstance; + +} // namespace detail + +} // namespace common +} // namespace smtk + +#endif // smtk_common_Singletons_h -- GitLab From 8b0392dce55450d11a2e528a4b58bf072a0ee72d Mon Sep 17 00:00:00 2001 From: David Thompson Date: Mon, 30 Jun 2025 13:31:47 -0400 Subject: [PATCH 16/20] =?UTF-8?q?Improve=20the=20macros=20providing=20`typ?= =?UTF-8?q?eToken()`=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit … to compute the hash-code of the type at compile-time if possible. This also requires us to manually insert some type-names at registration time into the string-token manager. --- smtk/SharedFromThis.h | 18 ++++++++++-------- smtk/attribute/Registrar.cxx | 1 + smtk/graph/Registrar.cxx | 3 +++ smtk/graph/testing/cxx/TestArcs.cxx | 4 ++++ smtk/graph/testing/cxx/TestRuntimeJSON.cxx | 4 ++++ smtk/model/Registrar.cxx | 1 + smtk/resource/Registrar.cxx | 3 +++ smtk/session/oscillator/Registrar.cxx | 1 + smtk/session/polygon/Registrar.cxx | 1 + smtk/session/vtk/Registrar.cxx | 1 + 10 files changed, 29 insertions(+), 8 deletions(-) diff --git a/smtk/SharedFromThis.h b/smtk/SharedFromThis.h index 84ec3a1c2b..a9bf74de95 100644 --- a/smtk/SharedFromThis.h +++ b/smtk/SharedFromThis.h @@ -30,10 +30,6 @@ /// Used by smtkTypeMacro and smtkTypeMacroBase to provide access to the inheritance hierarchy. #define smtkInheritanceHierarchyBase(...) \ - virtual smtk::string::Token typeToken() const \ - { \ - return smtk::string::Token(type_name); \ - } \ virtual std::vector classHierarchy() const \ { \ static std::vector baseTypes; \ @@ -71,10 +67,6 @@ } #define smtkInheritanceHierarchy(...) \ - smtk::string::Token typeToken() const override \ - { \ - return smtk::string::Token(type_name); \ - } \ std::vector classHierarchy() const override \ { \ static std::vector baseTypes; \ @@ -111,6 +103,11 @@ { \ return type_name; \ } \ + virtual smtk::string::Token typeToken() const \ + { \ + using namespace smtk::string::literals; \ + return smtk::string::Token::fromHash(#__VA_ARGS__##_hash); \ + } \ smtkInheritanceHierarchyBase(__VA_ARGS__); #define smtkTypenameMacro(...) \ static constexpr const char* const type_name = #__VA_ARGS__; \ @@ -118,6 +115,11 @@ { \ return type_name; \ } \ + smtk::string::Token typeToken() const override \ + { \ + using namespace smtk::string::literals; \ + return smtk::string::Token::fromHash(#__VA_ARGS__##_hash); \ + } \ smtkInheritanceHierarchy(__VA_ARGS__); ///@} diff --git a/smtk/attribute/Registrar.cxx b/smtk/attribute/Registrar.cxx index b441cecb78..919712982c 100644 --- a/smtk/attribute/Registrar.cxx +++ b/smtk/attribute/Registrar.cxx @@ -154,6 +154,7 @@ void Registrar::unregisterFrom(const smtk::operation::Manager::Ptr& operationMan void Registrar::registerTo(const smtk::resource::Manager::Ptr& resourceManager) { + smtk::string::Token dummy("smtk::attribute::Resource"); resourceManager->registerResource(read, write); auto& typeLabels = resourceManager->objectTypeLabels(); diff --git a/smtk/graph/Registrar.cxx b/smtk/graph/Registrar.cxx index 77686485e1..f8d2620fe5 100644 --- a/smtk/graph/Registrar.cxx +++ b/smtk/graph/Registrar.cxx @@ -54,6 +54,9 @@ void Registrar::registerTo(const smtk::resource::Manager::Ptr& resourceManager) auto& typeLabels = resourceManager->objectTypeLabels(); typeLabels[smtk::common::typeName()] = "graph resource"; typeLabels[smtk::common::typeName()] = "graph node"; + // We need to create an entry in the string-token manager for this type-name + // so that it exists when referenced by DeleteArc::registerDeleter(): + smtk::string::Token dummy("smtk::graph::ResourceBase"); } void Registrar::unregisterFrom(const smtk::resource::Manager::Ptr& resourceManager) diff --git a/smtk/graph/testing/cxx/TestArcs.cxx b/smtk/graph/testing/cxx/TestArcs.cxx index fca0854b29..231b004095 100644 --- a/smtk/graph/testing/cxx/TestArcs.cxx +++ b/smtk/graph/testing/cxx/TestArcs.cxx @@ -863,6 +863,10 @@ void testRuntimeArcs() int TestArcs(int, char*[]) { + // We need to create an entry in the string-token manager for this type-name + // so that it exists when referenced by DeleteArc::registerDeleter(): + smtk::string::Token dummy("smtk::graph::ResourceBase"); + // Needed for log messages to appear in test output: smtk::io::Logger::instance().setFlushToStdout(true); diff --git a/smtk/graph/testing/cxx/TestRuntimeJSON.cxx b/smtk/graph/testing/cxx/TestRuntimeJSON.cxx index 691b6895b7..5de2646426 100644 --- a/smtk/graph/testing/cxx/TestRuntimeJSON.cxx +++ b/smtk/graph/testing/cxx/TestRuntimeJSON.cxx @@ -74,6 +74,10 @@ int TestRuntimeJSON(int, char*[]) using namespace smtk::test; using namespace smtk::graph; + // We need to create an entry in the string-token manager for this type-name + // so that it exists when referenced by DeleteArc::registerDeleter(): + smtk::string::Token dummy("smtk::graph::ResourceBase"); + // DirectedDistinct: Notable → Comment // DirectedSelfArc: Thingy → Thingy // UndirectedSelfArc: Thingy — Thingy diff --git a/smtk/model/Registrar.cxx b/smtk/model/Registrar.cxx index 33c4718615..21c55bb9b1 100644 --- a/smtk/model/Registrar.cxx +++ b/smtk/model/Registrar.cxx @@ -77,6 +77,7 @@ void Registrar::unregisterFrom(const smtk::operation::Manager::Ptr& operationMan void Registrar::registerTo(const smtk::resource::Manager::Ptr& resourceManager) { + smtk::string::Token dummy("smtk::model::Resource"); resourceManager->registerResource(); auto& typeLabels = resourceManager->objectTypeLabels(); diff --git a/smtk/resource/Registrar.cxx b/smtk/resource/Registrar.cxx index e6caaf38af..ae73b12096 100644 --- a/smtk/resource/Registrar.cxx +++ b/smtk/resource/Registrar.cxx @@ -35,6 +35,9 @@ void Registrar::registerTo(const smtk::common::Managers::Ptr& managers) typeLabels[smtk::common::typeName()] = "component"; typeLabels[smtk::common::typeName()] = "resource"; typeLabels[smtk::common::typeName()] = "object"; + smtk::string::Token dummy1("smtk::resource::Resource"); + smtk::string::Token dummy2("smtk::resource::Component"); + smtk::string::Token dummy3("smtk::resource::PersistentObject"); } void Registrar::unregisterFrom(const smtk::common::Managers::Ptr& managers) diff --git a/smtk/session/oscillator/Registrar.cxx b/smtk/session/oscillator/Registrar.cxx index e5a3f8934c..c28e76e523 100644 --- a/smtk/session/oscillator/Registrar.cxx +++ b/smtk/session/oscillator/Registrar.cxx @@ -40,6 +40,7 @@ typedef std::tuple Ope void Registrar::registerTo(const smtk::resource::Manager::Ptr& resourceManager) { + smtk::string::Token dummy("smtk::session::oscillator::Resource"); // Providing a write method to the resource manager is what allows // modelbuilder's pqSMTKSaveResourceBehavior to determine how to write // the resource when users click "Save Resource" or ⌘ S/⌃ S. diff --git a/smtk/session/polygon/Registrar.cxx b/smtk/session/polygon/Registrar.cxx index fc2cf92c5b..489d2bfee1 100644 --- a/smtk/session/polygon/Registrar.cxx +++ b/smtk/session/polygon/Registrar.cxx @@ -77,6 +77,7 @@ typedef std::tuple< void Registrar::registerTo(const smtk::resource::Manager::Ptr& resourceManager) { + smtk::string::Token dummy("smtk::session::polygon::Resource"); resourceManager->registerResource(read, write); } diff --git a/smtk/session/vtk/Registrar.cxx b/smtk/session/vtk/Registrar.cxx index 4740da475c..a84cc0cedf 100644 --- a/smtk/session/vtk/Registrar.cxx +++ b/smtk/session/vtk/Registrar.cxx @@ -40,6 +40,7 @@ typedef std::tuple OperationList; void Registrar::registerTo(const smtk::resource::Manager::Ptr& resourceManager) { + smtk::string::Token dummy("smtk::session::vtk::Resource"); resourceManager->registerResource(read, write); RegisterVTKBackend::registerClass(); } -- GitLab From 7633a55250b8594d6c88dfc84c837d4d8e676ddf Mon Sep 17 00:00:00 2001 From: David Thompson Date: Mon, 30 Jun 2025 13:38:04 -0400 Subject: [PATCH 17/20] Introduce `smtk::view::HandleManager`. This class provides handles for persistent objects, views, and attribute items (and any other classes which inherit `std::enable_shared_from_this<>` and use SMTK's type macros). --- smtk/attribute/pybind11/PybindItem.h | 21 +- smtk/attribute/testing/python/CMakeLists.txt | 9 + .../attributeOperationsAndHandlesTest.py | 147 ++++++++++ smtk/common/pybind11/PybindCommon.cxx | 7 + smtk/operation/pybind11/PybindOperation.h | 2 +- .../pybind11/PybindPersistentObject.h | 23 +- smtk/view/CMakeLists.txt | 1 + smtk/view/HandleManager.cxx | 216 +++++++++++++++ smtk/view/HandleManager.h | 256 ++++++++++++++++++ smtk/view/Registrar.cxx | 42 +++ smtk/view/Registrar.h | 4 + smtk/view/plugin/CMakeLists.txt | 1 + smtk/view/pybind11/PybindHandleManager.h | 131 +++++++++ smtk/view/pybind11/PybindView.cxx | 5 + smtk/view/pybind11/PybindView.h | 25 +- 15 files changed, 844 insertions(+), 46 deletions(-) create mode 100644 smtk/attribute/testing/python/attributeOperationsAndHandlesTest.py create mode 100644 smtk/view/HandleManager.cxx create mode 100644 smtk/view/HandleManager.h create mode 100644 smtk/view/pybind11/PybindHandleManager.h diff --git a/smtk/attribute/pybind11/PybindItem.h b/smtk/attribute/pybind11/PybindItem.h index a03aa1d084..995524d9e8 100644 --- a/smtk/attribute/pybind11/PybindItem.h +++ b/smtk/attribute/pybind11/PybindItem.h @@ -19,6 +19,7 @@ #include "smtk/attribute/CopyAssignmentOptions.h" #include "smtk/io/Logger.h" #include "smtk/simulation/UserData.h" +#include "smtk/view/HandleManager.h" #include #include @@ -89,23 +90,19 @@ inline PySharedPtrClass< smtk::attribute::Item > pybind11_init_smtk_attribute_It auto result = item.assign(sourceItem, options, logger); return result.success(); }, py::arg("sourceItem"), py::arg("options"), py::arg("logger")) - .def("pointer", [](const smtk::attribute::Item& self) + .def("pointer", [](smtk::attribute::Item& self) { - std::ostringstream addr; - addr << std::hex << &self; - allItems.insert(&self); - return addr.str(); + return smtk::view::HandleManager::instance()->handle(&self); + }) - .def_static("fromPointer", [](const std::string& ptrStr) + .def_static("fromPointer", [](const std::string& ptrStr) -> std::shared_ptr { - char* end = const_cast(ptrStr.c_str() + ptrStr.size()); - // NOLINTNEXTLINE(performance-no-int-to-ptr) - auto* ptr = reinterpret_cast(strtoull(ptrStr.c_str(), &end, 16)); - if (ptr && allItems.find(ptr) != allItems.end()) + auto* item = smtk::view::HandleManager::instance()->fromHandle(ptrStr); + if (!item) { - return ptr->shared_from_this(); + return smtk::attribute::Item::Ptr(); } - return smtk::attribute::Item::Ptr(); + return item->shared_from_this(); }) .def_static("type2String", &smtk::attribute::Item::type2String, py::arg("t")) .def_static("string2Type", &smtk::attribute::Item::string2Type, py::arg("s")) diff --git a/smtk/attribute/testing/python/CMakeLists.txt b/smtk/attribute/testing/python/CMakeLists.txt index 967dab4405..08554423d1 100644 --- a/smtk/attribute/testing/python/CMakeLists.txt +++ b/smtk/attribute/testing/python/CMakeLists.txt @@ -29,6 +29,14 @@ if (SMTK_ENABLE_VTK_SESSION) list(APPEND vtk_tests buildAttributeTest) endif() +# Tests that require plugins to be loaded currently require ParaView. +set(paraview_tests) +if (SMTK_ENABLE_PARAVIEW_SUPPORT) + list(APPEND paraview_tests + attributeOperationsAndHandlesTest + ) +endif() + # Tests that require SMTK_DATA_DIR set(smtkAttributePythonDataTests copyDefinitionTest @@ -45,6 +53,7 @@ set(smtkAttributePythonNewDataTests valueItemRotateTest vectorExpressionTest ${vtk_tests} + ${paraview_tests} ) if (SMTK_DATA_DIR) diff --git a/smtk/attribute/testing/python/attributeOperationsAndHandlesTest.py b/smtk/attribute/testing/python/attributeOperationsAndHandlesTest.py new file mode 100644 index 0000000000..1024842f22 --- /dev/null +++ b/smtk/attribute/testing/python/attributeOperationsAndHandlesTest.py @@ -0,0 +1,147 @@ +import sys +import os +# ============================================================================= +# +# Copyright (c) Kitware, Inc. +# All rights reserved. +# See LICENSE.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even +# the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the above copyright notice for more information. +# +# ============================================================================= + +import smtk +import smtk.string +import smtk.common +import smtk.io +import smtk.resource +import smtk.attribute +import smtk.operation +import smtk.view +from smtk import attribute +from smtk import io + +import smtk.testing + + +class TestAttributeOperationsAndHandles(smtk.testing.TestCase): + + def setUp(self): + print('Setup') + if smtk.testing.DATA_DIR == '': + self.skipTest('SMTK test-data directory not provided') + + self.app = smtk.applicationContext() + print(f' Application context? {self.app != None}') + self.resource_mgr = self.app.get('smtk.resource.Manager') + self.operation_mgr = self.app.get('smtk.operation.Manager') + print(f' Resource manager? {self.resource_mgr != None}') + print(f' Operation manager? {self.operation_mgr != None}') + if self.resource_mgr == None: + # This test should only be run if ParaView support was enabled. + raise Exception('Required plugins were not loaded.') + + # Attribute registrar + print(' Attribute registrar') + smtk.attribute.Registrar.registerTo(self.resource_mgr) + smtk.attribute.Registrar.registerTo(self.operation_mgr) + # Operation registrar + print(' Operation registrar') + smtk.operation.Registrar.registerTo(self.operation_mgr) + + print(' Resource') + self.resource = self.resource_mgr.createResource( + 'smtk::attribute::Resource') + print(' created.') + logger = smtk.io.Logger() + reader = smtk.io.AttributeReader() + filenm = os.path.join(smtk.testing.DATA_DIR, 'attribute', + 'attribute_collection', 'HydraTemplateV1.sbt') + status = reader.read(self.resource, filenm, logger) + print(' read from template.') + print( + '\n'.join([logger.record(i).message for i in range(logger.numberOfRecords())])) + self.assertFalse(status, 'Could not read {fn}'.format(fn=filenm)) + + def checkAttributeCreation(self, op, result): + """When the creation operation launched by testAttributeOperations() completes, + this method is called to test its success.""" + outcome = smtk.operation.outcome(result) + self.assertEqual( + outcome, smtk.operation.Operation.Outcome.SUCCEEDED, 'Creation failed') + self.att = result.findComponent('created').values()[0] + self.attHandle = smtk.view.HandleManager.instance().handle(self.att) + print(f' Attribute {str(self.att)} handle {self.attHandle}') + + def checkAttributeRename(self, op, result): + """When the rename operation launched by testAttributeOperations() completes, + this method is called to test its success.""" + outcome = smtk.operation.outcome(result) + self.assertEqual( + outcome, smtk.operation.Operation.Outcome.SUCCEEDED, 'Rename failed') + self.assertEqual(self.att, smtk.view.HandleManager.instance().object(self.attHandle), + 'Attribute should still be fetchable from handle.') + self.assertEqual(self.att.name(), 'thingy', + 'Name should have been changed.') + + def checkAttributeDeletion(self, op, result): + """When the deletion operation launched by testAttributeOperations() completes, + this method is called to test its success.""" + outcome = smtk.operation.outcome(result) + op.log().setFlushToStdout(True) + self.assertEqual( + outcome, smtk.operation.Operation.Outcome.SUCCEEDED, 'Delete failed') + + def handleEvents(self, handleMap, event): + """Called when handles are created/modified/expunged.""" + print(' Handle updates', event, handleMap) + + def testAttributeOperations(self): + print('1. Insert handle observer') + key = smtk.view.HandleManager.instance().observers().insert( + self.handleEvents, 0, 'Handle observer') + print(f' Key assigned? {key.assigned()}') + + # Test creation + print('2. Create an attribute') + op = self.operation_mgr.createOperation( + 'smtk::attribute::CreateAttribute') + print(f' Operation? {op != None}') + op.parameters().associate(self.resource) + op.parameters().findString('definition').setValue('hydrostat') + print(f' Operation able to run? {op.ableToOperate()}') + res = op.operate() + print(f' Operation outcome: {smtk.operation.outcome(res)}') + self.checkAttributeCreation(op, res) + + # Test rename + print('3. Rename an attribute') + op = self.operation_mgr.createOperation( + 'smtk::attribute::RenameAttribute') + print(f' Operation? {op != None}') + op.parameters().associate(self.att) + op.parameters().findString('name').appendValue('thingy') + print(f' Operation able to run? {op.ableToOperate()}') + res = op.operate() + print(f' Operation outcome: {smtk.operation.outcome(res)}') + self.checkAttributeRename(op, res) + + # Test deletion + print('4. Delete an attribute') + op = self.operation_mgr.createOperation( + 'smtk::attribute::DeleteAttribute') + print(f' Operation? {op != None}') + op.parameters().associate(self.att) + print(f' Operation able to run? {op.ableToOperate()}') + res = op.operate() + print(f' Operation outcome: {smtk.operation.outcome(res)}') + self.checkAttributeDeletion(op, res) + self.assertEqual(None, smtk.view.HandleManager.instance().object(self.attHandle), + 'Attribute should not still be fetchable from handle.') + + +if __name__ == '__main__': + smtk.testing.process_arguments() + smtk.testing.main() diff --git a/smtk/common/pybind11/PybindCommon.cxx b/smtk/common/pybind11/PybindCommon.cxx index 8f8ffb9150..09cd980702 100644 --- a/smtk/common/pybind11/PybindCommon.cxx +++ b/smtk/common/pybind11/PybindCommon.cxx @@ -54,6 +54,13 @@ PYBIND11_MODULE(_smtkPybindCommon, common) { common.doc() = ""; + // Import modules so that return types of smtk.common.Managers' get() method are known. + py::module::import("smtk.string"); + py::module::import("smtk.resource"); + py::module::import("smtk.operation"); + py::module::import("smtk.geometry"); + py::module::import("smtk.view"); + // The order of these function calls is important! It was determined by // comparing the dependencies of each of the wrapped objects. py::class_< smtk::common::Categories > smtk_common_Categories = pybind11_init_smtk_common_Categories(common); diff --git a/smtk/operation/pybind11/PybindOperation.h b/smtk/operation/pybind11/PybindOperation.h index 5e1775d1e8..8fde55cb57 100644 --- a/smtk/operation/pybind11/PybindOperation.h +++ b/smtk/operation/pybind11/PybindOperation.h @@ -137,7 +137,7 @@ inline PySharedPtrClass< smtk::operation::Operation, smtk::operation::PyOperatio .def("createResult", &smtk::operation::Operation::createResult, py::arg("arg0")) .def("manager", &smtk::operation::Operation::manager) .def("managers", &smtk::operation::Operation::managers) - .def("addHandler", &smtk::operation::Operation::addHandler, py::arg("handler"), py::arg("priority")) + .def("addHandler", &smtk::operation::Operation::addHandler, py::arg("handler"), py::arg("priority") = 0) .def("removeHandler", &smtk::operation::Operation::removeHandler, py::arg("handler"), py::arg("priority")) .def("clearHandlers", &smtk::operation::Operation::clearHandlers) .def("restoreTrace", (bool (smtk::operation::Operation::*)(::std::string const &)) &smtk::operation::Operation::restoreTrace) diff --git a/smtk/resource/pybind11/PybindPersistentObject.h b/smtk/resource/pybind11/PybindPersistentObject.h index e48964fe50..e7ce52ff11 100644 --- a/smtk/resource/pybind11/PybindPersistentObject.h +++ b/smtk/resource/pybind11/PybindPersistentObject.h @@ -16,17 +16,13 @@ #include "smtk/resource/PersistentObject.h" #include "smtk/common/pybind11/PybindUUIDTypeCaster.h" +#include "smtk/view/HandleManager.h" #include #include namespace py = pybind11; -namespace -{ - std::unordered_set allObjects; -} - inline PySharedPtrClass< smtk::resource::PersistentObject > pybind11_init_smtk_resource_PersistentObject(py::module &m) { PySharedPtrClass< smtk::resource::PersistentObject > instance(m, "PersistentObject"); @@ -58,23 +54,18 @@ inline PySharedPtrClass< smtk::resource::PersistentObject > pybind11_init_smtk_r .def("classHierarchy", &smtk::resource::PersistentObject::classHierarchy) .def("matchesType", &smtk::resource::PersistentObject::matchesType, py::arg("candidate")) .def("generationsFromBase", &smtk::resource::PersistentObject::generationsFromBase, py::arg("base")) - .def("pointer", [](const smtk::resource::PersistentObject& self) + .def("pointer", [](smtk::resource::PersistentObject& self) { - std::ostringstream addr; - addr << std::hex << &self; - allObjects.insert(&self); - return addr.str(); + return smtk::view::HandleManager::instance()->handle(&self); }) .def_static("fromPointer", [](const std::string& ptrStr) { - char* end = const_cast(ptrStr.c_str() + ptrStr.size()); - // NOLINTNEXTLINE(performance-no-int-to-ptr) - auto* ptr = reinterpret_cast(strtoull(ptrStr.c_str(), &end, 16)); - if (ptr && allObjects.find(ptr) != allObjects.end()) + auto* obj = smtk::view::HandleManager::instance()->fromHandle(ptrStr); + if (!obj) { - return ptr->shared_from_this(); + return smtk::resource::PersistentObject::Ptr(); } - return smtk::resource::PersistentObject::Ptr(); + return obj->shared_from_this(); }) ; return instance; diff --git a/smtk/view/CMakeLists.txt b/smtk/view/CMakeLists.txt index 36e95e97d7..5115398d3a 100644 --- a/smtk/view/CMakeLists.txt +++ b/smtk/view/CMakeLists.txt @@ -9,6 +9,7 @@ set(viewClasses Configuration DescriptivePhrase EmptySubphraseGenerator + HandleManager Information LockedResourceBadge Manager diff --git a/smtk/view/HandleManager.cxx b/smtk/view/HandleManager.cxx new file mode 100644 index 0000000000..d720a31a8c --- /dev/null +++ b/smtk/view/HandleManager.cxx @@ -0,0 +1,216 @@ +//========================================================================= +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//========================================================================= +#include "smtk/view/HandleManager.h" + +#include "smtk/attribute/Attribute.h" +#include "smtk/attribute/ComponentItem.h" +#include "smtk/attribute/ResourceItem.h" +#include "smtk/common/Singletons.h" + +#include + +namespace smtk +{ +namespace view +{ + +HandleManager::HandleManager() = default; + +HandleManager::Ptr HandleManager::instance() +{ + auto& singletons(smtk::common::singletons()); + if (!singletons.contains()) + { + singletons.insertOrAssign(std::make_shared()); + } + return singletons.get(); +} + +void HandleManager::registerOperationManager(const smtk::operation::Manager::Ptr& opMgr) +{ + if (m_operationManager == opMgr) + { + return; + } + + if (m_operationManager) + { + m_operationManager->observers().erase(m_watcher); + } + + m_operationManager = opMgr; + + if (m_operationManager) + { + m_watcher = m_operationManager->observers().insert( + [this]( + const smtk::operation::Operation& op, + smtk::operation::EventType event, + smtk::operation::Operation::Result result) { + this->handleOperation(op, event, result); + return 0; + }, + std::numeric_limits::max(), + false, + "Maintain object handles."); + } +} + +bool HandleManager::typeResolves( + smtk::string::Token requestedType, + smtk::string::Token immediateType) +{ + if (requestedType == immediateType) + { + return true; + } + return this->typeResolvesRecursive(requestedType, immediateType); +} + +bool HandleManager::typeResolvesRecursive( + smtk::string::Token requestedType, + smtk::string::Token immediateType) +{ + // std::cout << " Resolve " << immediateType.data() << " to " << requestedType.data() << "\n"; + auto it = m_typeMap.find(immediateType); + if (it == m_typeMap.end()) + { + // std::cout << " No entry for " << immediateType.data() << "\n"; + return false; + } + if (it->second == requestedType) + { + // std::cout << " Matched " << immediateType.data() << "\n"; + return true; + } + if (it->first == it->second) + { + // std::cout << " No match and " << it->first.data() << " is the base-most class\n"; + return false; + } + return this->typeResolvesRecursive(requestedType, it->second); +} + +void HandleManager::handleOperation( + const smtk::operation::Operation& op, + smtk::operation::EventType event, + smtk::operation::Operation::Result result) +{ + (void)op; + if (event == smtk::operation::EventType::WILL_OPERATE) + { + return; + } + HandlesByType handleGroup; + for (auto obj : *result->findComponent("expunged")) + { + if (auto att = std::dynamic_pointer_cast(obj)) + { + // Invalidate any items that exist in the manager as well. + std::vector> items; + att->filterItems( + items, [](std::shared_ptr) { return true; }, /*only active*/ false); + for (const auto& item : items) + { + auto it = m_objects.find(this->handleString(item.get())); + if (it == m_objects.end()) + { + continue; + } + handleGroup[it->second.m_type].insert(it->second.m_handle); + } + } + auto it = m_objects.find(this->handleString(obj.get())); + if (it == m_objects.end()) + { + continue; + } + handleGroup[it->second.m_type].insert(it->second.m_handle); + m_objects.erase(it); + } + for (auto obj : *result->findResource("resourcesToExpunge")) + { + auto it = m_objects.find(this->handleString(obj.get())); + if (it == m_objects.end()) + { + continue; + } + handleGroup[it->second.m_type].insert(it->second.m_handle); + m_objects.erase(it); + } + if (!handleGroup.empty()) + { + m_observers(handleGroup, EventType::Expunged); + handleGroup.clear(); + } + + // TODO: We could accept some filters and notify observers when objects + // of a particular type are created. It might make maintaining + // views easier since some views need to respond to new attributes + // being created (for example). + for (auto obj : *result->findComponent("created")) + { + // TODO: Check that handle should be created by manager. + handleGroup[obj->typeToken()].insert(this->handle(obj.get())); + } + for (auto obj : *result->findResource("resourcesCreated")) + { + // TODO: Check that handle should be created by manager. + handleGroup[obj->typeToken()].insert(this->handle(obj.get())); + } + if (!handleGroup.empty()) + { + m_observers(handleGroup, EventType::Created); + handleGroup.clear(); + } + + for (auto obj : *result->findComponent("modified")) + { + if (auto att = std::dynamic_pointer_cast(obj)) + { + // Invalidate any items that exist in the manager as well. + std::vector> items; + att->filterItems( + items, [](std::shared_ptr) { return true; }, /*only active*/ false); + for (const auto& item : items) + { + auto it = m_objects.find(this->handleString(item.get())); + if (it == m_objects.end()) + { + continue; + } + handleGroup[it->second.m_type].insert(it->second.m_handle); + } + } + auto it = m_objects.find(this->handleString(obj.get())); + if (it == m_objects.end()) + { + continue; + } + handleGroup[it->second.m_type].insert(it->second.m_handle); + } + for (auto obj : *result->findResource("resourcesModified")) + { + auto it = m_objects.find(this->handleString(obj.get())); + if (it == m_objects.end()) + { + continue; + } + handleGroup[it->second.m_type].insert(it->second.m_handle); + } + if (!handleGroup.empty()) + { + m_observers(handleGroup, EventType::Modified); + handleGroup.clear(); + } +} + +} // namespace view +} // namespace smtk diff --git a/smtk/view/HandleManager.h b/smtk/view/HandleManager.h new file mode 100644 index 0000000000..b62bf50c66 --- /dev/null +++ b/smtk/view/HandleManager.h @@ -0,0 +1,256 @@ +//========================================================================= +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//========================================================================= +#ifndef smtk_view_HandleManager_h +#define smtk_view_HandleManager_h + +#include "smtk/CoreExports.h" +#include "smtk/PublicPointerDefs.h" +#include "smtk/SharedFromThis.h" + +#include "smtk/attribute/Attribute.h" +#include "smtk/attribute/Item.h" +#include "smtk/operation/Manager.h" +#include "smtk/operation/Observer.h" +#include "smtk/resource/Lock.h" +#include "smtk/resource/Resource.h" +#include "smtk/view/Configuration.h" + +#include + +namespace smtk +{ +namespace view +{ + +/**\brief A utility for views manage dependencies on objects of various types. + * + * This class, which behaves as a singleton, manages "handles" (unique strings) + * for objects which may or may not be persistent. Instances of any class + * that inherits `std::enable_shared_from_this<>` and uses the SMTK type macros + * (so that typeToken() and related methods are defined) may be assigned handles. + * + * The intent is for handles to be used as references during the lifetime of + * a process (usually the server process, while the client would be a browser + * which may be remote) in order to refer to items that may not be present on + * the client. Handles are not unique across multiple processes. + * Handle strings are not preserved on disk; they live only for the duration of + * the process. + * + * Remote views of data may then query the process where the objects reside to + * obtain information on the objects or to modify the objects. + */ +class SMTKCORE_EXPORT HandleManager : smtkEnableSharedPtr(HandleManager) +{ +public: + smtkTypeMacroBase(HandleManager); + smtkCreateMacro(HandleManager); + + HandleManager(); + HandleManager(const HandleManager&) = delete; + HandleManager& operator=(const HandleManager&) = delete; + + static Ptr instance(); + + /// Do not manage the given \a object, but return the handle string for it. + template + std::string handleString(const T* object) const + { + if (!object) + { + return "0x0"; + } + std::ostringstream hstr; + hstr << std::hex << object << std::dec; + return hstr.str(); + } + + /// Return true if the \a object has a pre-existing handle and false otherwise. + template + bool hasHandle(const T* object) const + { + auto key = this->handleString(object); + return m_objects.find(key) != m_objects.end(); + } + + template + std::string handle(T* object) + { + if (!object) + { + return "0x0"; + } + auto immediateType = object->typeToken(); + auto itit = m_typeMap.find(immediateType); + if (itit == m_typeMap.end()) + { + auto inh = object->classHierarchy(); + for (std::size_t ii = 1; ii < inh.size(); ++ii) + { + // std::cerr << "** Mapping " << inh[ii - 1].data() << " to " << inh[ii].data() << "\n"; + m_typeMap[inh[ii - 1]] = inh[ii]; + } + // Force the base-most type to map to itself: + m_typeMap[*inh.rbegin()] = *inh.rbegin(); + // std::cerr << "** Mapping " << inh.rbegin()->data() << " to " << inh.rbegin()->data() << "\n"; + } + std::ostringstream hstr; + hstr << std::hex << object << std::dec; + auto obit = m_objects.find(hstr.str()); + if (obit != m_objects.end()) + { + return obit->second.m_handle; + } + obit = m_objects.emplace(hstr.str(), HandleEntry(immediateType, object)).first; + return obit->second.m_handle; + } + + template + T* fromHandle(const std::string& handle) + { + auto obit = m_objects.find(handle); + if (obit == m_objects.end()) + { + std::cerr << "No object for handle " << handle << "\n"; + return nullptr; + } + if ( + obit->second.m_resource && + obit->second.m_resource->locked() == smtk::resource::LockType::Write) + { + std::cerr << "Resource owning " << handle << " is locked for writing.\n"; + return nullptr; + } + smtk::string::Token typeToken(smtk::common::typeName()); + if (!this->typeResolves(typeToken, obit->second.m_type)) + { + std::cerr << "Object for handle " << handle << " is type " << obit->second.m_type.data() + << " not " << typeToken.data() << "\n"; + return nullptr; + } + return reinterpret_cast(obit->second.m_object); + } + + /// Event types for handles. + enum EventType + { + Created, // Objects with the given handles were created. + Expunged, // Objects with the given handles were expunged. + Modified // Objects with the given handles have been modified. + }; + + /// The manager aggregates changes to handles by the type of object involved. + using HandlesByType = std::unordered_map>; + + /// Observers are passed a collection of handles (strings) grouped by the type of object + /// at the handle (a string token), and the type of event. + /// + /// Each time an operation complete, the the observer may be called twice: once + /// for expunged handles and once for modified handles. + using Observer = std::function; + + /// The type of the object which holds a collection of observers to invoke. + using Observers = smtk::common::Observers; + + /// Return the collection of observers so observers can be added or removed. + /// + /// These observers will only be called from within an operation observer, + /// at which point the locks on relevant resources will be held. + Observers& observers() { return m_observers; } + const Observers& observers() const { return m_observers; } + + /// Tell this manager to monitor operations run for \a opMgr to scrub deleted objects. + void registerOperationManager(const smtk::operation::Manager::Ptr& opMgr); + +protected: + /// This method is invoked by the operation observer to remove expunged entries. + void handleOperation( + const smtk::operation::Operation& op, + smtk::operation::EventType event, + smtk::operation::Operation::Result result); + + /// Use m_typeMap to validate that a pointer is of the requested type (or a subclass). + bool typeResolves(smtk::string::Token requestedType, smtk::string::Token immediateType); + bool typeResolvesRecursive(smtk::string::Token requestedType, smtk::string::Token immediateType); + + /// Map object->typeName() values to their superclass type-name. + /// + /// Searching this map repeatedly until no match is found will return the + /// name of the object's base-most type (i.e., to a key of m_objects). + /// This exists for type validation before casting. + std::unordered_map m_typeMap; + + /// Data held for each handle. + struct HandleEntry + { + HandleEntry(smtk::string::Token immediateType, smtk::view::Configuration* object) + : m_type(immediateType) + , m_resource(nullptr) + , m_object(object) + { + std::ostringstream ptr; + ptr << std::hex << object << std::dec; + m_handle = ptr.str(); + } + + HandleEntry(smtk::string::Token immediateType, smtk::attribute::Item* object) + : m_type(immediateType) + , m_resource(object->attribute()->parentResource()) + , m_object(object) + { + std::ostringstream ptr; + ptr << std::hex << object << std::dec; + m_handle = ptr.str(); + } + + template + HandleEntry(smtk::string::Token immediateType, T* object) + : m_type(immediateType) + , m_resource(object->parentResource()) + , m_object(object) + { + std::ostringstream ptr; + ptr << std::hex << object << std::dec; + m_handle = ptr.str(); + } + + /// The exact type of the data. + smtk::string::Token m_type; + /// The resource owning the handle. + /// + /// This is used to check the lock state. If a handle is locked, requests + /// for its underlying data (fromHandle<>) will be denied. + smtk::resource::Resource* m_resource; + /// The handle of the data. + std::string m_handle; + /// A pointer to the object. + void* m_object; + }; + + /// Map an object to a tuple holding its type and its handle-string. + std::unordered_map m_objects; + + /// Functions to call when an object underlying a handle is modified or expunged. + Observers m_observers; + + /// The currently-registered operation manager to observe for updates. + smtk::operation::Manager::Ptr m_operationManager; + + /// The observer key for a method that monitors operations + /// in order to remove handles as they are expunged. + /// + /// This manager deals with components, resources, and + /// attribute-items. + smtk::operation::Observers::Key m_watcher; +}; + +} // namespace view +} // namespace smtk + +#endif diff --git a/smtk/view/Registrar.cxx b/smtk/view/Registrar.cxx index 4da261ecef..f0bc3365fb 100644 --- a/smtk/view/Registrar.cxx +++ b/smtk/view/Registrar.cxx @@ -20,6 +20,7 @@ #include "smtk/view/ComponentPhraseModel.h" #include "smtk/view/DefaultOperationIcon.h" #include "smtk/view/EmptySubphraseGenerator.h" +#include "smtk/view/HandleManager.h" #include "smtk/view/LockedResourceBadge.h" #include "smtk/view/ObjectIconBadge.h" #include "smtk/view/PhraseModel.h" @@ -67,6 +68,47 @@ void Registrar::unregisterFrom(const smtk::common::Managers::Ptr& managers) managers->erase(); } +void Registrar::registerTo(const smtk::operation::Manager::Ptr& operationManager) +{ + // Add some string hashes to the token manager to avoid + // "Hash does not exist in database" errors. + (void)smtk::string::Token("smtk::resource::PersistentObject"); + (void)smtk::string::Token("smtk::resource::Component"); + (void)smtk::string::Token("smtk::resource::Resource"); + (void)smtk::string::Token("smtk::geometry::Resource"); + (void)smtk::string::Token("smtk::attribute::Resource"); + (void)smtk::string::Token("smtk::attribute::Attribute"); + (void)smtk::string::Token("smtk::attribute::Item"); + (void)smtk::string::Token("smtk::attribute::ValueItem"); + (void)smtk::string::Token("smtk::attribute::ValueItemTemplate"); + (void)smtk::string::Token("smtk::attribute::ValueItemTemplate"); + (void)smtk::string::Token("smtk::attribute::ValueItemTemplate"); + (void)smtk::string::Token("smtk::attribute::IntItem"); + (void)smtk::string::Token("smtk::attribute::StringItem"); + (void)smtk::string::Token("smtk::attribute::DoubleItem"); + (void)smtk::string::Token("smtk::attribute::VoidItem"); + (void)smtk::string::Token("smtk::attribute::ReferenceItem"); + (void)smtk::string::Token("smtk::attribute::ResourceItem"); + (void)smtk::string::Token("smtk::attribute::ComponentItem"); + (void)smtk::string::Token("smtk::attribute::FileSystemItem"); + (void)smtk::string::Token("smtk::attribute::FileItem"); + (void)smtk::string::Token("smtk::attribute::DirectoryItem"); + (void)smtk::string::Token("smtk::attribute::DateTimeItem"); + (void)smtk::string::Token("smtk::model::Resource"); + (void)smtk::string::Token("smtk::view::Configuration"); + (void)smtk::string::Token("smtk::view::View"); + + auto handleManager = smtk::view::HandleManager::instance(); + handleManager->registerOperationManager(operationManager); +} + +void Registrar::unregisterFrom(const smtk::operation::Manager::Ptr& operationManager) +{ + (void)operationManager; + auto handleManager = smtk::view::HandleManager::instance(); + handleManager->registerOperationManager(nullptr); +} + void Registrar::registerTo(const smtk::view::Manager::Ptr& viewManager) { viewManager->phraseModelFactory().registerTypes(); diff --git a/smtk/view/Registrar.h b/smtk/view/Registrar.h index 452ec3f25f..ccf21ce1a4 100644 --- a/smtk/view/Registrar.h +++ b/smtk/view/Registrar.h @@ -13,6 +13,7 @@ #include "smtk/CoreExports.h" #include "smtk/common/Managers.h" +#include "smtk/operation/Manager.h" #include "smtk/view/Manager.h" namespace smtk @@ -25,6 +26,9 @@ public: static void registerTo(const smtk::common::Managers::Ptr&); static void unregisterFrom(const smtk::common::Managers::Ptr&); + static void registerTo(const smtk::operation::Manager::Ptr&); + static void unregisterFrom(const smtk::operation::Manager::Ptr&); + static void registerTo(const smtk::view::Manager::Ptr&); static void unregisterFrom(const smtk::view::Manager::Ptr&); }; diff --git a/smtk/view/plugin/CMakeLists.txt b/smtk/view/plugin/CMakeLists.txt index 75ba5bbad4..a0d360fa42 100644 --- a/smtk/view/plugin/CMakeLists.txt +++ b/smtk/view/plugin/CMakeLists.txt @@ -1,6 +1,7 @@ smtk_add_plugin(smtkViewPlugin REGISTRAR smtk::view::Registrar MANAGERS smtk::common::Managers + smtk::operation::Manager smtk::view::Manager PARAVIEW_PLUGIN_ARGS VERSION 1.0) diff --git a/smtk/view/pybind11/PybindHandleManager.h b/smtk/view/pybind11/PybindHandleManager.h new file mode 100644 index 0000000000..78463fe0dc --- /dev/null +++ b/smtk/view/pybind11/PybindHandleManager.h @@ -0,0 +1,131 @@ +//========================================================================= +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//========================================================================= + +#ifndef pybind_smtk_view_HandleManager_h +#define pybind_smtk_view_HandleManager_h + +#include +#include +#include + +#include "smtk/attribute/Item.h" +#include "smtk/resource/PersistentObject.h" +#include "smtk/view/Configuration.h" +#include "smtk/view/HandleManager.h" + +#include +#include + +namespace py = pybind11; + +inline py::class_ pybind11_init_smtk_view_HandleManager_Observers_Key(py::module& m) +{ + py::class_ instance(m, "HandleObserversKey"); + instance + .def("assigned", &smtk::view::HandleManager::Observers::Key::assigned) + .def("release", &smtk::view::HandleManager::Observers::Key::release) + ; + return instance; +} + +inline py::class_ pybind11_init_smtk_view_HandleManager_Observers(py::module& m) +{ + py::class_ instance(m, "HandleObservers"); + instance + .def("insert", []( + smtk::view::HandleManager::Observers& observers, + std::function< + void( + const smtk::view::HandleManager::HandlesByType&, + smtk::view::HandleManager::EventType + ) + > observer, + smtk::view::HandleManager::Observers::Priority priority, + const std::string& description) + { + return observers.insert(observer, priority, false, description); + }, py::arg("observer"), py::arg("priority"), py::arg("description") + ) + .def("erase", []( + smtk::view::HandleManager::Observers& observers, + smtk::view::HandleManager::Observers::Key& key) + { + observers.erase(key); + }, + py::arg("key") + ) + ; + return instance; +} + +inline void pybind11_init_smtk_view_HandleManager_EventType(py::module &m) +{ + py::enum_(m, "HandleManagerEventType") + .value("Created", smtk::view::HandleManager::EventType::Created) + .value("Modified", smtk::view::HandleManager::EventType::Modified) + .value("Expunged", smtk::view::HandleManager::EventType::Expunged) + .export_values(); +} + +inline PySharedPtrClass pybind11_init_smtk_view_HandleManager(py::module &m) +{ + PySharedPtrClass< smtk::view::HandleManager > instance(m, "HandleManager"); // , py::module_local()); + pybind11_init_smtk_view_HandleManager_EventType(m); + instance + .def_static("instance", &smtk::view::HandleManager::instance) + .def("handle", [](smtk::view::HandleManager& handleManager, smtk::attribute::Item* item) + { + return handleManager.handle(item); + }, py::arg("item")) + .def("handle", [](smtk::view::HandleManager& handleManager, smtk::view::Configuration* view) + { + return handleManager.handle(view); + }, py::arg("view")) + .def("handle", [](smtk::view::HandleManager& handleManager, smtk::resource::PersistentObject* object) + { + return handleManager.handle(object); + }, py::arg("object")) + .def("item", [](smtk::view::HandleManager& handleManager, const std::string& ptrStr) + { + auto* item = handleManager.fromHandle(ptrStr); + if (!item) + { + return smtk::attribute::Item::Ptr(); + } + return item->shared_from_this(); + }) + .def("view", [](smtk::view::HandleManager& handleManager, const std::string& ptrStr) + { + auto* view = handleManager.fromHandle(ptrStr); + if (!view) + { + return smtk::view::Configuration::Ptr(); + } + return view->shared_from_this(); + }) + .def("object", [](smtk::view::HandleManager& handleManager, const std::string& ptrStr) + { + auto* object = handleManager.fromHandle(ptrStr); + if (!object) + { + return smtk::resource::PersistentObject::Ptr(); + } + return object->shared_from_this(); + }) + .def("observers", [](smtk::view::HandleManager& handleManager) + { + return &handleManager.observers(); + }, py::return_value_policy::reference_internal + ) + ; + return instance; +} + +#endif diff --git a/smtk/view/pybind11/PybindView.cxx b/smtk/view/pybind11/PybindView.cxx index 509c372f45..47ff6c0a2a 100644 --- a/smtk/view/pybind11/PybindView.cxx +++ b/smtk/view/pybind11/PybindView.cxx @@ -22,6 +22,7 @@ template using PySharedPtrClass = py::class_, Args...>; #include "PybindDescriptivePhrase.h" +#include "PybindHandleManager.h" #include "PybindSubphraseGenerator.h" #include "PybindSelection.h" #include "PybindSelectionObserver.h" @@ -50,4 +51,8 @@ PYBIND11_MODULE(_smtkPybindView, view) PySharedPtrClass< smtk::view::Configuration > smtk_view_View = pybind11_init_smtk_view_View(view); py::class_< smtk::view::SelectionObservers > smtk_view_SelectionObserver = pybind11_init_smtk_view_SelectionObservers(view); py::class_< smtk::view::Selection > smtk_view_Selection = pybind11_init_smtk_view_Selection(view); + PySharedPtrClass smtk_view_HandleManager = pybind11_init_smtk_view_HandleManager(view); + py::class_ smtk_view_HandleManager_Observers = pybind11_init_smtk_view_HandleManager_Observers(view); + py::class_ smtk_view_HandleManager_Observers_Key = pybind11_init_smtk_view_HandleManager_Observers_Key(view); + } diff --git a/smtk/view/pybind11/PybindView.h b/smtk/view/pybind11/PybindView.h index 1e998ef62f..1008229370 100644 --- a/smtk/view/pybind11/PybindView.h +++ b/smtk/view/pybind11/PybindView.h @@ -14,17 +14,13 @@ #include #include "smtk/view/Configuration.h" +#include "smtk/view/HandleManager.h" #include #include namespace py = pybind11; -namespace -{ - std::unordered_set allViews; -} - inline PySharedPtrClass< smtk::view::Configuration > pybind11_init_smtk_view_View(py::module &m) { PySharedPtrClass< smtk::view::Configuration > instance(m, "View"); @@ -38,23 +34,18 @@ inline PySharedPtrClass< smtk::view::Configuration > pybind11_init_smtk_view_Vie .def("iconName", &smtk::view::Configuration::iconName) .def("setIconName", &smtk::view::Configuration::setIconName, py::arg("name")) .def("details", (smtk::view::Configuration::Component& (smtk::view::Configuration::*)()) &smtk::view::Configuration::details, py::return_value_policy::reference) - .def("pointer", [](const smtk::view::Configuration& self) + .def("pointer", [](smtk::view::Configuration& self) { - std::ostringstream addr; - addr << std::hex << &self; - allViews.insert(&self); - return addr.str(); + return smtk::view::HandleManager::instance()->handle(&self); }) .def_static("fromPointer", [](const std::string& ptrStr) - { // FIXME: DO NOT COMMIT THIS - char* end = const_cast(ptrStr.c_str() + ptrStr.size()); - // NOLINTNEXTLINE(performance-no-int-to-ptr) - auto* ptr = reinterpret_cast(strtoull(ptrStr.c_str(), &end, 16)); - if (ptr && allViews.find(ptr) != allViews.end()) + { + auto* view = smtk::view::HandleManager::instance()->fromHandle(ptrStr); + if (!view) { - return ptr->shared_from_this(); + return smtk::view::Configuration::Ptr(); } - return smtk::view::Configuration::Ptr(); + return view->shared_from_this(); }) ; py::class_< smtk::view::Configuration::Component >(instance, "Component") -- GitLab From 28a84a0a4bb9d6c213a76a1c9fa23f182abbff93 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Mon, 30 Jun 2025 13:40:11 -0400 Subject: [PATCH 18/20] Be careful in registrars not to replace existing managers. The resource and operation registrars would sometimes replace manager instances unnecessarily; this could cause problems for python scripts. --- smtk/operation/Registrar.cxx | 19 +++++++++++-------- smtk/resource/Registrar.cxx | 20 ++++++++++++++++---- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/smtk/operation/Registrar.cxx b/smtk/operation/Registrar.cxx index ef1fb4b963..a6c338fb1b 100644 --- a/smtk/operation/Registrar.cxx +++ b/smtk/operation/Registrar.cxx @@ -61,17 +61,20 @@ typedef std::tuple< void Registrar::registerTo(const smtk::common::Managers::Ptr& managers) { - if (managers->insert(smtk::operation::Manager::create())) + if (!managers->contains()) { - managers->get()->setManagers(managers); - - if (managers->contains()) + if (managers->insert(smtk::operation::Manager::create())) { - managers->get()->registerResourceManager( - managers->get()); + managers->get()->setManagers(managers); + + if (managers->contains()) + { + managers->get()->registerResourceManager( + managers->get()); + } + smtk::plugin::Manager::instance()->registerPluginsTo( + managers->get()); } - smtk::plugin::Manager::instance()->registerPluginsTo( - managers->get()); } } diff --git a/smtk/resource/Registrar.cxx b/smtk/resource/Registrar.cxx index ae73b12096..be3c543409 100644 --- a/smtk/resource/Registrar.cxx +++ b/smtk/resource/Registrar.cxx @@ -16,18 +16,26 @@ #include "smtk/plugin/Manager.h" +namespace +{ +bool didCreateResourceManager = false; +} + namespace smtk { namespace resource { void Registrar::registerTo(const smtk::common::Managers::Ptr& managers) { - managers->insert(smtk::resource::Manager::create()); + if (!managers->contains()) + { + managers->insert(smtk::resource::Manager::create()); + didCreateResourceManager = true; + } auto resourceManager = managers->get(); smtk::plugin::Manager::instance()->registerPluginsTo(resourceManager); - managers->insert( - smtk::resource::query::Manager::create(managers->get())); + managers->insert(smtk::resource::query::Manager::create(resourceManager)); smtk::plugin::Manager::instance()->registerPluginsTo( managers->get()); @@ -42,7 +50,11 @@ void Registrar::registerTo(const smtk::common::Managers::Ptr& managers) void Registrar::unregisterFrom(const smtk::common::Managers::Ptr& managers) { - managers->erase(); + if (didCreateResourceManager) + { + managers->erase(); + } } + } // namespace resource } // namespace smtk -- GitLab From 45c0de1ee5f4b2b49b34a9fcd9a6ceedc40d8ad7 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Thu, 24 Jul 2025 16:41:27 -0400 Subject: [PATCH 19/20] Catch exceptions during operation observation/handling. --- doc/release/notes/operation-exceptions.rst | 10 +++++++ doc/userguide/operation/operators.rst | 5 ++++ smtk/operation/Operation.cxx | 34 ++++++++++++++++------ 3 files changed, 40 insertions(+), 9 deletions(-) create mode 100644 doc/release/notes/operation-exceptions.rst diff --git a/doc/release/notes/operation-exceptions.rst b/doc/release/notes/operation-exceptions.rst new file mode 100644 index 0000000000..558d79c9c7 --- /dev/null +++ b/doc/release/notes/operation-exceptions.rst @@ -0,0 +1,10 @@ +Operation System +================ + +Exception handling +------------------ + +Since April 2025, exceptions thrown from within :smtk:`smtk::operation::Operation::operateInternal` +have been caught to avoid situations where resource locks are never freed because the stack frame +holding the lock was unwound. We now catch exceptions thrown by operation observers and handlers +for the same reason. diff --git a/doc/userguide/operation/operators.rst b/doc/userguide/operation/operators.rst index 3d6beb6259..d0822eb5f8 100644 --- a/doc/userguide/operation/operators.rst +++ b/doc/userguide/operation/operators.rst @@ -15,6 +15,11 @@ may run in parallel in other threads. By using the locking mechanism which operations provide, you avoid race conditions, memory stomping, and many other issues encountered with parallel code. +All exceptions thrown inside an operation or an operation handler/observer (described +below) will be caught and logged as errors. +This is done to avoid situations where resource locks are never released because an +exception thrown inside these code segments. + The next sections describe in detail: first, how operators specify the inputs they require and outputs they produce; and second, how operators register themselves for introspection by applications and scripts. diff --git a/smtk/operation/Operation.cxx b/smtk/operation/Operation.cxx index 01c3f2c585..c47bc30494 100644 --- a/smtk/operation/Operation.cxx +++ b/smtk/operation/Operation.cxx @@ -331,6 +331,10 @@ Operation::Result Operation::operate(const BaseKey& key) smtk::attribute::IntItem::Ptr debugItem = this->parameters()->findInt("debug level"); m_debugLevel = ((debugItem && debugItem->isEnabled()) ? debugItem->value() : 0); + // We catch all exceptions here to avoid situations where resource + // locks are never released. You should never expect exceptions to + // be thrown out of an operation in any event because operations + // are run in separate threads. try { // Perform the derived operation. @@ -342,6 +346,7 @@ Operation::Result Operation::operate(const BaseKey& key) // This allows the operation to return normally so that // any threads do not continue to be blocked. result = this->createResult(Outcome::FAILED); + this->log().setFlushToStderr(true); smtkErrorMacro( this->log(), "An unhandled exception (" << e.what() << ") occurred in " << this->typeName() << "."); @@ -379,18 +384,29 @@ Operation::Result Operation::operate(const BaseKey& key) } } - // Execute post-operation observation - // Always call handlers, but based on the \a key, observers may be skipped. - // Handlers will always be invoked before other observers, but in priority order. - // In the future, we may attempt to interleave the handler and observer calls. - for (const auto& entry : instanceHandlers) + // We catch all exceptions here as well to avoid situations where resource + // locks are never released. + try { - entry.second(*this, result); + // Execute post-operation observation + // Always call handlers, but based on the \a key, observers may be skipped. + // Handlers will always be invoked before other observers, but in priority order. + // In the future, we may attempt to interleave the handler and observer calls. + for (const auto& entry : instanceHandlers) + { + entry.second(*this, result); + } + instanceHandlers.clear(); + if (key.m_observerOption == ObserverOption::InvokeObservers && observePostOperation && manager) + { + manager->observers()(*this, EventType::DID_OPERATE, result); + } } - instanceHandlers.clear(); - if (key.m_observerOption == ObserverOption::InvokeObservers && observePostOperation && manager) + catch (std::exception& e) { - manager->observers()(*this, EventType::DID_OPERATE, result); + this->log().setFlushToStderr(true); + smtkErrorMacro( + this->log(), "Caught exception \"" << e.what() << "\" during handler/observer invocation."); } // Un-manage any resources marked for removal before releasing locks. -- GitLab From 97368dd3ef3acf1973a6353da474e57eba7f917e Mon Sep 17 00:00:00 2001 From: David Thompson Date: Mon, 4 Aug 2025 13:02:06 -0400 Subject: [PATCH 20/20] Wrap python handler functions lock the GIL. --- smtk/operation/pybind11/PybindOperation.h | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/smtk/operation/pybind11/PybindOperation.h b/smtk/operation/pybind11/PybindOperation.h index 8fde55cb57..fbad0dc2d0 100644 --- a/smtk/operation/pybind11/PybindOperation.h +++ b/smtk/operation/pybind11/PybindOperation.h @@ -137,7 +137,22 @@ inline PySharedPtrClass< smtk::operation::Operation, smtk::operation::PyOperatio .def("createResult", &smtk::operation::Operation::createResult, py::arg("arg0")) .def("manager", &smtk::operation::Operation::manager) .def("managers", &smtk::operation::Operation::managers) - .def("addHandler", &smtk::operation::Operation::addHandler, py::arg("handler"), py::arg("priority") = 0) + .def("addHandler", + [](smtk::operation::Operation& op, smtk::operation::Handler handler, int priority) + { + // Wrap the \a handler in a new function object which obtains the GIL as the operation will + // not hold the GIL at the point handlers are invoked and the only \a handler that can be + // passed to us is a python function object (which requires the GIL). Add this wrapped handler + // to the operation rather than the one we are passed. + smtk::operation::Handler pyHandler = [handler]( + smtk::operation::Operation& op, const std::shared_ptr& result) + { + py::gil_scoped_acquire guard; + handler(op, result); + }; + op.addHandler(pyHandler, priority); + }, + py::arg("handler"), py::arg("priority") = 0) .def("removeHandler", &smtk::operation::Operation::removeHandler, py::arg("handler"), py::arg("priority")) .def("clearHandlers", &smtk::operation::Operation::clearHandlers) .def("restoreTrace", (bool (smtk::operation::Operation::*)(::std::string const &)) &smtk::operation::Operation::restoreTrace) -- GitLab